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,
+}