From 984578933f0888e91f26e46fdffa9e57b7d3c7fe Mon Sep 17 00:00:00 2001 From: Johnathan Van Why Date: Fri, 12 Nov 2021 16:12:21 -0800 Subject: [PATCH] Add a Subscribe API to `libtock_platform`. This API is based on the design at https://github.com/tock/libtock-rs/issues/341. --- platform/src/exit_on_drop.rs | 26 ++++++ platform/src/lib.rs | 3 + platform/src/subscribe.rs | 130 +++++++++++++++++++++++++++++ platform/src/syscalls.rs | 31 ++++++- platform/src/syscalls_impl.rs | 32 ++++++- syscalls_tests/src/exit_on_drop.rs | 8 ++ syscalls_tests/src/lib.rs | 3 + 7 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 platform/src/exit_on_drop.rs create mode 100644 platform/src/subscribe.rs create mode 100644 syscalls_tests/src/exit_on_drop.rs diff --git a/platform/src/exit_on_drop.rs b/platform/src/exit_on_drop.rs new file mode 100644 index 000000000..499bb39d9 --- /dev/null +++ b/platform/src/exit_on_drop.rs @@ -0,0 +1,26 @@ +/// Calls the Exit system call when dropped. Used to catch panic unwinding from +/// a `#![no_std]` context. The caller should `core::mem::forget` the +/// `ExitOnDrop` when it no longer needs to catch unwinding. +/// +/// +/// # Example +/// ``` +/// fn function_that_must_not_unwind() { +/// let exit_on_drop: ExitOnDrop:: = Default::default(); +/// operation_that_may_unwind(); +/// core::mem::forget(exit_on_drop); +/// } +/// ``` +pub struct ExitOnDrop(core::marker::PhantomData); + +impl Default for ExitOnDrop { + fn default() -> ExitOnDrop { + ExitOnDrop(core::marker::PhantomData) + } +} + +impl Drop for ExitOnDrop { + fn drop(&mut self) { + S::exit_terminate(0); + } +} diff --git a/platform/src/lib.rs b/platform/src/lib.rs index ef68b3c9c..bd5655efd 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -5,9 +5,11 @@ mod async_traits; mod command_return; mod constants; mod error_code; +pub mod exit_on_drop; mod raw_syscalls; mod register; pub mod return_variant; +pub mod subscribe; mod syscall_scope; mod syscalls; mod syscalls_impl; @@ -21,6 +23,7 @@ pub use error_code::ErrorCode; pub use raw_syscalls::RawSyscalls; pub use register::Register; pub use return_variant::ReturnVariant; +pub use subscribe::{Subscribe, Upcall}; pub use syscall_scope::syscall_scope; pub use syscalls::Syscalls; pub use termination::Termination; diff --git a/platform/src/subscribe.rs b/platform/src/subscribe.rs new file mode 100644 index 000000000..d8fbb4272 --- /dev/null +++ b/platform/src/subscribe.rs @@ -0,0 +1,130 @@ +use crate::syscall_scope::ShareList; +use crate::Syscalls; + +// ----------------------------------------------------------------------------- +// `Subscribe` struct +// ----------------------------------------------------------------------------- + +/// A `Subscribe` instance allows safe code to call Tock's Subscribe system +/// call, by guaranteeing the upcall will be cleaned up before 'scope ends. It +/// is generally used with the `syscall_scope` function, which offers a safe +/// interface for constructing `Subscribe` instances. +pub struct Subscribe<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> { + _syscalls: core::marker::PhantomData, + + // Make this struct invariant with respect to the 'scope lifetime. + // + // Covariance would be unsound, as that would allow code with a + // `Subscribe<'static, ...>` to register an upcall that lasts for a shorter + // lifetime, resulting in use-after-free if the upcall in invoked. + // Contravariance would be sound, but is not necessary and may be confusing. + // + // Additionally, we want to have at least one private member of this struct + // so that code outside this module cannot construct a `Subscribe` without + // calling `ShareList::new`. + _scope: core::marker::PhantomData>, +} + +impl<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> Drop + for Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM> +{ + fn drop(&mut self) { + S::unsubscribe(DRIVER_NUM, SUBSCRIBE_NUM); + } +} + +impl<'scope, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> ShareList<'scope> + for Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM> +{ + unsafe fn new() -> Subscribe<'scope, S, DRIVER_NUM, SUBSCRIBE_NUM> { + Subscribe { + _syscalls: core::marker::PhantomData, + _scope: core::marker::PhantomData, + } + } +} + +// ----------------------------------------------------------------------------- +// `Upcall` trait +// ----------------------------------------------------------------------------- + +/// A Tock kernel upcall. Upcalls are registered using the Subscribe system +/// call, and are invoked during Yield calls. +/// +/// Each `Upcall` supports one or more subscribe IDs, which are indicated by the +/// `SupportedIds` parameter. The types `AnySubscribeId` and `OneSubscribeId` +/// are provided to use as `SupportedIds` parameters in `Upcall` +/// implementations. +pub trait Upcall { + fn upcall(&self, arg0: u32, arg1: u32, arg2: u32); +} + +pub trait SupportsId {} + +pub struct AnyId; +impl SupportsId + for AnyId +{ +} + +pub struct OneId; +impl SupportsId + for OneId +{ +} + +// ----------------------------------------------------------------------------- +// Upcall implementations that simply store their arguments +// ----------------------------------------------------------------------------- + +/// An implementation of `Upcall` that sets the contained boolean value to +/// `true` when the upcall is invoked. +impl Upcall for core::cell::Cell { + fn upcall(&self, _: u32, _: u32, _: u32) { + self.set(true); + } +} + +/// Implemented for consistency with the other `Cell>` `Upcall` +/// impls. Most users would prefer the `Cell` implementation over this +/// impl, but this may be useful in a generic or macro context. +impl Upcall for core::cell::Cell> { + fn upcall(&self, _: u32, _: u32, _: u32) { + self.set(Some(())); + } +} + +/// An `Upcall` implementation that stores its first argument when called. +impl Upcall for core::cell::Cell> { + fn upcall(&self, arg0: u32, _: u32, _: u32) { + self.set(Some((arg0,))); + } +} + +/// An `Upcall` implementation that stores its first two arguments when called. +impl Upcall for core::cell::Cell> { + fn upcall(&self, arg0: u32, arg1: u32, _: u32) { + self.set(Some((arg0, arg1))); + } +} + +/// An `Upcall` implementation that stores its arguments when called. +impl Upcall for core::cell::Cell> { + fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) { + self.set(Some((arg0, arg1, arg2))); + } +} + +// ----------------------------------------------------------------------------- +// `Config` trait +// ----------------------------------------------------------------------------- + +/// `Config` configures the behavior of the Subscribe system call. It should +/// generally be passed through by drivers, to allow application code to +/// configure error handling. +pub trait Config { + /// Called if a Subscribe call succeeds and returns a non-null upcall. In + /// some applications, this may indicate unexpected reentrance. By default, + /// the non-null upcall is ignored. + fn returned_nonnull_upcall(_driver_num: u32, _subscribe_num: u32) {} +} diff --git a/platform/src/syscalls.rs b/platform/src/syscalls.rs index e87cc89b4..91889bd88 100644 --- a/platform/src/syscalls.rs +++ b/platform/src/syscalls.rs @@ -1,4 +1,4 @@ -use crate::{CommandReturn, YieldNoWaitReturn}; +use crate::{subscribe, CommandReturn, ErrorCode, Upcall, YieldNoWaitReturn}; /// `Syscalls` provides safe abstractions over Tock's system calls. It is /// implemented for `libtock_runtime::TockSyscalls` and @@ -17,7 +17,30 @@ pub trait Syscalls { /// callback, then returns. fn yield_wait(); - // TODO: Add a subscribe interface. + // ------------------------------------------------------------------------- + // Subscribe + // ------------------------------------------------------------------------- + + /// Registers an upcall with the kernel. + /// + /// `REVERT_NONNULL_RETURN` controls the behavior of `subscribe` if the + /// Subscribe calls succeeds but returns a non-null upcall. If + /// `REVERT_NONNULL_RETURN` is `true`, then `subscribe` will restore the + /// previous upcall and return `Err(ErrorCode::Already)`. If + /// `REVERT_NONNULL_RETURN` is `false`, the `subscribe` will succeed + /// regardless. + fn subscribe< + 'scope, + IDS: subscribe::SupportsId, + U: Upcall, + CONFIG: subscribe::Config, + const DRIVER_NUM: u32, + const SUBSCRIBE_NUM: u32, + >() -> Result<(), ErrorCode>; + + /// Unregisters the upcall with the given ID. If no upcall is registered + /// with the given ID, `unsubscribe` does nothing. + fn unsubscribe(driver_num: u32, subscribe_num: u32); // ------------------------------------------------------------------------- // Command @@ -31,6 +54,10 @@ pub trait Syscalls { // TODO: Add memop() methods. + // ------------------------------------------------------------------------- + // Exit + // ------------------------------------------------------------------------- + fn exit_terminate(exit_code: u32) -> !; fn exit_restart(exit_code: u32) -> !; diff --git a/platform/src/syscalls_impl.rs b/platform/src/syscalls_impl.rs index 3399c063b..836021b2a 100644 --- a/platform/src/syscalls_impl.rs +++ b/platform/src/syscalls_impl.rs @@ -1,7 +1,8 @@ //! Implements `Syscalls` for all types that implement `RawSyscalls`. use crate::{ - exit_id, syscall_class, yield_id, CommandReturn, RawSyscalls, Syscalls, YieldNoWaitReturn, + exit_id, subscribe, syscall_class, yield_id, CommandReturn, ErrorCode, RawSyscalls, Syscalls, + Upcall, YieldNoWaitReturn, }; impl Syscalls for S { @@ -33,6 +34,35 @@ impl Syscalls for S { } } + // ------------------------------------------------------------------------- + // Subscribe + // ------------------------------------------------------------------------- + + fn subscribe< + 'scope, + IDS: subscribe::SupportsId, + U: Upcall, + CONFIG: subscribe::Config, + const DRIVER_NUM: u32, + const SUBSCRIBE_NUM: u32, + >() -> Result<(), ErrorCode> { + unimplemented!("TODO") + } + + fn unsubscribe(driver_num: u32, subscribe_num: u32) { + unsafe { + // syscall4's documentation indicates it can be used to call + // Subscribe. The upcall pointer passed is the null upcall, which + // cannot cause undefined behavior on its own. + Self::syscall4::<{ syscall_class::SUBSCRIBE }>([ + driver_num.into(), + subscribe_num.into(), + 0usize.into(), + 0usize.into(), + ]); + } + } + // ------------------------------------------------------------------------- // Command // ------------------------------------------------------------------------- diff --git a/syscalls_tests/src/exit_on_drop.rs b/syscalls_tests/src/exit_on_drop.rs new file mode 100644 index 000000000..d1e5decad --- /dev/null +++ b/syscalls_tests/src/exit_on_drop.rs @@ -0,0 +1,8 @@ +use libtock_platform::exit_on_drop::ExitOnDrop; +use libtock_unittest::fake; + +#[test] +fn no_exit() { + let exit_on_drop: ExitOnDrop = Default::default(); + core::mem::forget(exit_on_drop); +} diff --git a/syscalls_tests/src/lib.rs b/syscalls_tests/src/lib.rs index 202f9f20f..ca80e4a91 100644 --- a/syscalls_tests/src/lib.rs +++ b/syscalls_tests/src/lib.rs @@ -15,6 +15,9 @@ #[cfg(test)] mod command_tests; +#[cfg(test)] +mod exit_on_drop; + // TODO: Add Exit. // TODO: Add Memop.