blob: 5bea5277be9786377465c8a10bafb265368face5 [file] [log] [blame]
// 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::fake::Kernel`. **Components should not use `RawSyscalls`
/// directly; instead, use the `Syscalls` trait, which provides higher-level
/// interfaces to the system calls.**
// 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.
//
// 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.
//
// Theoretically, RawSyscalls could consist of a single raw system call. To
// start, something like this should work:
//
// unsafe fn syscall<const CLASS: usize>([usize; 4]) -> [usize; 4];
//
// 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:
//
// unsafe fn syscall<const CLASS: usize>([*mut (); 4]) -> [*mut (); 4];
//
// 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:
//
// unsafe fn yield([*mut (); 4]) -> [*mut (); 4];
// unsafe fn syscall<const CLASS: usize>([*mut (); 4]) -> [*mut (); 4];
//
// 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:
//
// 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];
//
// However, not all of these are used! If we remove the system calls that are
// unused, we are left with the following:
//
// 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];
//
// 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. 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)
// 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)
/// `yield1` should only be called by `libtock_platform`.
/// # Safety
/// 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(_: [*mut (); 1]);
// 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(_: [*mut (); 2]);
// 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.
//
// 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)
// 6. NOT have any of the following options:
// pure (two invocations of the same memop can return
// different values)
// readonly (incompatible with nomem)
// noreturn
/// `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>(_: [*mut (); 1]) -> [*mut (); 2];
// 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.
//
// 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. 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>(_: [*mut (); 2]) -> [*mut (); 2];
// 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)
//
// 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>(_: [*mut (); 4]) -> [*mut (); 4];
}