Skip to content

Commit 1416599

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 1416599

File tree

10 files changed

+494
-6
lines changed

10 files changed

+494
-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 is used as the default
2+
/// configuration for `libtock_rs`' system calls.
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: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
// TODO: Test Drop impl
29+
impl<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> Drop
30+
for Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM>
31+
{
32+
fn drop(&mut self) {
33+
S::unsubscribe(DRIVER_NUM, SUBSCRIBE_NUM);
34+
}
35+
}
36+
37+
impl<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> ShareList<'scope>
38+
for Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM>
39+
{
40+
unsafe fn new() -> Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM> {
41+
Subscribe {
42+
_syscalls: core::marker::PhantomData,
43+
_scope: core::marker::PhantomData,
44+
}
45+
}
46+
}
47+
48+
// -----------------------------------------------------------------------------
49+
// `Upcall` trait
50+
// -----------------------------------------------------------------------------
51+
52+
/// A Tock kernel upcall. Upcalls are registered using the Subscribe system
53+
/// call, and are invoked during Yield calls.
54+
///
55+
/// Each `Upcall` supports one or more subscribe IDs, which are indicated by the
56+
/// `SupportedIds` parameter. The types `AnySubscribeId` and `OneSubscribeId`
57+
/// are provided to use as `SupportedIds` parameters in `Upcall`
58+
/// implementations.
59+
pub trait Upcall<SupportedIds> {
60+
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32);
61+
}
62+
63+
pub trait SupportsId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> {}
64+
65+
pub struct AnyId;
66+
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
67+
for AnyId
68+
{
69+
}
70+
71+
pub struct OneId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32>;
72+
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
73+
for OneId<DRIVER_NUM, SUBSCRIBE_NUM>
74+
{
75+
}
76+
77+
// -----------------------------------------------------------------------------
78+
// Upcall implementations that simply store their arguments
79+
// -----------------------------------------------------------------------------
80+
81+
/// An implementation of `Upcall` that sets the contained boolean value to
82+
/// `true` when the upcall is invoked.
83+
impl Upcall<AnyId> for core::cell::Cell<bool> {
84+
fn upcall(&self, _: u32, _: u32, _: u32) {
85+
self.set(true);
86+
}
87+
}
88+
89+
/// Implemented for consistency with the other `Cell<Option<...>>` `Upcall`
90+
/// impls. Most users would prefer the `Cell<bool>` implementation over this
91+
/// impl, but this may be useful in a generic or macro context.
92+
impl Upcall<AnyId> for core::cell::Cell<Option<()>> {
93+
fn upcall(&self, _: u32, _: u32, _: u32) {
94+
self.set(Some(()));
95+
}
96+
}
97+
98+
/// An `Upcall` implementation that stores its first argument when called.
99+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32,)>> {
100+
fn upcall(&self, arg0: u32, _: u32, _: u32) {
101+
self.set(Some((arg0,)));
102+
}
103+
}
104+
105+
/// An `Upcall` implementation that stores its first two arguments when called.
106+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32)>> {
107+
fn upcall(&self, arg0: u32, arg1: u32, _: u32) {
108+
self.set(Some((arg0, arg1)));
109+
}
110+
}
111+
112+
/// An `Upcall` implementation that stores its arguments when called.
113+
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32, u32)>> {
114+
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) {
115+
self.set(Some((arg0, arg1, arg2)));
116+
}
117+
}
118+
119+
#[cfg(test)]
120+
#[test]
121+
fn upcall_impls() {
122+
let cell_bool = core::cell::Cell::new(false);
123+
cell_bool.upcall(1, 2, 3);
124+
assert!(cell_bool.get());
125+
126+
let cell_empty = core::cell::Cell::new(None);
127+
cell_empty.upcall(1, 2, 3);
128+
assert_eq!(cell_empty.get(), Some(()));
129+
130+
let cell_one = core::cell::Cell::new(None);
131+
cell_one.upcall(1, 2, 3);
132+
assert_eq!(cell_one.get(), Some((1,)));
133+
134+
let cell_two = core::cell::Cell::new(None);
135+
cell_two.upcall(1, 2, 3);
136+
assert_eq!(cell_two.get(), Some((1, 2)));
137+
138+
let cell_three = core::cell::Cell::new(None);
139+
cell_three.upcall(1, 2, 3);
140+
assert_eq!(cell_three.get(), Some((1, 2, 3)));
141+
}
142+
143+
// -----------------------------------------------------------------------------
144+
// `Config` trait
145+
// -----------------------------------------------------------------------------
146+
147+
/// `Config` configures the behavior of the Subscribe system call. It should
148+
/// generally be passed through by drivers, to allow application code to
149+
/// configure error handling.
150+
pub trait Config {
151+
/// Called if a Subscribe call succeeds and returns a non-null upcall. In
152+
/// some applications, this may indicate unexpected reentrance. By default,
153+
/// the non-null upcall is ignored.
154+
fn returned_nonnull_upcall(_driver_num: u32, _subscribe_num: u32) {}
155+
}

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)