Merge #269

269: Implement system calls on RISC-V in libtock_runtime. r=phil-levis a=jrvanwhy

This introduces `TockSyscalls`, which implements `libtock_platform::RawSyscalls`. The blanket impl of `libtock_platform::Syscalls` in `libtock_platform` gives `TockSyscalls` a `Syscalls` implementation. I will implement ARM system calls in a future PR.

Co-authored-by: Johnathan Van Why <jrvanwhy@google.com>
diff --git a/core/platform/src/raw_syscalls.rs b/core/platform/src/raw_syscalls.rs
index 90fc602..f4e37b8 100644
--- a/core/platform/src/raw_syscalls.rs
+++ b/core/platform/src/raw_syscalls.rs
@@ -1,11 +1,11 @@
-// TODO: Implement `libtock_runtime` and `libtock_unittest`, which are
-// referenced in the comment on `RawSyscalls`.
+// TODO: Implement `libtock_unittest`, which is referenced in the comment on
+// `RawSyscalls`.
 
 /// `RawSyscalls` allows a fake Tock kernel to be injected into components for
 /// unit testing. It is implemented by `libtock_runtime::TockSyscalls` and
-/// `libtock_unittest::FakeSyscalls`. Components should not use `RawSyscalls`
+/// `libtock_unittest::FakeSyscalls`. **Components should not use `RawSyscalls`
 /// directly; instead, use the `Syscalls` trait, which provides higher-level
-/// interfaces to the system calls.
+/// interfaces to the system calls.**
 
 // RawSyscalls is designed to minimize the amount of handwritten assembly code
 // needed without generating unnecessary instructions. This comment describes
@@ -111,6 +111,7 @@
     // pass YieldType rather than a usize because if we used usize directly then
     // this API becomes unsound if the kernel adds support for an unsafe yield
     // type (or even one that takes one more argument).
+    /// `raw_yield` should only be called by `libtock_platform`.
     fn raw_yield(r0_in: YieldType) -> u32;
 
     // four_arg_syscall is used to invoke the subscribe, command, read-write
@@ -136,6 +137,8 @@
     //
     // For subscribe(), the callback pointer should be either 0 (for the null
     // callback) or an `unsafe extern fn(u32, u32, u32, usize)`.
+    /// `four_arg_syscall` should only be called by `libtock_platform`.
+    ///
     /// # Safety
     /// `four_arg_syscall` must NOT be used to invoke yield. Otherwise, it has
     /// the same safety invariants as the underlying system call, which varies
@@ -175,6 +178,7 @@
     // exist are safe. zero_arg_memop takes a ZeroArgMemop rather than a u32 so
     // that if the kernel adds an unsafe memop -- or one that can clobber r2/r3
     // --  this API doesn't become unsound.
+    /// `four_arg_syscall` should only be called by `libtock_platform`.
     fn zero_arg_memop(r0_in: ZeroArgMemop) -> (u32, usize);
 
     // one_arg_memop is used to invoke memop operations that take an argument.
@@ -199,6 +203,7 @@
     // exist are safe. zero_arg_memop takes a ZeroArgMemop rather than a u32 so
     // that if the kernel adds an unsafe memop -- or one that can clobber r2/r3
     // -- this API doesn't become unsound.
+    /// `four_arg_syscall` should only be called by `libtock_platform`.
     fn one_arg_memop(r0_in: OneArgMemop, r1: usize) -> (u32, usize);
 }
 
diff --git a/core/runtime/src/lib.rs b/core/runtime/src/lib.rs
index d7ea623..b0daf03 100644
--- a/core/runtime/src/lib.rs
+++ b/core/runtime/src/lib.rs
@@ -18,4 +18,11 @@
 //! `no_auto_layout` feature on `libtock_runtime` to disable this functionality
 //! and provide its own layout file.
 
+#![feature(asm)]
 #![no_std]
+
+/// TockSyscalls implements `libtock_platform::Syscalls`.
+pub struct TockSyscalls;
+
+#[cfg(target_arch = "riscv32")]
+mod syscalls_impl_riscv;
diff --git a/core/runtime/src/syscalls_impl_riscv.rs b/core/runtime/src/syscalls_impl_riscv.rs
new file mode 100644
index 0000000..3e635e8
--- /dev/null
+++ b/core/runtime/src/syscalls_impl_riscv.rs
@@ -0,0 +1,81 @@
+use libtock_platform::{OneArgMemop, RawSyscalls, YieldType, ZeroArgMemop};
+
+impl RawSyscalls for crate::TockSyscalls {
+    // This yield implementation is currently limited RISC-V versions without
+    // floating-point registers, as it does not mark them clobbered.
+    #[cfg(not(any(target_feature = "d", target_feature = "f")))]
+    fn raw_yield(r0_in: YieldType) -> u32 {
+        let mut r0 = r0_in as u32;
+        unsafe {
+            asm!("ecall",
+                 // x0 is the zero register.
+                 lateout("x1") _, // Return address
+                 // x2-x4 are stack, global, and thread pointers. sp is
+                 // callee-saved.
+                 lateout("x5") _, // t0
+                 lateout("x6") _, // t1
+                 lateout("x7") _, // t2
+                 // x8 and x9 are s0 and s1 and are callee-saved.
+                 inlateout("x10") r0,     // a0
+                 lateout("x11") _,        // a1
+                 lateout("x12") _,        // a2
+                 lateout("x13") _,        // a3
+                 inlateout("x14") 0 => _, // a4
+                 lateout("x15") _,        // a5
+                 lateout("x16") _,        // a6
+                 lateout("x17") _,        // a7
+                 // x18-27 are s2-s11 and are callee-saved
+                 lateout("x28") _, // t3
+                 lateout("x29") _, // t4
+                 lateout("x30") _, // t5
+                 lateout("x31") _, // t6
+            );
+        }
+        r0
+    }
+
+    unsafe fn four_arg_syscall(
+        mut r0: u32,
+        mut r1: u32,
+        mut r2: usize,
+        mut r3: usize,
+        class: u8,
+    ) -> (u32, usize, usize, usize) {
+        asm!("ecall",
+             inlateout("a0") r0,
+             inlateout("a1") r1,
+             inlateout("a2") r2,
+             inlateout("a3") r3,
+             in("a4") class,
+             options(preserves_flags, nostack),
+        );
+        (r0, r1 as usize, r2, r3)
+    }
+
+    fn zero_arg_memop(r0_in: ZeroArgMemop) -> (u32, usize) {
+        let mut r0 = r0_in as u32;
+        let r1;
+        unsafe {
+            asm!("ecall",
+                 inlateout("a0") r0,
+                 lateout("a1") r1,
+                 in("a4") 5,
+                 options(preserves_flags, nostack, nomem),
+            );
+        }
+        (r0, r1)
+    }
+
+    fn one_arg_memop(r0_in: OneArgMemop, mut r1: usize) -> (u32, usize) {
+        let mut r0 = r0_in as u32;
+        unsafe {
+            asm!("ecall",
+                 inlateout("a0") r0,
+                 inlateout("a1") r1,
+                 in("a4") 5,
+                 options(preserves_flags, nostack, nomem)
+            );
+        }
+        (r0, r1)
+    }
+}