Merge #221
221: Add the `Syscalls` trait to `libtock_platform`. r=hudson-ayers a=jrvanwhy
`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.
Co-authored-by: Johnathan Van Why <jrvanwhy@google.com>
diff --git a/core/platform/src/lib.rs b/core/platform/src/lib.rs
index 24c46f5..2e3dec6 100644
--- a/core/platform/src/lib.rs
+++ b/core/platform/src/lib.rs
@@ -6,8 +6,10 @@
// 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 error_code;
+mod syscalls;
pub use error_code::ErrorCode;
+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,
+}