godspeed_sdk/memory.rs
1// SPDX-License-Identifier: Apache-2.0
2//! A bounded block of this task's own memory, as a byte slice.
3//!
4//! `AllocMem` (syscall 6) maps fresh pages into the calling task and returns their address, and an
5//! address is all it returns: turning it into something a service can read and write needs `unsafe`,
6//! which no service may hold (CLAUDE.md 18.2). This module is that one step, audited once here, so that a
7//! service which needs a working set too large for its stack - the supervisor holding an image it read from
8//! disk before starting it - can have one without a heap and without `unsafe` of its own.
9//!
10//! **It is not an allocator.** There is no free: the kernel reclaims a task's memory only when the task
11//! dies (CLAUDE.md 10.5), so a region is asked for once and reused, and its size is the bound, stated where
12//! it is asked for (26.6.1). A caller that asks for one per operation leaks one per operation, until its
13//! contract's memory limit refuses the next (`AllocError::Denied`), which is loud rather than silent.
14
15use crate::service_context::{AllocError, ServiceContext};
16
17/// The page size `AllocMem` maps in.
18pub const PAGE: usize = 4096;
19
20/// A run of pages `AllocMem` mapped for this task, readable and writable, owned by this value.
21///
22/// Neither `Clone` nor `Copy`: there is one owner, and a mutable slice can be borrowed from it only through
23/// `&mut self`, so no two live slices of it can alias.
24pub struct Region {
25 va: u64,
26 len: usize,
27}
28
29impl Region {
30 /// Ask the kernel for at least `bytes` of fresh memory, rounded up to whole pages.
31 ///
32 /// `Denied` when it would pass this task's memory limit, `Failed` when the machine has no frames left
33 /// or `bytes` is zero or too large to round.
34 pub fn alloc(ctx: &ServiceContext, bytes: usize) -> Result<Region, AllocError> {
35 if bytes == 0 { return Err(AllocError::Failed); }
36 let len = bytes.checked_add(PAGE - 1).ok_or(AllocError::Failed)? & !(PAGE - 1);
37 let va = ctx.alloc_mem(len)?;
38 Ok(Region { va, len })
39 }
40
41 /// Its length in bytes: a whole number of pages, at least what was asked for.
42 pub fn len(&self) -> usize { self.len }
43
44 /// Always false: a region of no bytes cannot be made.
45 pub fn is_empty(&self) -> bool { self.len == 0 }
46
47 /// The region, to read.
48 pub fn bytes(&self) -> &[u8] {
49 // SAFETY: `va .. va + len` is exactly the mapping a successful `AllocMem` made for this task (the
50 // constructor is the only way to build a Region): mapped, readable, page-aligned, and initialised,
51 // because the kernel hands out zeroed frames. The kernel unmaps a task's memory only when the task
52 // dies, so the mapping outlives `self`. The borrow is shared, and a mutable one can only be taken
53 // through `&mut self`, so nothing writes while this slice lives.
54 unsafe { core::slice::from_raw_parts(self.va as *const u8, self.len) }
55 }
56
57 /// The region, to write.
58 pub fn bytes_mut(&mut self) -> &mut [u8] {
59 // SAFETY: as `bytes`, and the mapping is writable. `&mut self` on a value that is neither Clone nor
60 // Copy means this is the only live slice of the region.
61 unsafe { core::slice::from_raw_parts_mut(self.va as *mut u8, self.len) }
62 }
63
64 /// The region as 32-bit words, to write: for a device FIFO that is read a word at a time (a WiFi chip's
65 /// SDIO read buffer, larger than any stack can hold). The same memory as [`bytes`](Region::bytes), in
66 /// the machine's own byte order.
67 pub fn words_mut(&mut self) -> &mut [u32] {
68 // SAFETY: as `bytes_mut`. The base is page-aligned, so it is aligned for u32, and the length is a whole
69 // number of pages, so `len / 4` words lie exactly inside the mapping. Every bit pattern is a valid u32.
70 // `&mut self` makes this the only live view of the region.
71 unsafe { core::slice::from_raw_parts_mut(self.va as *mut u32, self.len / 4) }
72 }
73}