Rework the use of `usize` versus `u32` in RawSyscalls. I previously used `usize` for every value, because `usize` is register-sized on all platforms. However, I no longer think that is the correct thing to do. Having the values be register-sized only matters for `libtock_runtime::TockSyscalls`; it does not matter for `libtock_unittest::FakeSyscalls`. For the fake system calls, it is better to pass u32 where possible to minimize the number of invalid value representable in the API. This rework follows a simple rule: values that can potentially contain pointers have type `usize`; all other register values have type `u32`. This extends to the function signature of `subscribe` callbacks: unsafe extern fn(u32, u32, u32, usize).
diff --git a/core/platform/src/raw_syscalls.rs b/core/platform/src/raw_syscalls.rs index 5a87b2d..1743e3f 100644 --- a/core/platform/src/raw_syscalls.rs +++ b/core/platform/src/raw_syscalls.rs
@@ -57,10 +57,13 @@ // 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. // -// Because the variables passed in and out of raw system calls represent -// register values, they are of type usize. In cases where it doesn't make sense -// to pass a pointer-sized value, libtock_unittest::FakeSyscalls may panic if a -// too-large value is passed. +// 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: // 1. Call syscall class 0 @@ -82,7 +85,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). - fn raw_yield(r0_in: YieldType) -> usize; + 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. @@ -101,17 +104,23 @@ // 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)`. /// # 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: usize, + r0: u32, r1: usize, r2: usize, r3: usize, class: u8, - ) -> (usize, usize, usize, usize); + ) -> (u32, usize, usize, usize); // 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, @@ -137,10 +146,10 @@ // 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 usize - // so that if the kernel adds an unsafe memop -- or one that can clobber - // r2/r3 -- this API doesn't become unsound. - fn zero_arg_memop(r0_in: ZeroArgMemop) -> (usize, usize); + // 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. + fn zero_arg_memop(r0_in: ZeroArgMemop) -> (u32, usize); // 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 @@ -161,14 +170,14 @@ // 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 usize - // so that if the kernel adds an unsafe memop -- or one that can clobber - // r2/r3 -- this API doesn't become unsound. - fn one_arg_memop(r0_in: OneArgMemop, r1: usize) -> (usize, usize); + // 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. + fn one_arg_memop(r0_in: OneArgMemop, r1: usize) -> (u32, usize); } #[non_exhaustive] -#[repr(usize)] +#[repr(u32)] pub enum OneArgMemop { Brk = 0, Sbrk = 1, @@ -183,14 +192,14 @@ // TODO: When the numeric values (0 and 1) are assigned to the yield types, // specify those values here. #[non_exhaustive] -#[repr(usize)] +#[repr(u32)] pub enum YieldType { Wait, NoWait, } #[non_exhaustive] -#[repr(usize)] +#[repr(u32)] pub enum ZeroArgMemop { MemoryStart = 2, MemoryEnd = 3,