Skip to main content

godspeed_sdk/
mmio.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Memory-mapped I/O access for userspace driver services (§12, §18).
3//!
4//! This is the SDK's audited hardware-access layer - one of the places outside
5//! the kernel where `unsafe` is permitted. §18.1 names this file alongside
6//! `dma.rs`, the syscall ABI (`raw_syscall`) and `adversarial.rs`; this said
7//! "one of the two places", which `dma.rs`'s own header already contradicted. Driver services use the safe [`Mmio`] wrapper and never
8//! write `unsafe` themselves; every volatile access below carries a SAFETY
9//! argument, and `Mmio` is only constructable inside this crate (from a
10//! kernel-granted mapping), so its base pointer is always valid by construction.
11
12/// A mapped MMIO region granted to a driver (e.g. via
13/// [`crate::ServiceContext::xhci_mmio`]). Read/write device registers by byte
14/// offset. All accesses are volatile - never reordered or elided - and target
15/// the uncached device registers directly, with no kernel mediation (§12).
16#[derive(Clone, Copy)]
17pub struct Mmio {
18    base: *mut u8,
19    len: usize,
20}
21
22impl Mmio {
23    /// Wrap a kernel-granted MMIO base virtual address + window length. Crate-internal: only the
24    /// SDK constructs an `Mmio`, and only from a VA the kernel mapped for this driver, which is what
25    /// makes the volatile accesses below sound.
26    pub(crate) fn new(base: *mut u8, len: usize) -> Self {
27        Self { base, len }
28    }
29
30    /// Length of the mapped register window in bytes.
31    #[inline]
32    pub fn len(&self) -> usize {
33        self.len
34    }
35
36    /// Bounds-check an `off..off+size` access against the mapped window (SEC-4). An out-of-window
37    /// MMIO access would otherwise fault (the window is exactly what the kernel mapped) - this turns
38    /// that into a loud, explicit panic naming the cause instead of a bare page fault (§26.7).
39    /// `checked_add` so a wrapping `off` cannot slip past.
40    ///
41    /// It also refuses an access that is not aligned to its own size. An unaligned volatile access is
42    /// undefined behaviour in Rust, and on Device memory an alignment fault on Arm; this "must be
43    /// aligned" was a comment until 2026-10-10, so a safe call could reach it (`backlog/fixed/80` S6). The
44    /// ADDRESS is checked, not only the offset, so a window that does not start on a boundary is
45    /// caught too.
46    #[inline]
47    fn check(&self, off: usize, size: usize) {
48        assert!(
49            off.checked_add(size).map_or(false, |end| end <= self.len),
50            "Mmio access out of window bounds",
51        );
52        assert!(
53            (self.base as usize).wrapping_add(off) % size == 0,
54            "Mmio access not aligned to its size",
55        );
56    }
57
58    /// Read an 8-bit register at `off` bytes from the base.
59    #[inline]
60    pub fn read8(&self, off: usize) -> u8 {
61        self.check(off, 1);
62        // SAFETY: `base` is a valid kernel-granted MMIO mapping (Mmio is only
63        // constructed from one); check() bounded `off` within the mapped window.
64        unsafe { core::ptr::read_volatile(self.base.add(off)) }
65    }
66
67    /// Read a 16-bit register at `off` (must be 2-byte aligned).
68    #[inline]
69    pub fn read16(&self, off: usize) -> u16 {
70        self.check(off, 2);
71        // SAFETY: as `read8`; check() bounded the access and proved it 2-byte aligned.
72        unsafe { core::ptr::read_volatile(self.base.add(off) as *const u16) }
73    }
74
75    /// Read a 32-bit register at `off` (must be 4-byte aligned).
76    #[inline]
77    pub fn read32(&self, off: usize) -> u32 {
78        self.check(off, 4);
79        // SAFETY: as `read8`; check() bounded the access and proved it 4-byte aligned.
80        unsafe { core::ptr::read_volatile(self.base.add(off) as *const u32) }
81    }
82
83    /// Write an 8-bit register at `off`.
84    #[inline]
85    pub fn write8(&self, off: usize, val: u8) {
86        self.check(off, 1);
87        // SAFETY: as `read8`; volatile device-register write.
88        unsafe { core::ptr::write_volatile(self.base.add(off), val) }
89    }
90
91    /// Write a 16-bit register at `off` (must be 2-byte aligned).
92    #[inline]
93    pub fn write16(&self, off: usize, val: u16) {
94        self.check(off, 2);
95        // SAFETY: as `read8`; check() bounded the write and proved it 2-byte aligned.
96        unsafe { core::ptr::write_volatile(self.base.add(off) as *mut u16, val) }
97    }
98
99    /// Write a 32-bit register at `off` (must be 4-byte aligned).
100    #[inline]
101    pub fn write32(&self, off: usize, val: u32) {
102        self.check(off, 4);
103        // SAFETY: as `read8`; check() bounded the write and proved it 4-byte aligned.
104        unsafe { core::ptr::write_volatile(self.base.add(off) as *mut u32, val) }
105    }
106
107    /// Read a 64-bit register at `off` (must be 8-byte aligned). For the
108    /// controller's 64-bit registers (DCBAAP, CRCR, ERSTBA, ERDP).
109    #[inline]
110    pub fn read64(&self, off: usize) -> u64 {
111        self.check(off, 8);
112        // SAFETY: as `read8`; check() bounded the access and proved it 8-byte aligned.
113        unsafe { core::ptr::read_volatile(self.base.add(off) as *const u64) }
114    }
115
116    /// Write a 64-bit register at `off` (must be 8-byte aligned).
117    #[inline]
118    pub fn write64(&self, off: usize, val: u64) {
119        self.check(off, 8);
120        // SAFETY: as `read8`; check() bounded the write and proved it 8-byte aligned.
121        unsafe { core::ptr::write_volatile(self.base.add(off) as *mut u64, val) }
122    }
123}
124
125/// A mapped framebuffer granted to the `console` service (via
126/// [`crate::ServiceContext::framebuffer`]), plus the geometry needed to address a pixel in it.
127///
128/// Unlike [`Mmio`], which exists to read and write device *registers* one at a time, a framebuffer is
129/// addressed in bulk - long contiguous runs, whole-row fills, a full-screen clear - so this hands out a
130/// **slice** rather than word accessors. That is what lets the renderer above it be ordinary
131/// bounds-checked Rust with no `unsafe` at all (§18.2 forbids `unsafe` in a service outright), and it
132/// keeps write-combining and write-buffer gathering available: a pixel run is one `copy_from_slice`,
133/// not N volatile stores.
134///
135/// The handle **owns** the mapping for the service's lifetime and is not `Copy`, so there is exactly one
136/// route to those bytes and it is a `&mut self` borrow. That matters beyond tidiness: the alternative
137/// spelling, a `&'static mut [u8]` field, is an unowned mutable global in everything but name.
138pub struct Framebuffer {
139    base: *mut u8,
140    len: usize,
141    /// Bytes per scanline.
142    pub pitch: usize,
143    /// Bytes per pixel.
144    pub bpp: usize,
145    /// Visible width in pixels.
146    pub width: usize,
147    /// Visible height in pixels.
148    pub height: usize,
149    /// Bit position of the red channel within a pixel.
150    pub r_shift: u32,
151    /// Bit position of the green channel within a pixel.
152    pub g_shift: u32,
153    /// Bit position of the blue channel within a pixel.
154    pub b_shift: u32,
155}
156
157impl Framebuffer {
158    /// Wrap a kernel-granted framebuffer mapping. Crate-internal: only the SDK constructs one, and only
159    /// from the VA and geometry the kernel wrote into this service's context page.
160    #[allow(clippy::too_many_arguments)]
161    pub(crate) fn new(
162        base: *mut u8,
163        len: usize,
164        pitch: usize,
165        bpp: usize,
166        width: usize,
167        height: usize,
168        r_shift: u32,
169        g_shift: u32,
170        b_shift: u32,
171    ) -> Self {
172        Self { base, len, pitch, bpp, width, height, r_shift, g_shift, b_shift }
173    }
174
175    /// The framebuffer's bytes, for rendering.
176    ///
177    /// The one `unsafe` a display costs. From here up, every pixel write is a bounds-checked slice
178    /// write: a wrong offset paints nothing rather than corrupting memory.
179    #[inline]
180    pub fn bytes_mut(&mut self) -> &mut [u8] {
181        // SAFETY: the kernel mapped exactly [base, base + len) into this service's address space as
182        // writable USER memory at spawn (the framebuffer grant), and that mapping lives as long as the
183        // service does - it is torn down only when the address space is, on death. `&mut self` gives
184        // exclusive access, and `Framebuffer` is neither `Copy` nor `Clone`, so no second live slice to
185        // these bytes can exist. `len` is the kernel's own `pitch * height`.
186        unsafe { core::slice::from_raw_parts_mut(self.base, self.len) }
187    }
188
189    /// Length of the mapping in bytes (`pitch * height`).
190    #[inline]
191    pub fn len(&self) -> usize {
192        self.len
193    }
194
195    /// Whether the mapping is empty. Present because clippy asks for it beside `len`; a granted
196    /// framebuffer is never zero-length.
197    #[inline]
198    pub fn is_empty(&self) -> bool {
199        self.len == 0
200    }
201}