Skip to main content

godspeed/driver/
arena.rs

1// SPDX-License-Identifier: Apache-2.0
2//! A driver's ARENA: the memory its device reads and writes, granted at spawn, and laid out by name.
3//!
4//! # Why this exists
5//!
6//! A device that does DMA is given an arena: physically contiguous memory the kernel mapped into the
7//! driver, whose physical addresses the driver programs into the device (CLAUDE.md 12.3, 6.4). Every
8//! driver reached past the standard library for it, with the SDK's `ctx.dma_region()`, and then laid it out
9//! by hand (AHCI and GENET were the first to come through `gs`, 2026-10-19, and every compiled driver did
10//! by the next day, `docs/driver-library.md` step 3): seven drivers placed their rings, buffers and tables at offset constants
11//! of their own, 33 of them, and two asserts between them checked that nothing overlapped. The day this
12//! was written, the RTL8168's 24 receive buffers filled its arena to the last 4 KiB, and one hand-written
13//! assert was all that kept its counters from landing inside a buffer.
14//!
15//! So the arena comes through `gs` (`docs/driver-library.md`, step 3), with the one mechanism every one
16//! of those drivers wrote out: **parts, taken by name, size and alignment, that cannot overlap.**
17//!
18//! # The shape
19//!
20//! ```ignore
21//! let Some(arena) = gs::driver::arena(&ctx) else { /* say so and park */ };
22//! let mut parts = arena.layout();
23//! let rx_ring = parts.take("rx ring", 24 * 16, 256)?;
24//! let rx_bufs = parts.take("rx buffers", 24 * 2048, 8)?;
25//! let tally = parts.take("tally", 64, 64)?;
26//!
27//! rx_ring.write32(i * 16, own | len);      // offsets are within the part
28//! let buf = rx_bufs.phys_at(i * 2048);     // the address the device is given
29//! ```
30//!
31//! - **A part that does not fit is refused, with its name** ([`NoRoom`]), before anything is written:
32//!   ``the arena (64 KiB) has no room for `tally` (64 bytes, aligned to 64, after 64 KiB)``.
33//! - **Alignment is of the PHYSICAL address**, because that is what the device checks. A ring a device
34//!   requires on a 256-byte boundary is on one, whatever the arena's base.
35//! - **Every access is bounded to its part.** An offset past the part ends the driver loudly, naming the
36//!   part, as an offset past the arena always did (SEC-4) - so a ring's index running off its end is
37//!   caught at the ring, not when it reaches someone else's buffer.
38//! - **Laying out takes the arena**: once laid out it is reached only through its parts. A driver that
39//!   wants the whole arena, one buffer and nothing else, uses the [`Arena`] directly and needs no layout.
40//!
41//! What a part HOLDS - a descriptor's fields, a ring's ownership bits - is the device's, and stays in its
42//! driver. What is here is only where things are, which is the same for every device.
43//!
44//! # Coherency
45//!
46//! None is done here, as none is done beneath (the SDK's `Dma`, SEC-28): each port maps the arena so the
47//! CPU and the device agree - uncached where DMA is not coherent. A part is a view of that mapping.
48
49use core::fmt;
50
51/// Why a part was refused. Its `Display` is a sentence a driver can log as it stands.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct NoRoom {
54    /// The part asked for.
55    pub name: &'static str,
56    /// Its size in bytes.
57    pub len: usize,
58    /// The alignment it asked for.
59    pub align: usize,
60    /// How much of the arena the parts before it had taken.
61    pub used: usize,
62    /// The arena's size.
63    pub arena: usize,
64    /// What was wrong.
65    pub why: Why,
66}
67
68/// What made a part impossible.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum Why {
71    /// It does not fit in what is left.
72    Full,
73    /// Its alignment is not a power of two.
74    Alignment,
75    /// It asked for no bytes, which is a mistake in the driver rather than a part.
76    Empty,
77}
78
79impl fmt::Display for NoRoom {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self.why {
82            Why::Full => write!(
83                f,
84                "the arena ({}) has no room for `{}` ({} bytes, aligned to {}, after {})",
85                Size(self.arena), self.name, self.len, self.align, Size(self.used)
86            ),
87            Why::Alignment => write!(
88                f,
89                "`{}` asks for an alignment of {}, which is not a power of two",
90                self.name, self.align
91            ),
92            Why::Empty => write!(f, "`{}` asks for no bytes", self.name),
93        }
94    }
95}
96
97/// A size for a sentence: bytes below 1 KiB, KiB to one decimal above.
98struct Size(usize);
99
100impl fmt::Display for Size {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        if self.0 < 1024 {
103            return write!(f, "{} bytes", self.0);
104        }
105        let tenths = self.0 as u64 * 10 / 1024;
106        if tenths % 10 == 0 {
107            write!(f, "{} KiB", tenths / 10)
108        } else {
109            write!(f, "{}.{} KiB", tenths / 10, tenths % 10)
110        }
111    }
112}
113
114/// Where a part of `len` bytes, aligned to `align`, goes in an arena of `arena` bytes whose physical base
115/// is `phys` and whose first `used` bytes are taken: its offset, or why it cannot go anywhere.
116///
117/// Pure, so it is pinned by host tests; [`Layout::take`] is this and nothing more.
118pub fn place(phys: u64, arena: usize, used: usize, name: &'static str, len: usize, align: usize)
119    -> Result<usize, NoRoom> {
120    let refuse = |why| Err(NoRoom { name, len, align, used, arena, why });
121    if len == 0 {
122        return refuse(Why::Empty);
123    }
124    if align == 0 || !align.is_power_of_two() {
125        return refuse(Why::Alignment);
126    }
127    // The first offset at or after `used` whose PHYSICAL address is a multiple of `align`.
128    let mask = align as u64 - 1;
129    let at = match phys.checked_add(used as u64) {
130        Some(p) => p,
131        None => return refuse(Why::Full),
132    };
133    let gap = ((align as u64 - (at & mask)) & mask) as usize;
134    match used.checked_add(gap).and_then(|start| start.checked_add(len).map(|end| (start, end))) {
135        // Within the arena, and with a physical END the address space holds: a part whose address
136        // wrapped past zero would send the device somewhere else entirely.
137        Some((start, end)) if end <= arena && phys.checked_add(end as u64).is_some() => Ok(start),
138        _ => refuse(Why::Full),
139    }
140}
141
142#[cfg(not(test))]
143pub use granted::*;
144
145#[cfg(not(test))]
146mod granted {
147    use super::{place, NoRoom};
148    use godspeed_sdk::dma::Dma;
149    use godspeed_sdk::service_context::ServiceContext;
150
151    /// The DMA arena the kernel granted this driver. Not `Copy`: [`Arena::layout`] takes it, so a laid-out
152    /// arena is reached only through its parts.
153    pub struct Arena {
154        dma: Dma,
155    }
156
157    /// This driver's arena, or `None` when the spawn granted none.
158    pub fn arena(ctx: &ServiceContext) -> Option<Arena> {
159        ctx.dma_region().map(|dma| Arena { dma })
160    }
161
162    impl Arena {
163        /// The arena's size in bytes.
164        pub fn len(&self) -> usize {
165            self.dma.len()
166        }
167
168        /// The physical address of byte `off`: what the device is given.
169        pub fn phys_at(&self, off: usize) -> u64 {
170            self.dma.phys_at(off)
171        }
172
173        /// Zero the whole arena, with aligned volatile stores (never a `memset`, which may fault on the
174        /// Device memory a non-coherent port maps it as).
175        pub fn zero(&self) {
176            self.dma.zero()
177        }
178
179        /// Lay the arena out in named parts, from its start. The arena is taken: from here on it is
180        /// reached through the parts.
181        pub fn layout(self) -> Layout {
182            Layout { dma: self.dma, used: 0 }
183        }
184
185        /// Read 8 bits at `off`.
186        pub fn read8(&self, off: usize) -> u8 { self.dma.read8(off) }
187        /// Read 16 bits at `off`.
188        pub fn read16(&self, off: usize) -> u16 { self.dma.read16(off) }
189        /// Read 32 bits at `off`.
190        pub fn read32(&self, off: usize) -> u32 { self.dma.read32(off) }
191        /// Read 64 bits at `off`.
192        pub fn read64(&self, off: usize) -> u64 { self.dma.read64(off) }
193        /// Write 8 bits at `off`.
194        pub fn write8(&self, off: usize, val: u8) { self.dma.write8(off, val) }
195        /// Write 16 bits at `off`.
196        pub fn write16(&self, off: usize, val: u16) { self.dma.write16(off, val) }
197        /// Write 32 bits at `off`.
198        pub fn write32(&self, off: usize, val: u32) { self.dma.write32(off, val) }
199        /// Write 64 bits at `off`.
200        pub fn write64(&self, off: usize, val: u64) { self.dma.write64(off, val) }
201    }
202
203    /// An arena being laid out: parts are taken in order, each after the last, and never overlap.
204    pub struct Layout {
205        dma: Dma,
206        used: usize,
207    }
208
209    impl Layout {
210        /// Take the next part: `len` bytes, its PHYSICAL address a multiple of `align` (a power of two).
211        /// Refused, with the reason, when it does not fit in what is left, asks for no bytes, or names an
212        /// alignment that is not a power of two.
213        pub fn take(&mut self, name: &'static str, len: usize, align: usize) -> Result<Part, NoRoom> {
214            let off = place(self.dma.phys_base(), self.dma.len(), self.used, name, len, align)?;
215            self.used = off + len;
216            Ok(Part { dma: self.dma, name, off, len })
217        }
218
219        /// Bytes taken so far, alignment gaps included.
220        pub fn used(&self) -> usize {
221            self.used
222        }
223
224        /// The arena's size.
225        pub fn len(&self) -> usize {
226            self.dma.len()
227        }
228    }
229
230    /// One named part of the arena. Offsets are within the part, and an access past it ends the driver
231    /// naming the part. Copies are views of the same bytes.
232    #[derive(Clone, Copy)]
233    pub struct Part {
234        dma: Dma,
235        name: &'static str,
236        off: usize,
237        len: usize,
238    }
239
240    impl Part {
241        /// The name it was taken with.
242        pub fn name(&self) -> &'static str { self.name }
243        /// Its size in bytes.
244        pub fn len(&self) -> usize { self.len }
245        /// Where it starts in the arena, for a log line.
246        pub fn offset(&self) -> usize { self.off }
247
248        /// The physical address of its first byte.
249        pub fn phys(&self) -> u64 { self.phys_at(0) }
250
251        /// The physical address of byte `off` within it: what the device is given.
252        pub fn phys_at(&self, off: usize) -> u64 {
253            self.check(off, 0);
254            self.dma.phys_at(self.off + off)
255        }
256
257        /// Zero the part, with aligned volatile stores where it is aligned and bytes where it is not.
258        pub fn zero(&self) {
259            let mut i = 0;
260            while i < self.len {
261                if (self.off + i) % 8 == 0 && i + 8 <= self.len {
262                    self.dma.write64(self.off + i, 0);
263                    i += 8;
264                } else {
265                    self.dma.write8(self.off + i, 0);
266                    i += 1;
267                }
268            }
269        }
270
271        fn check(&self, off: usize, size: usize) {
272            if !off.checked_add(size).map_or(false, |end| end <= self.len) {
273                panic!("arena part `{}`: {} bytes at {} is past its {} bytes", self.name, size, off, self.len);
274            }
275        }
276
277        /// Read 8 bits at `off` within the part.
278        pub fn read8(&self, off: usize) -> u8 { self.check(off, 1); self.dma.read8(self.off + off) }
279        /// Read 16 bits at `off` within the part.
280        pub fn read16(&self, off: usize) -> u16 { self.check(off, 2); self.dma.read16(self.off + off) }
281        /// Read 32 bits at `off` within the part.
282        pub fn read32(&self, off: usize) -> u32 { self.check(off, 4); self.dma.read32(self.off + off) }
283        /// Read 64 bits at `off` within the part.
284        pub fn read64(&self, off: usize) -> u64 { self.check(off, 8); self.dma.read64(self.off + off) }
285        /// Write 8 bits at `off` within the part.
286        pub fn write8(&self, off: usize, val: u8) { self.check(off, 1); self.dma.write8(self.off + off, val) }
287        /// Write 16 bits at `off` within the part.
288        pub fn write16(&self, off: usize, val: u16) { self.check(off, 2); self.dma.write16(self.off + off, val) }
289        /// Write 32 bits at `off` within the part.
290        pub fn write32(&self, off: usize, val: u32) { self.check(off, 4); self.dma.write32(self.off + off, val) }
291        /// Write 64 bits at `off` within the part.
292        pub fn write64(&self, off: usize, val: u64) { self.check(off, 8); self.dma.write64(self.off + off, val) }
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    const ARENA: usize = 64 * 1024;
301    const PAGE: u64 = 0x4361000;
302
303    #[test]
304    fn parts_follow_one_another() {
305        assert_eq!(place(PAGE, ARENA, 0, "ring", 384, 256), Ok(0));
306        assert_eq!(place(PAGE, ARENA, 384, "bufs", 2048, 8), Ok(384));
307    }
308
309    #[test]
310    fn alignment_is_of_the_physical_address() {
311        // 384 rounds up to 512 for a 256-byte boundary on a page-aligned arena...
312        assert_eq!(place(PAGE, ARENA, 384, "ring", 16, 256), Ok(512));
313        // ...and an arena whose base is only 64-aligned is padded to the PHYSICAL boundary, not to its offset.
314        assert_eq!(place(0x1040, ARENA, 0, "ring", 16, 256), Ok(0xC0));
315    }
316
317    #[test]
318    fn the_rtl8168_layout_fits_exactly_and_one_more_byte_does_not() {
319        // TX ring, TX buffer, RX ring, 24 x 2 KiB RX buffers, the tally: the RTL8168's layout when its ring was
320        // 24 (it is 256 now, in 132 pages); kept as a layout that fills its arena exactly.
321        let mut used = 0;
322        for (name, len, align) in [("tx ring", 128, 256), ("tx buffer", 2048, 4096), ("rx ring", 384, 4096),
323                                   ("rx buffers", 24 * 2048, 4096), ("tally", 64, 64)] {
324            let off = place(PAGE, ARENA, used, name, len, align).expect(name);
325            used = off + len;
326        }
327        assert_eq!(used, 0xF040);
328        let r = place(PAGE, ARENA, ARENA - 63, "one more", 64, 1);
329        assert_eq!(r.map_err(|e| e.why), Err(Why::Full));
330    }
331
332    #[test]
333    fn a_part_that_does_not_fit_is_named() {
334        let e = place(PAGE, ARENA, ARENA, "tally", 64, 64).unwrap_err();
335        assert_eq!(e.why, Why::Full);
336        let mut s = String::new();
337        use core::fmt::Write;
338        write!(s, "{}", e).unwrap();
339        assert_eq!(s, "the arena (64 KiB) has no room for `tally` (64 bytes, aligned to 64, after 64 KiB)");
340    }
341
342    #[test]
343    fn nonsense_is_refused_as_nonsense() {
344        assert_eq!(place(PAGE, ARENA, 0, "x", 16, 3).map_err(|e| e.why), Err(Why::Alignment));
345        assert_eq!(place(PAGE, ARENA, 0, "x", 16, 0).map_err(|e| e.why), Err(Why::Alignment));
346        assert_eq!(place(PAGE, ARENA, 0, "x", 0, 8).map_err(|e| e.why), Err(Why::Empty));
347    }
348
349    #[test]
350    fn no_arithmetic_wraps() {
351        assert_eq!(place(PAGE, ARENA, usize::MAX - 2, "x", 16, 8).map_err(|e| e.why), Err(Why::Full));
352        assert_eq!(place(u64::MAX - 4, ARENA, 0, "x", 16, 8).map_err(|e| e.why), Err(Why::Full));
353        assert_eq!(place(PAGE, ARENA, 0, "x", usize::MAX, 1).map_err(|e| e.why), Err(Why::Full));
354    }
355
356    #[test]
357    fn sizes_read_as_people_say_them() {
358        let s = |n| { let mut t = String::new(); use core::fmt::Write; write!(t, "{}", Size(n)).unwrap(); t };
359        assert_eq!(s(64), "64 bytes");
360        assert_eq!(s(64 * 1024), "64 KiB");
361        assert_eq!(s(0xF000 + 900), "60.8 KiB");
362    }
363}