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