Skip to content

Commit c251e0a

Browse files
author
Johnathan Van Why
committed
Add a Subscribe API to libtock_platform.
This API is based on the design at tock#341.
1 parent a69ab88 commit c251e0a

File tree

8 files changed

+318
-6
lines changed

8 files changed

+318
-6
lines changed

platform/src/exit_on_drop.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// Calls the Exit system call when dropped. Used to catch panic unwinding from
2+
/// a `#![no_std]` context. The caller should `core::mem::forget` the
3+
/// `ExitOnDrop` when it no longer needs to catch unwinding.
4+
///
5+
///
6+
/// # Example
7+
/// ```
8+
/// use libtock_platform::exit_on_drop::ExitOnDrop;
9+
/// fn function_that_must_not_unwind<S: libtock_platform::Syscalls>() {
10+
/// let exit_on_drop: ExitOnDrop::<S> = Default::default();
11+
/// /* Do something that might unwind here. */
12+
/// core::mem::forget(exit_on_drop);
13+
/// }
14+
/// ```
15+
pub struct ExitOnDrop<S: crate::Syscalls>(core::marker::PhantomData<S>);
16+
17+
impl<S: crate::Syscalls> Default for ExitOnDrop<S> {
18+
fn default() -> ExitOnDrop<S> {
19+
ExitOnDrop(core::marker::PhantomData)
20+
}
21+
}
22+
23+
impl<S: crate::Syscalls> Drop for ExitOnDrop<S> {
24+
fn drop(&mut self) {
25+
S::exit_terminate(0);
26+
}
27+
}

platform/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ mod async_traits;
55
mod command_return;
66
mod constants;
77
mod error_code;
8+
pub mod exit_on_drop;
89
mod raw_syscalls;
910
mod register;
1011
pub mod return_variant;
12+
pub mod subscribe;
1113
mod syscall_scope;
1214
mod syscalls;
1315
mod syscalls_impl;
@@ -21,6 +23,7 @@ pub use error_code::ErrorCode;
2123
pub use raw_syscalls::RawSyscalls;
2224
pub use register::Register;
2325
pub use return_variant::ReturnVariant;
26+
pub use subscribe::{Subscribe, Upcall};
2427
pub use syscall_scope::syscall_scope;
2528
pub use syscalls::Syscalls;
2629
pub use termination::Termination;

platform/src/raw_syscalls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ use crate::Register;
5959
//
6060
// These system calls are refined further individually, which is documented on
6161
// a per-function basis.
62-
pub unsafe trait RawSyscalls {
62+
pub unsafe trait RawSyscalls: Sized {
6363
// yield1 can only be used to call `yield-wait`, which does not have a
6464
// return value. To simplify the assembly implementation, we remove its
6565
// return value.

platform/src/subscribe.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use crate::syscall_scope::ShareList;
2+
use crate::Syscalls;
3+
4+
// -----------------------------------------------------------------------------
5+
// `Subscribe` struct
6+
// -----------------------------------------------------------------------------
7+
8+
/// A `Subscribe` instance allows safe code to call Tock's Subscribe system
9+
/// call, by guaranteeing the upcall will be cleaned up before 'scope ends. It
10+
/// is generally used with the `syscall_scope` function, which offers a safe
11+
/// interface for constructing `Subscribe` instances.
12+
pub struct Subscribe<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> {
13+
_syscalls: core::marker::PhantomData<S>,
14+
15+
// Make this struct invariant with respect to the 'scope lifetime.
16+
//
17+
// Covariance would be unsound, as that would allow code with a
18+
// `Subscribe<'static, ...>` to register an upcall that lasts for a shorter
19+
// lifetime, resulting in use-after-free if the upcall in invoked.
20+
// Contravariance would be sound, but is not necessary and may be confusing.
21+
//
22+
// Additionally, we want to have at least one private member of this struct
23+
// so that code outside this module cannot construct a `Subscribe` without
24+
// calling `ShareList::new`.
25+
_scope: core::marker::PhantomData<core::cell::Cell<&'scope ()>>,
26+
}
27+
28+
impl<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> Drop
29+
for Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM>
30+
{
31+
fn drop(&mut self) {
32+
S::unsubscribe(DRIVER_NUM, SUBSCRIBE_NUM);
33+
}
34+
}
35+
36+
impl<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> ShareList<'scope>
37+
for Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM>
38+
{
39+
unsafe fn new() -> Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM> {
40+
Subscribe {
41+
_syscalls: core::marker::PhantomData,
42+
_scope: core::marker::PhantomData,
43+
}
44+
}
45+
}
46+
47+
// -----------------------------------------------------------------------------
48+
// `Upcall` trait
49+
// -----------------------------------------------------------------------------
50+
51+
/// A Tock kernel upcall. Upcalls are registered using the Subscribe system
52+
/// call, and are invoked during Yield calls.
53+
///
54+
/// Each `Upcall` supports one or more subscribe IDs, which are indicated by the
55+
/// `SupportedIds` parameter. The types `AnySubscribeId` and `OneSubscribeId`
56+
/// are provided to use as `SupportedIds` parameters in `Upcall`
57+
/// implementations.
58+
pub trait Upcall<SupportedIds> {
59+
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32);
60+
}
61+
62+
pub trait SupportsId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> {}
63+
64+
pub struct AnyId;
65+
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
66+
for AnyId
67+
{
68+
}
69+
70+
pub struct OneId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32>;
71+
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
72+
for OneId<DRIVER_NUM, SUBSCRIBE_NUM>
73+
{
74+
}
75+
76+
// -----------------------------------------------------------------------------
77+
// Upcall implementations that simply store their arguments
78+
// -----------------------------------------------------------------------------
79+
80+
/// An implementation of `Upcall` that sets the contained boolean value to
81+
/// `true` when the upcall is invoked.
82+
impl Upcall<AnyId> for core::cell::Cell<bool> {
83+
fn upcall(&self, _: u32, _: u32, _: u32) {
84+
self.set(true);
85+
}
86+
}
87+
88+
/// Implemented for consistency with the other `Cell<Option<...>>` `Upcall`
89+
/// impls. Most users would prefer the `Cell<bool>` implementation over this
90+
/// impl, but this may be useful in a generic or macro context.
91+
impl Upcall<AnyId> for core::cell::Cell<Option<()>> {
92+
fn upcall(&self, _: u32, _: u32, _: u32) {
93+
self.set(Some(()));
94+
}
95+
}
96+
97+
/// An `Upcall` implementation that stores its first argument when called.
98+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32,)>> {
99+
fn upcall(&self, arg0: u32, _: u32, _: u32) {
100+
self.set(Some((arg0,)));
101+
}
102+
}
103+
104+
/// An `Upcall` implementation that stores its first two arguments when called.
105+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32)>> {
106+
fn upcall(&self, arg0: u32, arg1: u32, _: u32) {
107+
self.set(Some((arg0, arg1)));
108+
}
109+
}
110+
111+
/// An `Upcall` implementation that stores its arguments when called.
112+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32, u32)>> {
113+
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) {
114+
self.set(Some((arg0, arg1, arg2)));
115+
}
116+
}
117+
118+
// -----------------------------------------------------------------------------
119+
// `Config` trait
120+
// -----------------------------------------------------------------------------
121+
122+
/// `Config` configures the behavior of the Subscribe system call. It should
123+
/// generally be passed through by drivers, to allow application code to
124+
/// configure error handling.
125+
pub trait Config {
126+
/// Called if a Subscribe call succeeds and returns a non-null upcall. In
127+
/// some applications, this may indicate unexpected reentrance. By default,
128+
/// the non-null upcall is ignored.
129+
fn returned_nonnull_upcall(_driver_num: u32, _subscribe_num: u32) {}
130+
}

platform/src/syscalls.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use crate::{CommandReturn, YieldNoWaitReturn};
1+
use crate::{
2+
subscribe, CommandReturn, ErrorCode, RawSyscalls, Subscribe, Upcall, YieldNoWaitReturn,
3+
};
24

35
/// `Syscalls` provides safe abstractions over Tock's system calls. It is
46
/// implemented for `libtock_runtime::TockSyscalls` and
57
/// `libtock_unittest::fake::Kernel` (by way of `RawSyscalls`).
6-
pub trait Syscalls {
8+
pub trait Syscalls: RawSyscalls + Sized {
79
// -------------------------------------------------------------------------
810
// Yield
911
// -------------------------------------------------------------------------
@@ -17,7 +19,33 @@ pub trait Syscalls {
1719
/// callback, then returns.
1820
fn yield_wait();
1921

20-
// TODO: Add a subscribe interface.
22+
// -------------------------------------------------------------------------
23+
// Subscribe
24+
// -------------------------------------------------------------------------
25+
26+
/// Registers an upcall with the kernel.
27+
///
28+
/// `REVERT_NONNULL_RETURN` controls the behavior of `subscribe` if the
29+
/// Subscribe calls succeeds but returns a non-null upcall. If
30+
/// `REVERT_NONNULL_RETURN` is `true`, then `subscribe` will restore the
31+
/// previous upcall and return `Err(ErrorCode::Already)`. If
32+
/// `REVERT_NONNULL_RETURN` is `false`, the `subscribe` will succeed
33+
/// regardless.
34+
fn subscribe<
35+
'scope,
36+
IDS: subscribe::SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>,
37+
U: Upcall<IDS>,
38+
CONFIG: subscribe::Config,
39+
const DRIVER_NUM: u32,
40+
const SUBSCRIBE_NUM: u32,
41+
>(
42+
subscribe: &Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
43+
upcall: &'scope U,
44+
) -> Result<(), ErrorCode>;
45+
46+
/// Unregisters the upcall with the given ID. If no upcall is registered
47+
/// with the given ID, `unsubscribe` does nothing.
48+
fn unsubscribe(driver_num: u32, subscribe_num: u32);
2149

2250
// -------------------------------------------------------------------------
2351
// Command
@@ -31,6 +59,10 @@ pub trait Syscalls {
3159

3260
// TODO: Add memop() methods.
3361

62+
// -------------------------------------------------------------------------
63+
// Exit
64+
// -------------------------------------------------------------------------
65+
3466
fn exit_terminate(exit_code: u32) -> !;
3567

3668
fn exit_restart(exit_code: u32) -> !;

platform/src/syscalls_impl.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
//! Implements `Syscalls` for all types that implement `RawSyscalls`.
22
33
use crate::{
4-
exit_id, syscall_class, yield_id, CommandReturn, RawSyscalls, Syscalls, YieldNoWaitReturn,
4+
exit_id, exit_on_drop, return_variant, subscribe, syscall_class, yield_id, CommandReturn,
5+
ErrorCode, RawSyscalls, Register, ReturnVariant, Subscribe, Syscalls, Upcall,
6+
YieldNoWaitReturn,
57
};
68

79
impl<S: RawSyscalls> Syscalls for S {
810
// -------------------------------------------------------------------------
9-
// Yield
11+
// Y ield
1012
// -------------------------------------------------------------------------
1113

1214
fn yield_no_wait() -> YieldNoWaitReturn {
@@ -33,6 +35,113 @@ impl<S: RawSyscalls> Syscalls for S {
3335
}
3436
}
3537

38+
// -------------------------------------------------------------------------
39+
// Subscribe
40+
// -------------------------------------------------------------------------
41+
42+
fn subscribe<
43+
'scope,
44+
IDS: subscribe::SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>,
45+
U: Upcall<IDS>,
46+
CONFIG: subscribe::Config,
47+
const DRIVER_NUM: u32,
48+
const SUBSCRIBE_NUM: u32,
49+
>(
50+
_subscribe: &Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
51+
upcall: &'scope U,
52+
) -> Result<(), ErrorCode> {
53+
// The upcall function passed to the Tock kernel.
54+
//
55+
// Safety: data must be a reference to a valid instance of U.
56+
unsafe extern "C" fn kernel_upcall<S: Syscalls, IDS, U: Upcall<IDS>>(
57+
arg0: u32,
58+
arg1: u32,
59+
arg2: u32,
60+
data: Register,
61+
) {
62+
let upcall: *const U = data.into();
63+
let exit: exit_on_drop::ExitOnDrop<S> = Default::default();
64+
unsafe { &*upcall }.upcall(arg0, arg1, arg2);
65+
core::mem::forget(exit);
66+
}
67+
68+
// Inner function that does the majority of the work. This is not
69+
// monomorphized over DRIVER_NUM and SUBSCRIBE_NUM to keep code size
70+
// small.
71+
//
72+
// Safety: upcall_fcn must be kernel_upcall<S, IDS, U> and upcall_data
73+
// must be a reference to an instance of U that will remain valid as
74+
// long as the 'scope lifetime is alive. Can only be called if a
75+
// Subscribe<'scope, S, driver_num, subscribe_num> exists.
76+
unsafe fn inner<S: Syscalls, CONFIG: subscribe::Config>(
77+
driver_num: u32,
78+
subscribe_num: u32,
79+
upcall_fcn: Register,
80+
upcall_data: Register,
81+
) -> Result<(), ErrorCode> {
82+
// Safety: syscall4's documentation indicates it can be used to call
83+
// Subscribe. These arguments follow TRD104. kernel_upcall has the
84+
// required signature. This function's preconditions mean that
85+
// upcall is a reference to an instance of U that will remain valid
86+
// until the 'scope lifetime is alive The existence of the
87+
// Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM> guarantees
88+
// that if this Subscribe succeeds then the upcall will be cleaned
89+
// up before the 'scope lifetime ends, guaranteeing that upcall is
90+
// still alive when kernel_upcall is invoked.
91+
let [r0, r1, _, _] = unsafe {
92+
S::syscall4::<{ syscall_class::SUBSCRIBE }>([
93+
driver_num.into(),
94+
subscribe_num.into(),
95+
upcall_fcn,
96+
upcall_data,
97+
])
98+
};
99+
100+
let return_variant: ReturnVariant = r0.as_u32().into();
101+
if return_variant == return_variant::FAILURE_2_U32 {
102+
// Safety: TRD 104 guarantees that if r0 is Failure with 2 U32,
103+
// then r1 will contain a valid error code. ErrorCode is
104+
// designed to be safely transmuted directly from a kernel error
105+
// code.
106+
Err(unsafe { core::mem::transmute(r1.as_u32() as u16) })
107+
} else {
108+
// r0 indicates Success with 2 u32s. Confirm the null upcall was
109+
// returned, and it if wasn't then call the configured function.
110+
// We're relying on the optimizer to remove this branch if
111+
// returned_nonnull_upcall is a no-op.
112+
let returned_upcall: *const () = r1.into();
113+
#[allow(clippy::zero_ptr)]
114+
if returned_upcall != 0 as *const () {
115+
CONFIG::returned_nonnull_upcall(driver_num, subscribe_num);
116+
}
117+
Ok(())
118+
}
119+
}
120+
121+
let upcall_fcn = (kernel_upcall::<S, IDS, U> as usize).into();
122+
let upcall_data = (upcall as *const U).into();
123+
// Safety: upcall's type guarantees it is a reference to a U that will
124+
// remain valid for at least the 'scope lifetime. _subscribe is a
125+
// reference to a Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
126+
// proving one exists. upcall_fcn and upcall_data are derived in ways
127+
// that satisfy inner's requirements.
128+
unsafe { inner::<Self, CONFIG>(DRIVER_NUM, SUBSCRIBE_NUM, upcall_fcn, upcall_data) }
129+
}
130+
131+
fn unsubscribe(driver_num: u32, subscribe_num: u32) {
132+
unsafe {
133+
// syscall4's documentation indicates it can be used to call
134+
// Subscribe. The upcall pointer passed is the null upcall, which
135+
// cannot cause undefined behavior on its own.
136+
Self::syscall4::<{ syscall_class::SUBSCRIBE }>([
137+
driver_num.into(),
138+
subscribe_num.into(),
139+
0usize.into(),
140+
0usize.into(),
141+
]);
142+
}
143+
}
144+
36145
// -------------------------------------------------------------------------
37146
// Command
38147
// -------------------------------------------------------------------------

syscalls_tests/src/exit_on_drop.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use libtock_platform::exit_on_drop::ExitOnDrop;
2+
use libtock_unittest::fake;
3+
4+
#[test]
5+
fn no_exit() {
6+
let exit_on_drop: ExitOnDrop<fake::Syscalls> = Default::default();
7+
core::mem::forget(exit_on_drop);
8+
}

syscalls_tests/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
#[cfg(test)]
1616
mod command_tests;
1717

18+
#[cfg(test)]
19+
mod exit_on_drop;
20+
1821
// TODO: Add Exit.
1922

2023
// TODO: Add Memop.

0 commit comments

Comments
 (0)