Skip to content

Commit

Permalink
kernel/syscall: split out encode_syscall_return, create TRD104 subset
Browse files Browse the repository at this point in the history
Because all architecture crates depend on the kernel crate, we use it
not just for architecture-agnostic core kernel infrastructure, but
also architecture-specific code that happens to be shared between two
or more `arch` crates. Prior to this change, we conflated these
helpers with the core kernel infrastructure in `syscall.rs`.

With this change, we split out the 32-bit and TRD 104-specific
`encode_syscall_return` helper, shared between the 32-bit RISC-V and
Cortex-M architecture implementations, into its own dedicated module
under utilities.

We create a separate `SyscallReturn` subset for those return values as
specified in TRD 104, and create a function that translates between
these types. This allows architectures which use these functions to
guarantee that they are conformant to TRD 104 by first explicitly
converting into this type, and then encoding it into their stored
registers using the TRD 104 encoding helper.
  • Loading branch information
lschuermann committed Nov 12, 2024
1 parent f3feec6 commit a001b33
Show file tree
Hide file tree
Showing 5 changed files with 454 additions and 269 deletions.
10 changes: 9 additions & 1 deletion arch/cortex-m/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,15 @@ impl<A: CortexMVariant> kernel::syscall::UserspaceKernelBoundary for SysCall<A>
//
// Refer to
// https://doc.rust-lang.org/std/primitive.pointer.html#safety-13
return_value.encode_syscall_return_32bit_trd104(&mut *r0, &mut *r1, &mut *r2, &mut *r3);
kernel::utilities::arch_helpers::encode_syscall_return_32bit_trd104(
&kernel::utilities::arch_helpers::TRD104SyscallReturn::from_syscall_return(
return_value,
),
&mut *r0,
&mut *r1,
&mut *r2,
&mut *r3,
);

Ok(())
}
Expand Down
5 changes: 4 additions & 1 deletion arch/rv32i/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,10 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall {
let (a1slice, r) = r.split_at_mut(R_A2 - R_A1);
let (a2slice, a3slice) = r.split_at_mut(R_A3 - R_A2);

return_value.encode_syscall_return_32bit_trd104(
kernel::utilities::arch_helpers::encode_syscall_return_32bit_trd104(
&kernel::utilities::arch_helpers::TRD104SyscallReturn::from_syscall_return(
return_value,
),
&mut a0slice[0],
&mut a1slice[0],
&mut a2slice[0],
Expand Down
270 changes: 3 additions & 267 deletions kernel/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,10 @@ use core::fmt::Write;

use crate::errorcode::ErrorCode;
use crate::process;
use crate::utilities::capability_ptr::{CapabilityPtr, CapabilityPtrPermissions};
use crate::utilities::capability_ptr::CapabilityPtr;

pub use crate::syscall_driver::{CommandReturn, SyscallDriver};

/// Helper function to split a [`u64`] into a higher and lower [`u32`].
///
/// Used in encoding 64-bit wide system call return values on 32-bit platforms.
#[inline]
fn u64_to_be_u32s(src: u64) -> (u32, u32) {
let src_bytes = src.to_be_bytes();
let src_msb = u32::from_be_bytes([src_bytes[0], src_bytes[1], src_bytes[2], src_bytes[3]]);
let src_lsb = u32::from_be_bytes([src_bytes[4], src_bytes[5], src_bytes[6], src_bytes[7]]);

(src_msb, src_lsb)
}

// ---------- SYSTEMCALL ARGUMENT DECODING ----------

/// Enumeration of the system call classes based on the identifiers specified in
Expand Down Expand Up @@ -381,58 +369,9 @@ impl Syscall {
}
}

// ---------- SYSCALL RETURN VALUE ENCODING ----------
// ---------- SYSCALL RETURN VALUES ----------

/// Enumeration of the system call return type variant identifiers described
/// in TRD104.
///
/// Each variant is associated with the respective variant identifier that would
/// be passed along with the return value to userspace.
#[repr(u32)]
#[derive(Copy, Clone, Debug)]
pub enum SyscallReturnVariant {
Failure = 0,
FailureU32 = 1,
FailureU32U32 = 2,
FailureU64 = 3,
FailurePtrUsize = 4,
FailurePtrPtr = 5,
Success = 128,
SuccessU32 = 129,
SuccessU32U32 = 130,
SuccessU64 = 131,
SuccessU32U32U32 = 132,
SuccessU32U64 = 133,
SuccessUsize = 134,
SuccessPtr = 135,
SuccessPtrUsize = 136,
SuccessPtrPtr = 137,
}

impl SyscallReturnVariant {
/// Maps newly introduced return variants (usize and ptr)
/// to old ones (u32) for backwards compatibility.
/// This should not be used for any newly designed interfaces,
/// and will eventually be deprecated once all interfaces are updated.
pub const fn into_compat_32bit_trd104(self) -> Self {
// We only need to be backwards compatible on 32-bit systems
let compat = core::mem::size_of::<usize>() == core::mem::size_of::<u32>();

if !compat {
return self;
}

match self {
// Map all usizes and ptrs to u32
Self::SuccessUsize | Self::SuccessPtr => Self::SuccessU32,
Self::SuccessPtrUsize | Self::SuccessPtrPtr => Self::SuccessU32U32,
Self::FailurePtrUsize | Self::FailurePtrPtr => Self::FailureU32U32,
x => x,
}
}
}

/// Enumeration of the possible system call return variants specified in TRD104.
/// Enumeration of the possible system call return variants.
///
/// This struct operates over primitive types such as integers of fixed length
/// and pointers. It is constructed by the scheduler and passed down to the
Expand Down Expand Up @@ -567,209 +506,6 @@ impl SyscallReturn {
SyscallReturn::SuccessUsize(_) => true,
}
}

/// Encode the system call return value into 4 registers, following the
/// encoding specified in TRD104. Architectures which do not follow TRD104
/// are free to define their own encoding.
pub fn encode_syscall_return_32bit_trd104(
&self,
a0: &mut u32,
a1: &mut u32,
a2: &mut u32,
a3: &mut u32,
) {
assert!(
core::mem::size_of::<CapabilityPtr>() == core::mem::size_of::<u32>()
&& core::mem::align_of::<u32>() >= align_of::<CapabilityPtr>(),
"encode_syscall_return used on a 64-bit platform or CHERI platform"
);

// SAFETY: if the two integers are the same size (and alignment permits) references
// to them can be safely transmuted.
// Ugly coercion could be avoided by first copying to the stack, then assigning with
// "as" in order to satisfy the compiler.
unsafe {
let a0 = &mut *(core::ptr::from_mut(a0).cast::<CapabilityPtr>());
let a1 = &mut *(core::ptr::from_mut(a1).cast::<CapabilityPtr>());
let a2 = &mut *(core::ptr::from_mut(a2).cast::<CapabilityPtr>());
let a3 = &mut *(core::ptr::from_mut(a3).cast::<CapabilityPtr>());
self.encode_syscall_return_usize_trd104_compat(a0, a1, a2, a3);
}
}

/// An extension of TRD104 that works for 32-bit and 64-bit platforms.
/// This implements TRD104 exactly on 32-bit platforms by mapping new codes intended for
/// 64-bit platforms to existing ones.
/// On 64-bit platforms, both 64-bit and usize values are passed as a single register,
/// Does not handle usize other than 4 and 8 bytes.
/// Pointers from allow'd buffers have permissions and length reattached matching
/// those that were checked at the syscall boundary.
fn encode_syscall_return_usize_trd104_compat(
&self,
a0: &mut CapabilityPtr,
a1: &mut CapabilityPtr,
a2: &mut CapabilityPtr,
a3: &mut CapabilityPtr,
) {
// On 32-bit CHERI, given that capabilities cannot be used as 64-bit integers, 64-bit
// integers will still be returned as two 32-bit values in different registers.
fn write_64(a: &mut CapabilityPtr, b: &mut CapabilityPtr, val: u64) {
let is_64_bit = core::mem::size_of::<usize>() == 8;
if !is_64_bit {
let (msb, lsb) = u64_to_be_u32s(val);
*a = (lsb as usize).into();
*b = (msb as usize).into();
} else {
*a = (val as usize).into();
}
}

match *self {
SyscallReturn::Failure(e) => {
*a0 = (SyscallReturnVariant::Failure as usize).into();
*a1 = (usize::from(e)).into();
}
SyscallReturn::FailureU32(e, data0) => {
*a0 = (SyscallReturnVariant::FailureU32 as usize).into();
*a1 = usize::from(e).into();
*a2 = (data0 as usize).into();
}
SyscallReturn::FailureU32U32(e, data0, data1) => {
*a0 = (SyscallReturnVariant::FailureU32U32 as usize).into();
*a1 = (usize::from(e)).into();
*a2 = (data0 as usize).into();
*a3 = (data1 as usize).into();
}
SyscallReturn::FailureU64(e, data0) => {
*a0 = (SyscallReturnVariant::FailureU64 as usize).into();
*a1 = (usize::from(e)).into();
write_64(a2, a3, data0)
}
SyscallReturn::Success => {
*a0 = (SyscallReturnVariant::Success as usize).into();
}
SyscallReturn::SuccessU32(data0) => {
*a0 = (SyscallReturnVariant::SuccessU32 as usize).into();
*a1 = (data0 as usize).into();
}
SyscallReturn::SuccessU32U32(data0, data1) => {
*a0 = (SyscallReturnVariant::SuccessU32U32 as usize).into();
*a1 = (data0 as usize).into();
*a2 = (data1 as usize).into();
}
SyscallReturn::SuccessU32U32U32(data0, data1, data2) => {
*a0 = (SyscallReturnVariant::SuccessU32U32U32 as usize).into();
*a1 = (data0 as usize).into();
*a2 = (data1 as usize).into();
*a3 = (data2 as usize).into();
}
SyscallReturn::SuccessU64(data0) => {
*a0 = (SyscallReturnVariant::SuccessU64 as usize).into();
write_64(a1, a2, data0);
}
SyscallReturn::SuccessU32U64(data0, data1) => {
*a0 = (SyscallReturnVariant::SuccessU32U64 as usize).into();
*a1 = (data0 as usize).into();
write_64(a2, a3, data1);
}
SyscallReturn::AllowReadWriteSuccess(ptr, len) => {
*a0 = (SyscallReturnVariant::SuccessPtrUsize.into_compat_32bit_trd104() as usize)
.into();
*a1 = CapabilityPtr::new_with_metadata(
ptr as *const (),
ptr as usize,
len,
CapabilityPtrPermissions::ReadWrite,
);
*a2 = len.into();
}
SyscallReturn::UserspaceReadableAllowSuccess(ptr, len) => {
*a0 = (SyscallReturnVariant::SuccessPtrUsize.into_compat_32bit_trd104() as usize)
.into();
*a1 = CapabilityPtr::new_with_metadata(
ptr as *const (),
ptr as usize,
len,
CapabilityPtrPermissions::Read,
);
*a2 = len.into();
}
SyscallReturn::AllowReadWriteFailure(err, ptr, len) => {
*a0 = (SyscallReturnVariant::FailurePtrUsize.into_compat_32bit_trd104() as usize)
.into();
*a1 = (usize::from(err)).into();
*a2 = CapabilityPtr::new_with_metadata(
ptr as *const (),
ptr as usize,
len,
CapabilityPtrPermissions::ReadWrite,
);
*a3 = len.into();
}
SyscallReturn::UserspaceReadableAllowFailure(err, ptr, len) => {
*a0 = (SyscallReturnVariant::FailurePtrUsize.into_compat_32bit_trd104() as usize)
.into();
*a1 = (usize::from(err)).into();
*a2 = CapabilityPtr::new_with_metadata(
ptr as *const (),
ptr as usize,
len,
CapabilityPtrPermissions::Read,
);
*a3 = len.into();
}
SyscallReturn::AllowReadOnlySuccess(ptr, len) => {
*a0 = (SyscallReturnVariant::SuccessPtrUsize.into_compat_32bit_trd104() as usize)
.into();
*a1 = CapabilityPtr::new_with_metadata(
ptr as *const (),
ptr as usize,
len,
CapabilityPtrPermissions::Read,
);
*a2 = len.into();
}
SyscallReturn::AllowReadOnlyFailure(err, ptr, len) => {
*a0 = (SyscallReturnVariant::FailurePtrUsize.into_compat_32bit_trd104() as usize)
.into();
*a1 = (usize::from(err)).into();
*a2 = CapabilityPtr::new_with_metadata(
ptr as *const (),
ptr as usize,
len,
CapabilityPtrPermissions::Read,
);
*a3 = len.into();
}
SyscallReturn::SubscribeSuccess(ptr, data) => {
*a0 = (SyscallReturnVariant::SuccessPtrPtr.into_compat_32bit_trd104() as usize)
.into();
*a1 = (ptr as usize).into();
*a2 = data.into();
}
SyscallReturn::SubscribeFailure(err, ptr, data) => {
*a0 = (SyscallReturnVariant::FailurePtrPtr.into_compat_32bit_trd104() as usize)
.into();
*a1 = (usize::from(err)).into();
*a2 = (ptr as usize).into();
*a3 = data.into();
}
SyscallReturn::SuccessPtr(ptr) => {
*a0 = (SyscallReturnVariant::SuccessPtr.into_compat_32bit_trd104() as usize).into();
*a1 = ptr;
}
SyscallReturn::YieldWaitFor(data0, data1, data2) => {
*a0 = data0.into();
*a1 = data1.into();
*a2 = data2.into();
}
SyscallReturn::SuccessUsize(data) => {
*a0 =
(SyscallReturnVariant::SuccessUsize.into_compat_32bit_trd104() as usize).into();
*a1 = data.into();
}
}
}
}

// ---------- USERSPACE KERNEL BOUNDARY ----------
Expand Down
Loading

0 comments on commit a001b33

Please sign in to comment.