Merge #222

222: Add the Allowed type to libtock_platform. r=hudson-ayers a=jrvanwhy

Allowed is the type that will be returned by `Platform::allow()` in the successful case. It represents a memory buffer shared with the kernel. It uses raw pointers and volatile writes to perform read/write accesses to the memory to avoid encountering undefined behavior.

This is the first step towards fixing #129 and #143.

Co-authored-by: Johnathan Van Why <jrvanwhy@google.com>
diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml
index 3617c11..527e418 100644
--- a/.github/workflows/artifacts.yml
+++ b/.github/workflows/artifacts.yml
@@ -1,5 +1,7 @@
 name: Artifacts
-on: [push]
+on:
+  push:
+    branches: [master]
 
 jobs:
   build:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..744ea69
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,44 @@
+# This workflow provides automated testing. It builds and runs tests on each PR.
+
+name: ci
+on: pull_request
+
+jobs:
+  ci:
+    # Using ubuntu-latest can cause breakage when ubuntu-latest is updated to
+    # point at a new Ubuntu version. Instead, explicitly specify the version, so
+    # we can update when we need to. This *could* break if we don't update it
+    # until support for 18.04 is dropped, but it is likely we'll have a reason
+    # to update to a newer Ubuntu before then anyway.
+    runs-on: ubuntu-18.04
+
+    steps:
+      # Clones a single commit from the libtock-rs repository. The commit cloned
+      # is a merge commit between the PR's target branch and the PR's source.
+      # Note that we checkout submodules so that we can invoke Tock's CI setup
+      # scripts, but we do not recursively checkout submodules as we need Tock's
+      # makefile to set up the qemu submodule itself.
+      - name: Clone repository
+        uses: actions/checkout@v2.3.0
+        with:
+          submodules: true
+
+      # Install a Rust toolchain with the components necessary to build
+      # libtock-rs and its examples. This action doesn't seem to be able to
+      # install toolchains for multiple targets, so I add the targets later in
+      # the "Build and Test" step.
+      - name: Install Rust toolchain
+        uses: actions-rs/toolchain@v1.0.6
+        with:
+          profile: minimal
+
+      # The main test step. We let the makefile do most of the work because the
+      # makefile can be tested locally. We experimentally determined that -j2 is
+      # optimal for the Azure Standard_DS2_v2 VM, which is the VM type used by
+      # GitHub Actions at the time of this writing.
+      - name: Build and Test
+        run: |
+          cd "${GITHUB_WORKSPACE}"
+          rustup target add riscv32imc-unknown-none-elf thumbv7em-none-eabi
+          make -j2 setup
+          make -j2 test
diff --git a/core/platform/src/lib.rs b/core/platform/src/lib.rs
index 11543ed..7237a1d 100644
--- a/core/platform/src/lib.rs
+++ b/core/platform/src/lib.rs
@@ -6,10 +6,12 @@
 //      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 allows;
 mod error_code;
+mod syscalls;
 
 pub use allows::{AllowReadable, Allowed};
 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,
+}