Skip to content

Commit

Permalink
Add a Subscribe API to libtock_platform.
Browse files Browse the repository at this point in the history
This API is based on the design at tock#341.
  • Loading branch information
jrvanwhy committed Nov 17, 2021
1 parent a69ab88 commit 3918cc5
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 5 deletions.
27 changes: 27 additions & 0 deletions platform/src/exit_on_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// 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
/// ```
/// use libtock_platform::exit_on_drop::ExitOnDrop;
/// fn function_that_must_not_unwind<S: libtock_platform::Syscalls>() {
/// let exit_on_drop: ExitOnDrop::<S> = Default::default();
/// /* Do something that might unwind here. */
/// core::mem::forget(exit_on_drop);
/// }
/// ```
pub struct ExitOnDrop<S: crate::Syscalls>(core::marker::PhantomData<S>);

impl<S: crate::Syscalls> Default for ExitOnDrop<S> {
fn default() -> ExitOnDrop<S> {
ExitOnDrop(core::marker::PhantomData)
}
}

impl<S: crate::Syscalls> Drop for ExitOnDrop<S> {
fn drop(&mut self) {
S::exit_terminate(0);
}
}
3 changes: 3 additions & 0 deletions platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion platform/src/raw_syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use crate::Register;
//
// These system calls are refined further individually, which is documented on
// a per-function basis.
pub unsafe trait RawSyscalls {
pub unsafe trait RawSyscalls: Sized {
// yield1 can only be used to call `yield-wait`, which does not have a
// return value. To simplify the assembly implementation, we remove its
// return value.
Expand Down
130 changes: 130 additions & 0 deletions platform/src/subscribe.rs
Original file line number Diff line number Diff line change
@@ -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<S>,

// 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<core::cell::Cell<&'scope ()>>,
}

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<SupportedIds> {
fn upcall(&self, arg0: u32, arg1: u32, arg2: u32);
}

pub trait SupportsId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> {}

pub struct AnyId;
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
for AnyId
{
}

pub struct OneId<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32>;
impl<const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>
for OneId<DRIVER_NUM, SUBSCRIBE_NUM>
{
}

// -----------------------------------------------------------------------------
// 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<AnyId> for core::cell::Cell<bool> {
fn upcall(&self, _: u32, _: u32, _: u32) {
self.set(true);
}
}

/// Implemented for consistency with the other `Cell<Option<...>>` `Upcall`
/// impls. Most users would prefer the `Cell<bool>` implementation over this
/// impl, but this may be useful in a generic or macro context.
impl Upcall<AnyId> for core::cell::Cell<Option<()>> {
fn upcall(&self, _: u32, _: u32, _: u32) {
self.set(Some(()));
}
}

/// An `Upcall` implementation that stores its first argument when called.
impl Upcall<AnyId> for core::cell::Cell<Option<(u32,)>> {
fn upcall(&self, arg0: u32, _: u32, _: u32) {
self.set(Some((arg0,)));
}
}

/// An `Upcall` implementation that stores its first two arguments when called.
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32)>> {
fn upcall(&self, arg0: u32, arg1: u32, _: u32) {
self.set(Some((arg0, arg1)));
}
}

/// An `Upcall` implementation that stores its arguments when called.
impl Upcall<AnyId> for core::cell::Cell<Option<(u32, u32, u32)>> {
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) {}
}
31 changes: 28 additions & 3 deletions platform/src/syscalls.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::{CommandReturn, YieldNoWaitReturn};
use crate::{
subscribe, CommandReturn, ErrorCode, RawSyscalls, Subscribe, Upcall, YieldNoWaitReturn,
};

/// `Syscalls` provides safe abstractions over Tock's system calls. It is
/// implemented for `libtock_runtime::TockSyscalls` and
/// `libtock_unittest::fake::Kernel` (by way of `RawSyscalls`).
pub trait Syscalls {
pub trait Syscalls: RawSyscalls + Sized {
// -------------------------------------------------------------------------
// Yield
// -------------------------------------------------------------------------
Expand All @@ -17,7 +19,26 @@ pub trait Syscalls {
/// callback, then returns.
fn yield_wait();

// TODO: Add a subscribe interface.
// -------------------------------------------------------------------------
// Subscribe
// -------------------------------------------------------------------------

/// Registers an upcall with the kernel.
fn subscribe<
'scope,
IDS: subscribe::SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>,
U: Upcall<IDS>,
CONFIG: subscribe::Config,
const DRIVER_NUM: u32,
const SUBSCRIBE_NUM: u32,
>(
subscribe: &Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
upcall: &'scope U,
) -> 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
Expand All @@ -31,6 +52,10 @@ pub trait Syscalls {

// TODO: Add memop() methods.

// -------------------------------------------------------------------------
// Exit
// -------------------------------------------------------------------------

fn exit_terminate(exit_code: u32) -> !;

fn exit_restart(exit_code: u32) -> !;
Expand Down
111 changes: 110 additions & 1 deletion platform/src/syscalls_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Implements `Syscalls` for all types that implement `RawSyscalls`.
use crate::{
exit_id, syscall_class, yield_id, CommandReturn, RawSyscalls, Syscalls, YieldNoWaitReturn,
exit_id, exit_on_drop, return_variant, subscribe, syscall_class, yield_id, CommandReturn,
ErrorCode, RawSyscalls, Register, ReturnVariant, Subscribe, Syscalls, Upcall,
YieldNoWaitReturn,
};

impl<S: RawSyscalls> Syscalls for S {
Expand Down Expand Up @@ -33,6 +35,113 @@ impl<S: RawSyscalls> Syscalls for S {
}
}

// -------------------------------------------------------------------------
// Subscribe
// -------------------------------------------------------------------------

fn subscribe<
'scope,
IDS: subscribe::SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>,
U: Upcall<IDS>,
CONFIG: subscribe::Config,
const DRIVER_NUM: u32,
const SUBSCRIBE_NUM: u32,
>(
_subscribe: &Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
upcall: &'scope U,
) -> Result<(), ErrorCode> {
// The upcall function passed to the Tock kernel.
//
// Safety: data must be a reference to a valid instance of U.
unsafe extern "C" fn kernel_upcall<S: Syscalls, IDS, U: Upcall<IDS>>(
arg0: u32,
arg1: u32,
arg2: u32,
data: Register,
) {
let upcall: *const U = data.into();
let exit: exit_on_drop::ExitOnDrop<S> = Default::default();
unsafe { &*upcall }.upcall(arg0, arg1, arg2);
core::mem::forget(exit);
}

// Inner function that does the majority of the work. This is not
// monomorphized over DRIVER_NUM and SUBSCRIBE_NUM to keep code size
// small.
//
// Safety: upcall_fcn must be kernel_upcall<S, IDS, U> and upcall_data
// must be a reference to an instance of U that will remain valid as
// long as the 'scope lifetime is alive. Can only be called if a
// Subscribe<'scope, S, driver_num, subscribe_num> exists.
unsafe fn inner<S: Syscalls, CONFIG: subscribe::Config>(
driver_num: u32,
subscribe_num: u32,
upcall_fcn: Register,
upcall_data: Register,
) -> Result<(), ErrorCode> {
// Safety: syscall4's documentation indicates it can be used to call
// Subscribe. These arguments follow TRD104. kernel_upcall has the
// required signature. This function's preconditions mean that
// upcall is a reference to an instance of U that will remain valid
// until the 'scope lifetime is alive The existence of the
// Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM> guarantees
// that if this Subscribe succeeds then the upcall will be cleaned
// up before the 'scope lifetime ends, guaranteeing that upcall is
// still alive when kernel_upcall is invoked.
let [r0, r1, _, _] = unsafe {
S::syscall4::<{ syscall_class::SUBSCRIBE }>([
driver_num.into(),
subscribe_num.into(),
upcall_fcn,
upcall_data,
])
};

let return_variant: ReturnVariant = r0.as_u32().into();
if return_variant == return_variant::FAILURE_2_U32 {
// Safety: TRD 104 guarantees that if r0 is Failure with 2 U32,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
Err(unsafe { core::mem::transmute(r1.as_u32() as u16) })
} else {
// r0 indicates Success with 2 u32s. Confirm the null upcall was
// returned, and it if wasn't then call the configured function.
// We're relying on the optimizer to remove this branch if
// returned_nonnull_upcall is a no-op.
let returned_upcall: *const () = r1.into();
#[allow(clippy::zero_ptr)]
if returned_upcall != 0 as *const () {
CONFIG::returned_nonnull_upcall(driver_num, subscribe_num);
}
Ok(())
}
}

let upcall_fcn = (kernel_upcall::<S, IDS, U> as usize).into();
let upcall_data = (upcall as *const U).into();
// Safety: upcall's type guarantees it is a reference to a U that will
// remain valid for at least the 'scope lifetime. _subscribe is a
// reference to a Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
// proving one exists. upcall_fcn and upcall_data are derived in ways
// that satisfy inner's requirements.
unsafe { inner::<Self, CONFIG>(DRIVER_NUM, SUBSCRIBE_NUM, upcall_fcn, upcall_data) }
}

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
// -------------------------------------------------------------------------
Expand Down
27 changes: 27 additions & 0 deletions syscalls_tests/src/exit_on_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use libtock_platform::exit_on_drop::ExitOnDrop;
use libtock_unittest::fake;

// Unwinds if `unwind` is true, otherwise just returns.
fn maybe_unwind(unwind: bool) {
if unwind {
panic!("Triggering stack unwinding.");
}
}

#[cfg(not(miri))]
#[test]
fn exit() {
let exit = libtock_unittest::exit_test("exit_on_drop::exit", || {
let exit_on_drop: ExitOnDrop<fake::Syscalls> = Default::default();
maybe_unwind(true);
core::mem::forget(exit_on_drop);
});
assert_eq!(exit, libtock_unittest::ExitCall::Terminate(0));
}

#[test]
fn no_exit() {
let exit_on_drop: ExitOnDrop<fake::Syscalls> = Default::default();
maybe_unwind(false);
core::mem::forget(exit_on_drop);
}
Loading

0 comments on commit 3918cc5

Please sign in to comment.