Add the `Syscalls` trait to `libtock_platform`. `Syscalls` is a trait representing Tock's system call API. It will be implemented by both `libtock_runtime` (included by TBF binaries) and `libtock_fake` (included by unit tests). It will allow us to replace the real Tock kernel with a fake Tock kernel for testing purposes. `Syscalls` is designed to introduce no overhead over the system call assembly logic. For `allow`, `command`, and `subscribe`, it is based on Tock's `Driver` trait. A higher-level interface over Tock's system calls, for use by `libtock_core` and client code, will be introduced in a later PR.
diff --git a/core/platform/src/lib.rs b/core/platform/src/lib.rs index 64d520b..25c04df 100644 --- a/core/platform/src/lib.rs +++ b/core/platform/src/lib.rs
@@ -6,4 +6,8 @@ // kernel // 2. The PlatformApi trait and Platform implementation. // 3. A system call trait so that Platform works in both real Tock apps and -// unit test environments. +// unit test environments. [DONE] + +mod syscalls; + +pub use syscalls::{MemopNoArg, MemopWithArg, Syscalls};
diff --git a/core/platform/src/syscalls.rs b/core/platform/src/syscalls.rs new file mode 100644 index 0000000..9a78169 --- /dev/null +++ b/core/platform/src/syscalls.rs
@@ -0,0 +1,99 @@ +//! Provides the Syscalls trait which directly represents Tock's system call +//! APIs. Syscalls is implemented by both `libtock_runtime` which makes system +//! calls into a real Tock kernel, and `libtock_fake` which is a fake Tock +//! kernel. + +// TODO: Implement `libtock_runtime` and `libtock_fake`. + +/// Syscalls represents Tock's system call APIs. It is designed to be +/// implemented as easily as possible -- its arguments and return values +/// correspond directly to registers in the ABI. For a higher-level abstraction, +/// see Platform. +/// +/// By design, syscalls is designed to be zero-cost in a TBF binary and +/// functional (but not zero-cost) in unit tests. In a TBF binary, Syscalls is +/// implemented with the `'static` lifetime, and is a zero-sized type. Syscalls +/// requires `Copy` in order to support defining it usefully on zero-sized +/// types. When used in unit tests, the Syscalls implementation carries a +/// lifetime local to that unit test. +/// +/// With the exception of `memop`, this trait aligns closely to Tock's +/// kernel::Driver trait. +pub trait Syscalls<'k>: Copy { + /// Calls the `allow` system call. + /// + /// # Safety + /// `allow` is unsafe because callers must guarantee that `pointer` and + /// `length` refer to memory that the kernel can mutate safely. The buffer + /// must last for the lifetime 'k. + // `driver` and `minor` are `usize` because the kernel internally treats + // them as `usize`s. `allow`'s return value is a kernel `ReturnCode`; + // Platform translates the `isize` into a `ReturnCode`. + unsafe fn allow(self, driver: usize, minor: usize, pointer: *mut u8, length: usize) -> isize; + + /// Calls the `command` system call. + // `driver`, `minor`, `arg1`, and `arg2` are all `usize` (rather than `u32`) + // because the kernel refers to them internally as `usize`s. command returns + // a kernel ReturnCode; Platform is responsible for translating an isize + // into the local ReturnCode. + fn command(self, driver: usize, minor: usize, arg1: usize, arg2: usize) -> isize; + + /// Calls the `memop` system call with an argument. Note that memop() cannot + /// cause memory unsafety, although it can cause the app to fault (e.g. Brk + /// can move the app break below the stack, causing a fault). The isize + /// returned is a kernel ReturnCode. + // Platform performs the translation from isize into ReturnCode to keep + // Syscalls implementations simple. + fn memop_arg(self, op: MemopWithArg, arg: usize) -> isize; + + /// Calls the `memop` system call with no arguments. This version is + /// slightly cheaper because it does not need to set the argument register. + // We're okay with leaking the value in the argument register because + // memop() is always handled by the core kernel, never by an untrusted + // capsule. + fn memop_noarg(self, op: MemopNoArg) -> isize; + + /// Calls the `subscribe` system call. + /// + /// # Safety + /// `subscribe` is unsafe because the callback can potentially be unsafe, + /// and callers of `subscribe` must assert that calling the callback with + /// the provided `data` value is safe. The callback must last for the 'k + /// lifetime. + // Driver, minor, the callback args, and data are all represented as `usize` + // because that is the type the kernel uses internally to store them (e.g. + // as opposed to u32). + unsafe fn subscribe( + self, + driver: usize, + minor: usize, + callback: Option<unsafe extern "C" fn(usize, usize, usize, usize)>, + data: usize, + ); + + /// Puts the process to sleep until a callback becomes pending, then invokes + /// the callback. + fn yieldk(self); +} + +#[non_exhaustive] +#[repr(usize)] +pub enum MemopWithArg { + Brk = 0, + Sbrk = 1, + FlashRegionStart = 8, + FlashRegionEnd = 9, + SpecifyStackTop = 10, + SpecifyHeapStart = 11, +} + +#[non_exhaustive] +#[repr(usize)] +pub enum MemopNoArg { + MemoryStart = 2, + MemoryEnd = 3, + FlashStart = 4, + FlashEnd = 5, + GrantStart = 6, + FlashRegions = 7, +}