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 eab0f0e commit 9845789
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 3 deletions.
26 changes: 26 additions & 0 deletions platform/src/exit_on_drop.rs
Original file line number Diff line number Diff line change
@@ -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<S: libtock_platform::Syscalls>() {
/// let exit_on_drop: ExitOnDrop::<S> = Default::default();
/// operation_that_may_unwind();
/// 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
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: 29 additions & 2 deletions platform/src/syscalls.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<DRIVER_NUM, SUBSCRIBE_NUM>,
U: Upcall<IDS>,
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
Expand All @@ -31,6 +54,10 @@ pub trait Syscalls {

// TODO: Add memop() methods.

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

fn exit_terminate(exit_code: u32) -> !;

fn exit_restart(exit_code: u32) -> !;
Expand Down
32 changes: 31 additions & 1 deletion platform/src/syscalls_impl.rs
Original file line number Diff line number Diff line change
@@ -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<S: RawSyscalls> Syscalls for S {
Expand Down Expand Up @@ -33,6 +34,35 @@ 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,
>() -> 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
// -------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions syscalls_tests/src/exit_on_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use libtock_platform::exit_on_drop::ExitOnDrop;
use libtock_unittest::fake;

#[test]
fn no_exit() {
let exit_on_drop: ExitOnDrop<fake::Syscalls> = Default::default();
core::mem::forget(exit_on_drop);
}
3 changes: 3 additions & 0 deletions syscalls_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#[cfg(test)]
mod command_tests;

#[cfg(test)]
mod exit_on_drop;

// TODO: Add Exit.

// TODO: Add Memop.
Expand Down

0 comments on commit 9845789

Please sign in to comment.