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}