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 48636b1
Show file tree
Hide file tree
Showing 10 changed files with 377 additions and 6 deletions.
5 changes: 5 additions & 0 deletions platform/src/default_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// A general purpose syscall configuration, which is used as the default
/// configuration for `libtock_rs`' system calls.
pub struct DefaultConfig;

impl crate::subscribe::Config for DefaultConfig {}
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);
}
}
5 changes: 5 additions & 0 deletions platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
mod async_traits;
mod command_return;
mod constants;
mod default_config;
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 @@ -17,10 +20,12 @@ mod yield_types;
pub use async_traits::{CallbackContext, FreeCallback, Locator, MethodCallback};
pub use command_return::CommandReturn;
pub use constants::{exit_id, syscall_class, yield_id};
pub use default_config::DefaultConfig;
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
155 changes: 155 additions & 0 deletions platform/src/subscribe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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 ()>>,
}

// TODO: Test Drop impl
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)));
}
}

#[cfg(test)]
#[test]
fn upcall_impls() {
let cell_bool = core::cell::Cell::new(false);
cell_bool.upcall(1, 2, 3);
assert!(cell_bool.get());

let cell_empty = core::cell::Cell::new(None);
cell_empty.upcall(1, 2, 3);
assert_eq!(cell_empty.get(), Some(()));

let cell_one = core::cell::Cell::new(None);
cell_one.upcall(1, 2, 3);
assert_eq!(cell_one.get(), Some((1,)));

let cell_two = core::cell::Cell::new(None);
cell_two.upcall(1, 2, 3);
assert_eq!(cell_two.get(), Some((1, 2)));

let cell_three = core::cell::Cell::new(None);
cell_three.upcall(1, 2, 3);
assert_eq!(cell_three.get(), Some((1, 2, 3)));
}

// -----------------------------------------------------------------------------
// `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
Loading

0 comments on commit 48636b1

Please sign in to comment.