Skip to content

Commit 3c23fb3

Browse files
bors[bot]Johnathan Van Why
and
Johnathan Van Why
authored
Merge #344
344: Add a Subscribe API to `libtock_platform`. r=jrvanwhy a=jrvanwhy This API is based on the design at #341. Based on a request from Ti50, I added a configuration parameter to `subscribe`. Currently the only part of `subscribe` that is configurable is its behavior if the kernel returns a non-null upcall. By default I don't think `subscribe` should do anything special for that case (because it "shouldn't happen" and I want to keep code size small), but Ti50 wants to add a debug print to that branch. I also provided default `Upcall` implementations for several basic types (`Cell<Bool>`, `Cell<Option<(u32, u32, u32)>>`, and similar) which simply store their arguments and a flag indicating they were called. I expect these `Upcall` implementations to be used repeatedly across multiple codebases. Co-authored-by: Johnathan Van Why <[email protected]>
2 parents a69ab88 + 8db69b2 commit 3c23fb3

File tree

10 files changed

+512
-6
lines changed

10 files changed

+512
-6
lines changed

platform/src/default_config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// A general purpose syscall configuration, which drivers should use as their
2+
/// default syscall config.
3+
pub struct DefaultConfig;
4+
5+
impl crate::subscribe::Config for DefaultConfig {}

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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
mod async_traits;
55
mod command_return;
66
mod constants;
7+
mod default_config;
78
mod error_code;
9+
pub mod exit_on_drop;
810
mod raw_syscalls;
911
mod register;
1012
pub mod return_variant;
13+
pub mod subscribe;
1114
mod syscall_scope;
1215
mod syscalls;
1316
mod syscalls_impl;
@@ -17,10 +20,12 @@ mod yield_types;
1720
pub use async_traits::{CallbackContext, FreeCallback, Locator, MethodCallback};
1821
pub use command_return::CommandReturn;
1922
pub use constants::{exit_id, syscall_class, yield_id};
23+
pub use default_config::DefaultConfig;
2024
pub use error_code::ErrorCode;
2125
pub use raw_syscalls::RawSyscalls;
2226
pub use register::Register;
2327
pub use return_variant::ReturnVariant;
28+
pub use subscribe::{Subscribe, Upcall};
2429
pub use syscall_scope::syscall_scope;
2530
pub use syscalls::Syscalls;
2631
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: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 is 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+
// Safety: The safety invariant is inherited from the ShareList trait's
40+
// definition. The caller must guarantee that Drop::drop is called on this
41+
// Subscribe before the 'scope lifetime ends.
42+
unsafe fn new() -> Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM> {
43+
Subscribe {
44+
_syscalls: core::marker::PhantomData,
45+
_scope: core::marker::PhantomData,
46+
}
47+
}
48+
}
49+
50+
// -----------------------------------------------------------------------------
51+
// `Upcall` trait
52+
// -----------------------------------------------------------------------------
53+
54+
/// A Tock kernel upcall. Upcalls are registered using the Subscribe system
55+
/// call, and are invoked during Yield calls.
56+
///
57+
/// Each `Upcall` supports one or more subscribe IDs, which are indicated by the
58+
/// `SupportedIds` parameter. The types `AnySubscribeId` and `OneSubscribeId`
59+
/// are provided to use as `SupportedIds` parameters in `Upcall`
60+
/// implementations.
61+
pub trait Upcall<SupportedIds> {
62+
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32);
63+
}
64+
65+
pub trait SupportsId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> {}
66+
67+
pub struct AnyId;
68+
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
69+
for AnyId
70+
{
71+
}
72+
73+
pub struct OneId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32>;
74+
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
75+
for OneId<DRIVER_NUM, SUBSCRIBE_NUM>
76+
{
77+
}
78+
79+
// -----------------------------------------------------------------------------
80+
// Upcall implementations that simply store their arguments
81+
// -----------------------------------------------------------------------------
82+
83+
/// An implementation of `Upcall` that sets the contained boolean value to
84+
/// `true` when the upcall is invoked.
85+
impl Upcall<AnyId> for core::cell::Cell<bool> {
86+
fn upcall(&self, _: u32, _: u32, _: u32) {
87+
self.set(true);
88+
}
89+
}
90+
91+
/// Implemented for consistency with the other `Cell<Option<...>>` `Upcall`
92+
/// impls. Most users would prefer the `Cell<bool>` implementation over this
93+
/// impl, but this may be useful in a generic or macro context.
94+
impl Upcall<AnyId> for core::cell::Cell<Option<()>> {
95+
fn upcall(&self, _: u32, _: u32, _: u32) {
96+
self.set(Some(()));
97+
}
98+
}
99+
100+
/// An `Upcall` implementation that stores its first argument when called.
101+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32,)>> {
102+
fn upcall(&self, arg0: u32, _: u32, _: u32) {
103+
self.set(Some((arg0,)));
104+
}
105+
}
106+
107+
/// An `Upcall` implementation that stores its first two arguments when called.
108+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32)>> {
109+
fn upcall(&self, arg0: u32, arg1: u32, _: u32) {
110+
self.set(Some((arg0, arg1)));
111+
}
112+
}
113+
114+
/// An `Upcall` implementation that stores its arguments when called.
115+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32, u32)>> {
116+
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) {
117+
self.set(Some((arg0, arg1, arg2)));
118+
}
119+
}
120+
121+
#[cfg(test)]
122+
#[test]
123+
fn upcall_impls() {
124+
let cell_bool = core::cell::Cell::new(false);
125+
cell_bool.upcall(1, 2, 3);
126+
assert!(cell_bool.get());
127+
128+
let cell_empty = core::cell::Cell::new(None);
129+
cell_empty.upcall(1, 2, 3);
130+
assert_eq!(cell_empty.get(), Some(()));
131+
132+
let cell_one = core::cell::Cell::new(None);
133+
cell_one.upcall(1, 2, 3);
134+
assert_eq!(cell_one.get(), Some((1,)));
135+
136+
let cell_two = core::cell::Cell::new(None);
137+
cell_two.upcall(1, 2, 3);
138+
assert_eq!(cell_two.get(), Some((1, 2)));
139+
140+
let cell_three = core::cell::Cell::new(None);
141+
cell_three.upcall(1, 2, 3);
142+
assert_eq!(cell_three.get(), Some((1, 2, 3)));
143+
}
144+
145+
// -----------------------------------------------------------------------------
146+
// `Config` trait
147+
// -----------------------------------------------------------------------------
148+
149+
/// `Config` configures the behavior of the Subscribe system call. It should
150+
/// generally be passed through by drivers, to allow application code to
151+
/// configure error handling.
152+
pub trait Config {
153+
/// Called if a Subscribe call succeeds and returns a non-null upcall. In
154+
/// some applications, this may indicate unexpected reentrance. By default,
155+
/// the non-null upcall is ignored.
156+
fn returned_nonnull_upcall(_driver_num: u32, _subscribe_num: u32) {}
157+
}

platform/src/syscalls.rs

Lines changed: 28 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,26 @@ 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+
fn subscribe<
28+
'scope,
29+
IDS: subscribe::SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>,
30+
U: Upcall<IDS>,
31+
CONFIG: subscribe::Config,
32+
const DRIVER_NUM: u32,
33+
const SUBSCRIBE_NUM: u32,
34+
>(
35+
subscribe: &Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
36+
upcall: &'scope U,
37+
) -> Result<(), ErrorCode>;
38+
39+
/// Unregisters the upcall with the given ID. If no upcall is registered
40+
/// with the given ID, `unsubscribe` does nothing.
41+
fn unsubscribe(driver_num: u32, subscribe_num: u32);
2142

2243
// -------------------------------------------------------------------------
2344
// Command
@@ -31,6 +52,10 @@ pub trait Syscalls {
3152

3253
// TODO: Add memop() methods.
3354

55+
// -------------------------------------------------------------------------
56+
// Exit
57+
// -------------------------------------------------------------------------
58+
3459
fn exit_terminate(exit_code: u32) -> !;
3560

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

0 commit comments

Comments
 (0)