Merge branch 'master' into raw-syscalls
I cleaned up the #![feature(min_const_generics)] statements because the toolchain update made them unnecessary.
diff --git a/core/platform/src/lib.rs b/core/platform/src/lib.rs
index d474f2b..e189975 100644
--- a/core/platform/src/lib.rs
+++ b/core/platform/src/lib.rs
@@ -8,13 +8,15 @@
pub mod return_variant;
mod syscalls;
mod syscalls_impl;
+mod yield_types;
pub use async_traits::{CallbackContext, FreeCallback, Locator, MethodCallback};
pub use command_return::CommandReturn;
pub use error_code::ErrorCode;
-pub use raw_syscalls::{OneArgMemop, RawSyscalls, YieldType, ZeroArgMemop};
+pub use raw_syscalls::RawSyscalls;
pub use return_variant::ReturnVariant;
pub use syscalls::Syscalls;
+pub use yield_types::YieldNoWaitReturn;
#[cfg(test)]
mod command_return_tests;
diff --git a/core/platform/src/raw_syscalls.rs b/core/platform/src/raw_syscalls.rs
index f4e37b8..cd5115f 100644
--- a/core/platform/src/raw_syscalls.rs
+++ b/core/platform/src/raw_syscalls.rs
@@ -3,98 +3,94 @@
/// `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::fake::Kernel`. **Components should not use `RawSyscalls`
/// directly; instead, use the `Syscalls` trait, which provides higher-level
/// interfaces to the system calls.**
-// RawSyscalls is designed to minimize the amount of handwritten assembly code
-// needed without generating unnecessary instructions. This comment describes
-// the thought process that led to the choice of methods for RawSyscalls. There
-// are a few major considerations affecting its design:
+// The RawSyscalls trait is designed to minimize the complexity and size of its
+// implementation, as its implementation is difficult to test (it cannot be used
+// in unit tests, with sanitizers, or in Miri). It is also designed to minimize
+// the number of unnecessary instructions it generates.
//
-// 1. Most system calls only clobber r0-r4 (*), while yield has a far longer
-// clobber list. As such, yield must have its own assembly
-// implementation.
-// 2. The compiler is unable to optimize away unused arguments. For example,
-// memop's "get process RAM start address" operation only needs r0 set,
-// while memop's "break" operation needs both r0 and r1 set. If our
-// inline assembly calls "get process RAM start address" but sets both r0
-// and r1, the compiler doesn't know that r1 will be
-// ignored so setting that register will not be optimized away. Therefore
-// we want to set the minimum number of argument registers possible.
-// 3. The cost of specifying unused return registers is only that of
-// unnecessarily marking a register as clobbered. Explanation: After
-// inlining, an unused register is marked as "changed by the assembly"
-// but can immediately be re-used by the compiler, which is the same as a
-// clobbered register. System calls should generally be
-// inlined -- and even if they aren't, the unused return values will
-// probably be passed in caller-saved registers (this is true for the C
-// ABI, so probably true for the Rust ABI), which are treated as
-// clobbered regardless.
+// Convention: This file uses the same register naming conventions as the Tock
+// 2.0 syscall TRD. Registers r0-r4 correspond to ARM registers r0-r4 and RISC-V
+// registers a0-a4.
//
-// (*) When this file refers to registers, it uses the same naming convention as
-// the Tock 2.0 syscalls TRD. Registers r0-r4 correspond to ARM registers r0-r4
-// and RISC-V registers a0-a4.
+// Theoretically, RawSyscalls could consist of a single raw system call. To
+// start, something like this should work:
//
-// Currently, yield takes exactly one argument (to specify what yield type to
-// do). Therefore we only need one raw yield call.
+// unsafe fn syscall<const CLASS: usize>([usize; 4]) -> [usize; 4];
//
-// Based on these considerations, it would make sense to have the following
-// methods:
-// yield
-// zero_arg_syscall
-// one_arg_syscall
-// two_arg_syscall
-// three_arg_syscall
-// four_arg_syscall
+// However, this will not work with Miri's -Zmiri-track-raw-pointers flag, as it
+// causes pointers passed to the kernel via the Allow system calls to be
+// untagged. In order to work with -Zmiri-track-raw-pointers, we need to pass
+// pointers for the register values. Rust's closest analogue to C's void pointer
+// is *mut () or *const (); we use *mut () because it is shorter:
//
-// However, there are no system calls that take 0 or 3 arguments, so we do not
-// need the corresponding methods. This leaves yield, 1-arg, 2-arg, and 4-arg
-// system calls.
+// unsafe fn syscall<const CLASS: usize>([*mut (); 4]) -> [*mut (); 4];
//
-// The 1-arg and 2-arg system calls are only used for memop. Memop currently has
-// the property that none of its operations can lead to undefined behavior.
-// Therefore, we can rename the 1-arg and 2-arg system calls to zero_arg_memop
-// and one_arg_memop and make them safe methods (the argument counts change
-// because the number of system call arguments is one greater than the number of
-// arguments passed to the specific op).
+// Using a single system call has a major inefficiency. The single raw system
+// call would need to clobber every register that any system call can clobber.
+// Yield has a far longer clobber list than most system calls, so this would be
+// inefficient for the majority of system calls. As a result, we can split yield
+// out into its own function, giving the following API:
//
-// This only leaves four_arg_syscall, which is used to implement subscribe,
-// command, read-write allow, and read-only allow.
+// unsafe fn yield([*mut (); 4]) -> [*mut (); 4];
+// unsafe fn syscall<const CLASS: usize>([*mut (); 4]) -> [*mut (); 4];
//
-// Therefore the final design has 4 methods:
-// yield
-// zero_arg_memop
-// one_arg_memop
-// four_arg_syscall
+// There is one significant inefficiency remaining. Many system calls, such as
+// memop's "get RAM start address" operation, do not need to set all four
+// arguments. The compiler cannot optimize away this inefficiency, so to remove
+// it we need to split the system calls up based on the number of arguments they
+// take:
//
-// If a new system call class that uses fewer than four arguments is added, then
-// the above list will need to be revised.
+// unsafe fn yield0([*mut (); 0]) -> [*mut (); 4];
+// unsafe fn yield1([*mut (); 1]) -> [*mut (); 4];
+// unsafe fn yield2([*mut (); 2]) -> [*mut (); 4];
+// unsafe fn yield3([*mut (); 3]) -> [*mut (); 4];
+// unsafe fn yield4([*mut (); 4]) -> [*mut (); 4];
+// unsafe fn syscall0<const CLASS: usize>([*mut (); 0]) -> [*mut (); 4];
+// unsafe fn syscall1<const CLASS: usize>([*mut (); 1]) -> [*mut (); 4];
+// unsafe fn syscall2<const CLASS: usize>([*mut (); 2]) -> [*mut (); 4];
+// unsafe fn syscall3<const CLASS: usize>([*mut (); 3]) -> [*mut (); 4];
+// unsafe fn syscall4<const CLASS: usize>([*mut (); 4]) -> [*mut (); 4];
//
-// Note that `command` always needs to use four_arg_syscall, even when calling a
-// command with fewer arguments, because we don't want to leak the
-// (possibly secret) values in the r2 and r3 registers to untrusted capsules.
-// Yield and memop do not have this concern and can leave arbitrary data in r2
-// and r3, because they are implemented by the core kernel, which is trusted.
+// However, not all of these are used! If we remove the system calls that are
+// unused, we are left with the following:
//
-// The success type for Memop calls depends on the operation performed. However,
-// all *currently defined* memop operations return either Success or Success
-// with u32. Therefore, the memop implementations only need to mark r0 and r1 as
-// clobbered, not r2 and r3. This choice of clobbers will need to be revisited
-// if and when a memop operation that returns more data is added.
+// unsafe fn yield1([*mut (); 1]) -> [*mut (); 4];
+// unsafe fn yield2([*mut (); 2]) -> [*mut (); 4];
+// unsafe fn syscall1<const CLASS: usize>([*mut (); 1]) -> [*mut (); 4];
+// unsafe fn syscall2<const CLASS: usize>([*mut (); 2]) -> [*mut (); 4];
+// unsafe fn syscall4<const CLASS: usize>([*mut (); 4]) -> [*mut (); 4];
//
-// The decision of where to use u32 and usize can be a bit tricky. The Tock
-// syscall ABI is currently only specified for 32-bit systems, so on real Tock
-// systems both types match the size of a register, but the unit test
-// environment can be either 32 bit or 64 bit. This interface uses usize for
-// values that can contain pointers, so that pointers are not truncated in the
-// unit test environment. To keep types as consistent as possible, it uses u32
-// for all values that cannot be pointers.
-pub trait RawSyscalls {
- // raw_yield should:
+// To avoid making the RawSyscalls implementation index into arrays, we replace
+// the arrays in the input with multiple arguments. For symmetry, we also
+// replace the output with a tuple of individual values. This gives:
+//
+// unsafe fn yield1(*mut ()) -> (*mut (), *mut (), *mut (), *mut ());
+//
+// unsafe fn yield2(*mut (), *mut ()) -> (*mut (), *mut (), *mut (), *mut ());
+//
+// unsafe fn syscall1<const CLASS: usize>(*mut ())
+// -> (*mut (), *mut (), *mut (), *mut ());
+//
+// unsafe fn syscall2<const CLASS: usize>(*mut (), *mut ())
+// -> (*mut (), *mut (), *mut (), *mut ());
+//
+// unsafe fn syscall4<const CLASS: usize>(*mut (), *mut (), *mut (), *mut ())
+// -> (*mut (), *mut (), *mut (), *mut ());
+//
+// These system calls are refined further individually, which is documented on
+// a per-function basis.
+pub unsafe trait RawSyscalls {
+ // yield1 can only be used to call `yield-wait`, which does not have a
+ // return value. To simplify the assembly implementation, we remove its
+ // return value.
+ //
+ // yield1 should:
// 1. Call syscall class 0
- // 2. Use register r0 for input and output as an inlateout register,
- // passing in r0_in and returning its value.
+ // 2. Pass in r0 as an inlateout register.
// 3. Mark all caller-saved registers as lateout clobbers.
// 4. NOT provide any of the following options:
// pure (yield has side effects)
@@ -103,142 +99,111 @@
// preserves_flags (a callback can change flags)
// noreturn (yield is expected to return)
// nostack (a callback needs the stack)
- //
- // Design note: This is safe because the yield types that currently exist
- // are safe. If an unsafe yield type is added, we will need to make
- // raw_yield unsafe. Although raw_yield shouldn't be called by code outside
- // this crate, it can be, so that is a backwards-incompatible change. We
- // 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
- // allow, and read-only allow system calls.
- //
- // four_arg_syscall's inline assembly should have the following properties:
- // 1. Calls the syscall class specified by class
- // 2. Passes r0-r3 in the corresponding registers as inlateout
- // registers. Returns r0-r3 in order.
- // 3. Does not mark any registers as clobbered.
- // 4. Has all of the following options:
- // preserves_flags (these system calls do not touch flags)
- // nostack (these system calls do not touch the stack)
- // 5. Does NOT have any of the following options:
- // pure (these system calls have side effects)
- // nomem (the compiler needs to write to globals before allow)
- // readonly (rw allow can modify memory)
- // noreturn (all these system calls are expected to return)
- //
- // Note that subscribe's application data argument can potentially contain a
- // pointer, so r3 can contain a pointer (in addition to r1 and r2, which
- // more obviously contain pointers for subscribe and memop).
- //
- // 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`.
- ///
+ /// `yield1` 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
- /// depending on the system call class.
- unsafe fn four_arg_syscall(
- r0: u32,
- r1: u32,
- r2: usize,
- r3: usize,
- class: u8,
- ) -> (u32, usize, usize, usize);
+ /// yield1 may only be used for yield operations that do not return a value.
+ /// It is exactly as safe as the underlying system call.
+ unsafe fn yield1(r0: *mut ());
- // zero_arg_memop is used to invoke memop operations that do not accept an
- // argument register. Because there are no memop commands that set r2 or r3,
- // this only needs to return r0 and r1.
+ // yield2 can only be used to call `yield-no-wait`. `yield-no-wait` does not
+ // return any values, so to simplify the assembly we omit return arguments.
+ //
+ // yield2 should:
+ // 1. Call syscall class 0
+ // 2. Pass in r0 and r1 as inlateout registers.
+ // 3. Mark all caller-saved registers as lateout clobbers.
+ // 4. NOT provide any of the following options:
+ // pure (yield has side effects)
+ // nomem (a callback can read + write globals)
+ // readonly (a callback can write globals)
+ // preserves_flags (a callback can change flags)
+ // noreturn (yield is expected to return)
+ // nostack (a callback needs the stack)
+ /// `yield2` should only be called by `libtock_platform`.
+ /// # Safety
+ /// yield2 may only be used for yield operations that do not return a value.
+ /// It has the same safety invariants as the underlying system call.
+ unsafe fn yield2(r0: *mut (), r1: *mut ());
+
+ // syscall1 is only used to invoke Memop operations. Because there are no
+ // Memop commands that set r2 or r3, raw_syscall1 only needs to return r0
+ // and r1.
//
// Memop commands may panic in the unit test environment, as not all memop
// calls can be sensibly implemented in that environment.
//
- // zero_arg_memop's inline assembly should have the following properties:
- // 1. Calls syscall class 5
- // 2. Specifies r0 as an inlateout register, and r1 as a lateout
- // register.
- // 3. Does not mark any registers as clobbered.
- // 4. Has all of the following options:
+ // syscall1 should:
+ // 1. Call the syscall class specified by CLASS.
+ // 2. Pass r0 as an inlateout register.
+ // 3. Specify r1 as a lateout register and return its value.
+ // 4. Not mark any registers as clobbered.
+ // 5. Have all of the following options:
// preserves_flags
// nostack
// nomem (it is okay for the compiler to cache globals
// across memop calls)
- // 5. Does NOT have any of the following options:
+ // 6. NOT have any of the following options:
// pure (two invocations of the same memop can return
// different values)
// readonly (incompatible with nomem)
// noreturn
- //
- // Design note: like raw_yield, this is safe because memops that currently
- // 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);
+ /// `syscall1` should only be called by `libtock_platform`.
+ /// # Safety
+ /// This directly makes a system call. It can only be used for core kernel
+ /// system calls that accept 1 argument and only overwrite r0 and r1 on
+ /// return. It is unsafe any time the underlying system call is unsafe.
+ unsafe fn syscall1<const CLASS: usize>(r0: *mut ()) -> (*mut (), *mut ());
- // one_arg_memop is used to invoke memop operations that take an argument.
- // Because there are no memop operations that set r2 or r3, this only needs
- // to return r0 and r1.
+ // syscall2 is used to invoke Exit as well as Memop operations that take an
+ // argument. Memop does not currently use more than 2 registers for its
+ // return value, and Exit does not return, so syscall2 only returns 2
+ // values.
//
- // one_arg_memop's inline assembly should:
- // 1. Call syscall class 5
- // 2. Specify r0 and r1 as inlateout registers, and return (r0, r1)
+ // syscall2 should:
+ // 1. Call the syscall class specified by CLASS.
+ // 2. Pass r0 and r1 as inlateout registers.
// 3. Not mark any registers as clobbered.
// 4. Have all of the following options:
// preserves_flags
// nostack
// nomem (the compiler can cache globals across memop
// calls)
- // 5. Does NOT have any of the following options:
+ // 5. NOT have any of the following options:
// pure Two invocations of sbrk can return different values
// readonly Incompatible with nomem
// noreturn
+ /// `syscall2` should only be called by `libtock_platform`.
+ /// # Safety
+ /// `syscall2` directly makes a system call. It can only be used for core
+ /// kernel system calls that accept 2 arguments and only overwrite r0 and r1
+ /// on return. It is unsafe any time the underlying system call is unsafe.
+ unsafe fn syscall2<const CLASS: usize>(r0: *mut (), r1: *mut ()) -> (*mut (), *mut ());
+
+ // syscall4 should:
+ // 1. Call the syscall class specified by CLASS.
+ // 2. Pass r0-r3 in the corresponding registers as inlateout registers.
+ // 3. Not mark any registers as clobbered.
+ // 4. Have all of the following options:
+ // preserves_flags (these system calls do not touch flags)
+ // nostack (these system calls do not touch the stack)
+ // 5. NOT have any of the following options:
+ // pure (these system calls have side effects)
+ // nomem (the compiler needs to write to globals before allow)
+ // readonly (rw allow can modify memory)
+ // noreturn (all these system calls are expected to return)
//
- // Design note: like raw_yield, this is safe because memops that currently
- // 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);
-}
-
-#[non_exhaustive]
-#[repr(u32)]
-pub enum OneArgMemop {
- Brk = 0,
- Sbrk = 1,
- FlashRegionStart = 8,
- FlashRegionEnd = 9,
- SpecifyStackTop = 10,
- SpecifyHeapStart = 11,
- // Note: before adding new memop operations, make sure the assumptions in
- // the design notes on `one_arg_memop` are valid for the new operation type.
-}
-
-// TODO: When the numeric values (0 and 1) are assigned to the yield types,
-// specify those values here.
-#[non_exhaustive]
-#[repr(u32)]
-pub enum YieldType {
- Wait,
- NoWait,
-}
-
-#[non_exhaustive]
-#[repr(u32)]
-pub enum ZeroArgMemop {
- MemoryStart = 2,
- MemoryEnd = 3,
- FlashStart = 4,
- FlashEnd = 5,
- GrantStart = 6,
- FlashRegions = 7,
- // Note: before adding new memop operations, make sure the assumptions in
- // the design notes on `zero_arg_memop` are valid for the new operation
- // type.
+ // For subscribe(), the callback pointer should be either 0 (for the null
+ // callback) or an `unsafe extern fn(u32, u32, u32, Userdata)`.
+ /// `syscall4` should only be called by `libtock_platform`.
+ ///
+ /// # Safety
+ /// `syscall4` must NOT be used to invoke yield. Otherwise, it has the same
+ /// safety invariants as the underlying system call, which varies depending
+ /// on the system call class.
+ unsafe fn syscall4<const CLASS: usize>(
+ r0: *mut (),
+ r1: *mut (),
+ r2: *mut (),
+ r3: *mut (),
+ ) -> (*mut (), *mut (), *mut (), *mut ());
}
diff --git a/core/platform/src/syscalls.rs b/core/platform/src/syscalls.rs
index 8d85380..d5ff0bc 100644
--- a/core/platform/src/syscalls.rs
+++ b/core/platform/src/syscalls.rs
@@ -5,15 +5,15 @@
/// implemented for `libtock_runtime::TockSyscalls` and
/// `libtock_unittest::FakeSyscalls` (by way of `RawSyscalls`).
pub trait Syscalls {
+ /// Runs the next pending callback, if a callback is pending. Unlike
+ /// `yield_wait`, `yield_no_wait` returns immediately if no callback is
+ /// pending.
+ fn yield_no_wait() -> crate::YieldNoWaitReturn;
+
/// Puts the process to sleep until a callback becomes pending, invokes the
/// callback, then returns.
fn yield_wait();
- /// Runs the next pending callback, if a callback is pending. Unlike
- /// `yield_wait`, `yield_no_wait` returns immediately if no callback is
- /// pending. Returns true if a callback was executed, false otherwise.
- fn yield_no_wait() -> bool;
-
// TODO: Add a subscribe interface.
// TODO: Add a command interface.
diff --git a/core/platform/src/syscalls_impl.rs b/core/platform/src/syscalls_impl.rs
index 506ad64..c86da40 100644
--- a/core/platform/src/syscalls_impl.rs
+++ b/core/platform/src/syscalls_impl.rs
@@ -1,17 +1,40 @@
//! Implements `Syscalls` for all types that implement `RawSyscalls`.
-use crate::{return_variant, RawSyscalls, Syscalls, YieldType};
+use crate::{RawSyscalls, Syscalls, YieldNoWaitReturn};
+
+mod yield_op {
+ pub const NO_WAIT: u32 = 0;
+ pub const WAIT: u32 = 1;
+}
impl<S: RawSyscalls> Syscalls for S {
// -------------------------------------------------------------------------
// Yield
// -------------------------------------------------------------------------
- fn yield_wait() {
- Self::raw_yield(YieldType::Wait);
+ fn yield_no_wait() -> YieldNoWaitReturn {
+ unsafe {
+ // flag can be uninitialized because it is not read before the yield
+ // system call, and the kernel promises to only write to it (not
+ // read it).
+ let mut flag = core::mem::MaybeUninit::<YieldNoWaitReturn>::uninit();
+
+ // flag is safe to write a YieldNoWaitReturn to, as guaranteed by
+ // MaybeUninit.
+ Self::yield2(yield_op::NO_WAIT as *mut (), flag.as_mut_ptr() as *mut ());
+
+ // yield-no-wait guarantees it sets (initializes) flag before
+ // returning.
+ flag.assume_init()
+ }
}
- fn yield_no_wait() -> bool {
- Self::raw_yield(YieldType::NoWait) != return_variant::FAILURE.into()
+ fn yield_wait() {
+ // Safety: yield-wait does not return a value, which satisfies yield1's
+ // requirement. The yield-wait system call cannot trigger undefined
+ // behavior on its own in any other way.
+ unsafe {
+ Self::yield1(yield_op::WAIT as *mut ());
+ }
}
}
diff --git a/core/platform/src/yield_types.rs b/core/platform/src/yield_types.rs
new file mode 100644
index 0000000..12dd961
--- /dev/null
+++ b/core/platform/src/yield_types.rs
@@ -0,0 +1,15 @@
+/// The return value from a yield_no_wait call.
+// Calling yield-no-wait passes a *mut YieldNoWaitReturn to the kernel, which
+// the kernel writes to. We cannot safely pass a `*mut bool` to the kernel,
+// because the representation of `bool` in Rust is undefined (although it is
+// likely false == 0, true == 1, based on `bool`'s conversions). Using *mut
+// YieldNoWaitReturn rather than a *mut u8 allows the compiler to assume the
+// kernel will never write a value other than 0 or 1 into the pointee. Assuming
+// the likely representation of `bool`, this makes the conversion into `bool`
+// free.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(u8)]
+pub enum YieldNoWaitReturn {
+ NoCallback = 0,
+ Callback = 1,
+}
diff --git a/core/runtime/src/syscalls_impl_riscv.rs b/core/runtime/src/syscalls_impl_riscv.rs
index 0e5f58a..2ae90c2 100644
--- a/core/runtime/src/syscalls_impl_riscv.rs
+++ b/core/runtime/src/syscalls_impl_riscv.rs
@@ -1,11 +1,12 @@
-use libtock_platform::{OneArgMemop, RawSyscalls, YieldType, ZeroArgMemop};
+use libtock_platform::RawSyscalls;
-impl RawSyscalls for crate::TockSyscalls {
- // This yield implementation is currently limited RISC-V versions without
+unsafe impl RawSyscalls for crate::TockSyscalls {
+ // This yield implementation is currently limited to 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 fn yield1(r0: *mut ()) {
+ // Safety: This matches the invariants required by the documentation on
+ // RawSyscalls::yield1
unsafe {
asm!("ecall",
// x0 is the zero register.
@@ -16,14 +17,14 @@
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
+ 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
@@ -31,53 +32,88 @@
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) {
+ // This yield implementation is currently limited to RISC-V versions without
+ // floating-point registers, as it does not mark them clobbered.
+ #[cfg(not(any(target_feature = "d", target_feature = "f")))]
+ unsafe fn yield2(r0: *mut (), r1: *mut ()) {
+ // Safety: This matches the invariants required by the documentation on
+ // RawSyscalls::yield2
unsafe {
asm!("ecall",
- inlateout("a0") r0,
- inlateout("a1") r1,
- inlateout("a2") r2,
- inlateout("a3") r3,
- in("a4") class,
- options(preserves_flags, nostack),
+ // 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
+ inlateout("x11") r1 => _, // 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, r1 as usize, r2, r3)
}
- fn zero_arg_memop(r0_in: ZeroArgMemop) -> (u32, usize) {
- let mut r0 = r0_in as u32;
+ unsafe fn syscall1<const CLASS: usize>(mut r0: *mut ()) -> (*mut (), *mut ()) {
let r1;
+ // Safety: This matches the invariants required by the documentation on
+ // RawSyscalls::syscall1
unsafe {
asm!("ecall",
inlateout("a0") r0,
lateout("a1") r1,
- in("a4") 5,
+ in("a4") CLASS,
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 fn syscall2<const CLASS: usize>(mut r0: *mut (), mut r1: *mut ()) -> (*mut (), *mut ()) {
+ // Safety: This matches the invariants required by the documentation on
+ // RawSyscalls::syscall2
unsafe {
asm!("ecall",
inlateout("a0") r0,
inlateout("a1") r1,
- in("a4") 5,
+ in("a4") CLASS,
options(preserves_flags, nostack, nomem)
);
}
(r0, r1)
}
+
+ unsafe fn syscall4<const CLASS: usize>(
+ mut r0: *mut (),
+ mut r1: *mut (),
+ mut r2: *mut (),
+ mut r3: *mut (),
+ ) -> (*mut (), *mut (), *mut (), *mut ()) {
+ // Safety: This matches the invariants required by the documentation on
+ // RawSyscalls::syscall4
+ unsafe {
+ asm!("ecall",
+ inlateout("a0") r0,
+ inlateout("a1") r1,
+ inlateout("a2") r2,
+ inlateout("a3") r3,
+ in("a4") CLASS,
+ options(preserves_flags, nostack),
+ );
+ }
+ (r0, r1, r2, r3)
+ }
}