Skip to content

Commit

Permalink
Merge pull request #554 from tock/hudson.ayers/alloc
Browse files Browse the repository at this point in the history
Initial memop syscall implementation + tests
  • Loading branch information
alevy committed Aug 5, 2024
2 parents f4bd533 + 0b6e7c2 commit 1dc6133
Show file tree
Hide file tree
Showing 15 changed files with 596 additions and 23 deletions.
15 changes: 14 additions & 1 deletion platform/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use core::mem::transmute;
/// wraps, but instead use the conversion functions in this module.
// Register is repr(transparent) so that an upcall's application data can be
// soundly passed as a Register.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Register(pub *mut ());

Expand All @@ -28,6 +28,12 @@ impl From<u32> for Register {
}
}

impl From<i32> for Register {
fn from(value: i32) -> Register {
(value as usize).into()
}
}

impl From<usize> for Register {
fn from(value: usize) -> Register {
// Note: clippy is wrong here; transmute has different semantics than
Expand Down Expand Up @@ -72,6 +78,13 @@ impl Register {
pub fn as_u32(self) -> u32 {
self.0 as u32
}

/// Casts this register to a i32, truncating it if it is larger than
/// 32 bits. This conversion should be avoided in host-based test code; use
/// the `TryFrom<Register> for i32` implementation instead.
pub fn as_i32(self) -> i32 {
self.0 as i32
}
}

impl From<Register> for usize {
Expand Down
37 changes: 36 additions & 1 deletion platform/src/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,42 @@ pub trait Syscalls: RawSyscalls + Sized {
/// `unallow_ro` does nothing.
fn unallow_ro(driver_num: u32, buffer_num: u32);

// TODO: Add memop() methods.
// -------------------------------------------------------------------------
// Memop
// -------------------------------------------------------------------------

/// Changes the location of the program break to the specified address and
/// returns an error if it fails to do so.
///
/// # Safety
/// Callers of this function must ensure that they do not pass an
/// address below any address that includes a currently reachable object.
unsafe fn memop_brk(addr: *const u8) -> Result<(), ErrorCode>;

/// Changes the location of the program break by the passed increment,
/// and returns the previous break address.
///
/// # Safety
/// Callers of this function must ensure that they do not pass an
/// increment that would deallocate memory containing any currently
/// reachable object.
unsafe fn memop_sbrk(incr: i32) -> Result<*const u8, ErrorCode>;

/// Increments the program break by the passed increment,
/// and returns the previous break address.
fn memop_increment_brk(incr: u32) -> Result<*const u8, ErrorCode>;

/// Gets the address of the start of this application's RAM allocation.
fn memop_app_ram_start() -> Result<*const u8, ErrorCode>;

/// Tells the kernel where the start of the app stack is, to support
/// debugging.
fn memop_debug_stack_start(stack_top: *const u8) -> Result<(), ErrorCode>;

/// Tells the kernel the initial program break, to support debugging.
fn memop_debug_heap_start(initial_break: *const u8) -> Result<(), ErrorCode>;

// TODO: Add remaining memop() methods (3-9).

// -------------------------------------------------------------------------
// Exit
Expand Down
147 changes: 147 additions & 0 deletions platform/src/syscalls_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,153 @@ impl<S: RawSyscalls> Syscalls for S {
}
}

// -------------------------------------------------------------------------
// Memop
// -------------------------------------------------------------------------

// Safety: Callers of this function must ensure that they do not pass an
// address below any address that includes a currently reachable object.
unsafe fn memop_brk(addr: *const u8) -> Result<(), ErrorCode> {
// Safety: syscall2's documentation indicates it can be used to call Memop.
let [r0, r1] =
unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([0u32.into(), addr.into()]) };
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that memop 0, 10, and 11 return either Success
// or Failure. We check the return variant by comparing against Failure
// for 2 reasons:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE has value 0, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS uses an uncompressed instruction.
// 2. In the event the kernel malfunctions and returns a different
// return variant, the success path is actually safer than the
// failure path. The failure path assumes that r1 contains an
// ErrorCode, and produces UB if it has an out of range value.
if return_variant == return_variant::FAILURE {
// Safety: TRD 104 guarantees that if r0 is Failure,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
Err(unsafe { core::mem::transmute(r1.as_u32()) })
} else {
Ok(())
}
}

// Safety: Callers of this function must ensure that they do not pass an
// increment that would deallocate memory containing any currently
// reachable object.
unsafe fn memop_sbrk(incr: i32) -> Result<*const u8, ErrorCode> {
// Safety: syscall2's documentation indicates it can be used to call Memop.
let [r0, r1] =
unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([1u32.into(), incr.into()]) };
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that memop 1, returns either Success with U32
// or Failure. We check the return variant by comparing against Failure
// for 1 reason:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE has value 0, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS_U32 uses an uncompressed instruction.
if return_variant == return_variant::FAILURE {
// Safety: TRD 104 guarantees that if r0 is Failure,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
Err(unsafe { core::mem::transmute(r1.as_u32()) })
} else {
Ok(r1.into())
}
}

fn memop_increment_brk(incr: u32) -> Result<*const u8, ErrorCode> {
// Safety: memop_sbrk is safe if the passed increment is positive
unsafe { Self::memop_sbrk(i32::try_from(incr).map_err(|_| ErrorCode::Invalid)?) }
}

fn memop_app_ram_start() -> Result<*const u8, ErrorCode> {
// Safety: syscall1's documentation indicates it can be used to call Memop operations
// that only accept a memop operation number.
let [r0, r1] = unsafe { Self::syscall1::<{ syscall_class::MEMOP }>([2u32.into()]) };
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that memop 2 returns either Success
// or Failure. We check the return variant by comparing against Failure
// for 1 reason:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE has value 0, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS_U32 uses an uncompressed instruction.
if return_variant == return_variant::FAILURE {
// Safety: TRD 104 guarantees that if r0 is Failure,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
Err(unsafe { core::mem::transmute(r1.as_u32()) })
} else {
Ok(r1.into())
}
}

fn memop_debug_stack_start(stack_top: *const u8) -> Result<(), ErrorCode> {
// Safety: syscall2's documentation indicates it can be used to call Memop.
let [r0, r1] =
unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([10u32.into(), stack_top.into()]) };
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that memop 0, 10, and 11 return either Success
// or Failure. We check the return variant by comparing against Failure
// for 2 reasons:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE has value 0, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS uses an uncompressed instruction.
// 2. In the event the kernel malfunctions and returns a different
// return variant, the success path is actually safer than the
// failure path. The failure path assumes that r1 contains an
// ErrorCode, and produces UB if it has an out of range value.
if return_variant == return_variant::FAILURE {
// Safety: TRD 104 guarantees that if r0 is Failure,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
Err(unsafe { core::mem::transmute(r1.as_u32()) })
} else {
Ok(())
}
}

fn memop_debug_heap_start(initial_break: *const u8) -> Result<(), ErrorCode> {
// Safety: syscall2's documentation indicates it can be used to call Memop.
let [r0, r1] = unsafe {
Self::syscall2::<{ syscall_class::MEMOP }>([11u32.into(), initial_break.into()])
};
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that memop 0, 10, and 11 return either Success
// or Failure. We check the return variant by comparing against Failure
// for 2 reasons:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE has value 0, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS uses an uncompressed instruction.
// 2. In the event the kernel malfunctions and returns a different
// return variant, the success path is actually safer than the
// failure path. The failure path assumes that r1 contains an
// ErrorCode, and produces UB if it has an out of range value.
if return_variant == return_variant::FAILURE {
// Safety: TRD 104 guarantees that if r0 is Failure,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
Err(unsafe { core::mem::transmute(r1.as_u32()) })
} else {
Ok(())
}
}

// -------------------------------------------------------------------------
// Exit
// -------------------------------------------------------------------------
Expand Down
30 changes: 16 additions & 14 deletions runtime/src/startup/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Runtime components related to process startup.

use crate::TockSyscalls;
use libtock_platform::{syscall_class, RawSyscalls, Termination};
use libtock_platform::{Syscalls, Termination};

// Include the correct `start` symbol (the program entry point) for the
// architecture.
Expand Down Expand Up @@ -85,21 +85,12 @@ extern "C" fn rust_start() -> ! {
static rt_header: RtHeader;
}

// TODO: Implement a safe memop API in libtock_platform and migrate these
// calls to that API.
//
// Safety: Memop operations 10 and 11 are always memory-safe, as they do not
// impact the execution of this process.
#[cfg(not(feature = "no_debug_memop"))]
// Safety: rt_header is defined in the linker script, valid for its type,
// and not modified anywhere
unsafe {
TockSyscalls::syscall2::<{ syscall_class::MEMOP }>([
10u32.into(),
rt_header.stack_top.into(),
]);
TockSyscalls::syscall2::<{ syscall_class::MEMOP }>([
11u32.into(),
rt_header.initial_break.into(),
]);
let _ = TockSyscalls::memop_debug_stack_start(rt_header.stack_top as *const u8);
let _ = TockSyscalls::memop_debug_heap_start(rt_header.initial_break as *const u8);
}

// Safety: libtock_unsafe_main is defined by the set_main! macro, and its
Expand All @@ -108,3 +99,14 @@ extern "C" fn rust_start() -> ! {
libtock_unsafe_main();
}
}

/// Function which an allocator can call to learn the initial
/// start of the heap region
pub fn get_heap_start() -> *mut () {
extern "Rust" {
static rt_header: RtHeader;
}
// Safety: rt_header is defined in the linker script, valid for its type,
// and not modified anywhere
unsafe { rt_header.initial_break }
}
3 changes: 2 additions & 1 deletion syscalls_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ mod exit_on_drop;

// TODO: Add Exit.

// TODO: Add Memop.
#[cfg(test)]
mod memop_tests;

#[cfg(test)]
mod subscribe_tests;
Expand Down
Loading

0 comments on commit 1dc6133

Please sign in to comment.