Skip to main content

xarxa_driver/
buf.rs

1//! Owned packet buffers.
2//!
3//! Every packet in the stack is a [`PacketBuf`]: one fixed-size buffer, owned by
4//! whoever holds it (the driver, the stack, a socket, the application).
5//!
6//! Buffers are allocated from a static pool.
7
8use core::cell::UnsafeCell;
9use core::fmt;
10use core::mem::MaybeUninit;
11use core::ops::{Deref, DerefMut};
12use core::ptr::NonNull;
13
14use core::sync::atomic::{AtomicU32, Ordering};
15
16use crate::config::PACKET_BUF_SIZE;
17use crate::meta::PacketMeta;
18
19#[cfg(not(test))]
20const PACKET_BUF_COUNT: usize = crate::config::PACKET_BUF_COUNT;
21// The unit tests run in parallel threads of one process, all sharing the one
22// pool. The default is too small.
23#[cfg(test)]
24const PACKET_BUF_COUNT: usize = if crate::config::PACKET_BUF_COUNT > 1024 {
25    crate::config::PACKET_BUF_COUNT
26} else {
27    1024
28};
29
30const BITMAP_WORDS: usize = PACKET_BUF_COUNT.div_ceil(32);
31
32cfg_select! {
33    feature = "packet-buf-align-32" => { #[repr(C, align(32))] struct Data([u8; PACKET_BUF_SIZE]); }
34    feature = "packet-buf-align-16" => { #[repr(C, align(16))] struct Data([u8; PACKET_BUF_SIZE]); }
35    feature = "packet-buf-align-8" => { #[repr(C, align(8))] struct Data([u8; PACKET_BUF_SIZE]); }
36    feature = "packet-buf-align-4" => { #[repr(C, align(4))] struct Data([u8; PACKET_BUF_SIZE]); }
37    feature = "packet-buf-align-2" => { #[repr(C, align(2))] struct Data([u8; PACKET_BUF_SIZE]); }
38    _ => { #[repr(C, align(1))] struct Data([u8; PACKET_BUF_SIZE]); }
39}
40
41impl Deref for Data {
42    type Target = [u8; PACKET_BUF_SIZE];
43    fn deref(&self) -> &Self::Target {
44        &self.0
45    }
46}
47
48impl DerefMut for Data {
49    fn deref_mut(&mut self) -> &mut Self::Target {
50        &mut self.0
51    }
52}
53
54struct PacketBufInner {
55    /// Offset of the first valid byte within `data`.
56    headroom: u16,
57    /// Number of valid bytes.
58    len: u16,
59    // invariant: headroom + len <= PACKET_BUF_SIZE
60    /// Per-packet metadata. Zero-sized unless a `packetmeta-*` feature is enabled.
61    meta: PacketMeta,
62    data: Data,
63}
64
65struct Pool {
66    /// Bit `i` is set while slot `i` is owned by a `PacketBuf`.
67    used: [AtomicU32; BITMAP_WORDS],
68    slots: [UnsafeCell<MaybeUninit<PacketBufInner>>; PACKET_BUF_COUNT],
69}
70
71// SAFETY: a slot is handed to at most one `PacketBuf` at a time. Its bit is
72// set by the one CAS that wins it in `alloc_slot`, and cleared only by the
73// `PacketBuf` that owns it, in `Drop`. So no two threads ever touch the same
74// slot, and the bitmap is atomic.
75unsafe impl Sync for Pool {}
76
77static POOL: Pool = Pool {
78    used: [const { AtomicU32::new(0) }; BITMAP_WORDS],
79    slots: [const { UnsafeCell::new(MaybeUninit::zeroed()) }; PACKET_BUF_COUNT],
80};
81
82/// Claim a free slot: the first zero bit of the bitmap, set with a CAS.
83#[cfg(target_has_atomic = "32")]
84#[inline(never)]
85fn alloc_slot() -> Option<usize> {
86    for (w, word) in POOL.used.iter().enumerate() {
87        let mut cur = word.load(Ordering::Relaxed);
88        loop {
89            let bit = cur.trailing_ones() as usize;
90            if bit >= 32 {
91                break;
92            }
93            let index = w * 32 + bit;
94            if index >= PACKET_BUF_COUNT {
95                // Only the last word can have bits past the end. Everything
96                // before it was full, so the pool is.
97                return None;
98            }
99            // Acquire pairs with the Release in `Drop`: the previous owner's
100            // writes to the slot are done before ours start.
101            match word.compare_exchange_weak(cur, cur | (1 << bit), Ordering::Acquire, Ordering::Relaxed) {
102                Ok(_) => return Some(index),
103                Err(actual) => cur = actual,
104            }
105        }
106    }
107    None
108}
109
110/// Give a slot back: clear its bit.
111#[cfg(target_has_atomic = "32")]
112#[inline(never)]
113fn free_slot(index: usize) {
114    POOL.used[index / 32].fetch_and(!(1 << (index % 32)), Ordering::Release);
115}
116
117// Fallback for targets with 32-bit atomic load/store but no atomic
118// read-modify-write (e.g. thumbv6m): the whole bit update runs inside a
119// critical section, so a plain load + store can't race another one. The
120// Acquire load here still pairs with the Release store in `free_slot`, same
121// as the atomic version.
122
123/// Claim a free slot: the first zero bit of the bitmap.
124#[cfg(not(target_has_atomic = "32"))]
125fn alloc_slot() -> Option<usize> {
126    critical_section::with(|_| {
127        for (w, word) in POOL.used.iter().enumerate() {
128            let cur = word.load(Ordering::Acquire);
129            let bit = cur.trailing_ones() as usize;
130            if bit >= 32 {
131                continue;
132            }
133            let index = w * 32 + bit;
134            if index >= PACKET_BUF_COUNT {
135                // Only the last word can have bits past the end. Everything
136                // before it was full, so the pool is.
137                return None;
138            }
139            word.store(cur | (1 << bit), Ordering::Relaxed);
140            return Some(index);
141        }
142        None
143    })
144}
145
146/// Give a slot back: clear its bit.
147#[cfg(not(target_has_atomic = "32"))]
148fn free_slot(index: usize) {
149    critical_section::with(|_| {
150        let word = &POOL.used[index / 32];
151        word.store(word.load(Ordering::Relaxed) & !(1 << (index % 32)), Ordering::Release);
152    })
153}
154
155/// An owned network packet buffer.
156///
157/// ```text
158/// | headroom | data (len) | tailroom |
159/// ```
160pub struct PacketBuf {
161    inner: NonNull<PacketBufInner>,
162}
163
164// SAFETY: a `PacketBuf` is the unique owner of its slot, like a `Box` of it.
165unsafe impl Send for PacketBuf {}
166unsafe impl Sync for PacketBuf {}
167
168impl PacketBuf {
169    /// Allocate a buffer.
170    ///
171    /// - Zero headroom, len.
172    /// - Default metadata.
173    /// - **Uninitialized** data.
174    pub fn try_new() -> Option<Self> {
175        let index = alloc_slot()?;
176        let ptr = POOL.slots[index].get().cast::<PacketBufInner>();
177        // SAFETY:
178        // - the slot is ours (its bit is set), and nothing else points into it.
179        // - `data` is valid thanks to `MaybeUninit::zeroed()`, we don't have to initialize it.
180        // - We do initialize the header.
181        unsafe {
182            (&raw mut (*ptr).headroom).write(0);
183            (&raw mut (*ptr).len).write(0);
184            (&raw mut (*ptr).meta).write(PacketMeta::default());
185            // Catch code that relies on fresh buffers being zeroed.
186            #[cfg(test)]
187            (*ptr).data.fill(0xa5);
188        }
189        Some(Self {
190            // SAFETY: a pointer into a static is never null.
191            inner: unsafe { NonNull::new_unchecked(ptr) },
192        })
193    }
194
195    #[inline]
196    fn inner(&self) -> &PacketBufInner {
197        // SAFETY: we own the slot for as long as `self` exists.
198        unsafe { self.inner.as_ref() }
199    }
200
201    #[inline]
202    fn inner_mut(&mut self) -> &mut PacketBufInner {
203        // SAFETY: we own the slot for as long as `self` exists, and `&mut self`
204        // makes this the only reference.
205        unsafe { self.inner.as_mut() }
206    }
207
208    /// The packet's metadata.
209    ///
210    /// On a received packet this is what the driver attached to it. On a packet being
211    /// sent it is what the application attached, and what the driver will see in
212    /// [`Driver::transmit`](crate::Driver::transmit). It travels with the
213    /// buffer through the whole stack, unaffected by header pushes and pulls.
214    pub fn meta(&self) -> PacketMeta {
215        self.inner().meta
216    }
217
218    /// Mutable reference to the packet's metadata.
219    pub fn meta_mut(&mut self) -> &mut PacketMeta {
220        &mut self.inner_mut().meta
221    }
222
223    /// Replace the packet's metadata.
224    pub fn set_meta(&mut self, meta: PacketMeta) {
225        self.inner_mut().meta = meta;
226    }
227
228    /// Total storage capacity of the buffer, in bytes.
229    pub const fn capacity(&self) -> usize {
230        PACKET_BUF_SIZE
231    }
232
233    /// Amount of free space in front of the payload.
234    pub fn headroom(&self) -> usize {
235        self.inner().headroom as usize
236    }
237
238    /// Length of the payload.
239    pub fn len(&self) -> usize {
240        self.inner().len as usize
241    }
242
243    /// Whether the payload is empty.
244    pub fn is_empty(&self) -> bool {
245        self.inner().len == 0
246    }
247
248    /// Amount of free space behind the payload.
249    pub fn tailroom(&self) -> usize {
250        PACKET_BUF_SIZE - self.headroom() - self.len()
251    }
252
253    /// Set the headroom on an empty buffer, before writing a payload.
254    ///
255    /// # Panics
256    /// Panics if the buffer is not empty, or if `headroom > capacity`.
257    pub fn reserve(&mut self, headroom: usize) {
258        assert!(self.inner().len == 0);
259        assert!(headroom <= PACKET_BUF_SIZE);
260        self.inner_mut().headroom = headroom as u16;
261    }
262
263    /// Grow the payload at the front by `n` bytes, taking them from the headroom.
264    ///
265    /// # Panics
266    /// Panics if `n > headroom`.
267    pub fn push_front(&mut self, n: usize) {
268        assert!(n <= self.headroom());
269        let inner = self.inner_mut();
270        inner.headroom -= n as u16;
271        inner.len += n as u16;
272    }
273
274    /// Shrink the payload at the front by `n` bytes, returning them to the headroom.
275    ///
276    /// # Panics
277    /// Panics if `n > len`.
278    pub fn pull_front(&mut self, n: usize) {
279        assert!(n <= self.len());
280        let inner = self.inner_mut();
281        inner.headroom += n as u16;
282        inner.len -= n as u16;
283    }
284
285    /// Make room for `headroom` bytes in front of the payload, moving the payload
286    /// back if there isn't enough already.
287    ///
288    /// Returns `false` if the buffer can't fit `headroom` plus the payload, leaving
289    /// it unchanged.
290    pub fn ensure_headroom(&mut self, headroom: usize) -> bool {
291        if self.headroom() >= headroom {
292            return true;
293        }
294        let inner = self.inner_mut();
295        let len = inner.len as usize;
296        if headroom + len > PACKET_BUF_SIZE {
297            return false;
298        }
299        let old = inner.headroom as usize;
300        inner.data.copy_within(old..old + len, headroom);
301        inner.headroom = headroom as u16;
302        true
303    }
304
305    /// Set the payload length, growing or shrinking it at the back.
306    ///
307    /// # Panics
308    /// Panics if `headroom + len > capacity`.
309    pub fn set_len(&mut self, len: usize) {
310        assert!(self.headroom() + len <= PACKET_BUF_SIZE);
311        self.inner_mut().len = len as u16;
312    }
313
314    /// The whole underlying storage, ignoring headroom and length.
315    ///
316    /// The returned slice is aligned to [`PACKET_BUF_ALIGN`](crate::config::PACKET_BUF_ALIGN), and its length
317    /// ([`PACKET_BUF_SIZE`]) is a multiple of it.
318    pub fn storage_mut(&mut self) -> &mut [u8] {
319        &mut self.inner_mut().data[..]
320    }
321}
322
323impl Drop for PacketBuf {
324    #[inline(never)] // helps code size
325    fn drop(&mut self) {
326        let base = POOL.slots.as_ptr() as usize;
327        let index =
328            (self.inner.as_ptr() as usize - base) / core::mem::size_of::<UnsafeCell<MaybeUninit<PacketBufInner>>>();
329        free_slot(index);
330    }
331}
332
333impl Deref for PacketBuf {
334    type Target = [u8];
335    fn deref(&self) -> &Self::Target {
336        let inner = self.inner();
337        let start = inner.headroom as usize;
338        let end = start + inner.len as usize;
339        &inner.data[start..end]
340    }
341}
342impl DerefMut for PacketBuf {
343    fn deref_mut(&mut self) -> &mut Self::Target {
344        let inner = self.inner_mut();
345        let start = inner.headroom as usize;
346        let end = start + inner.len as usize;
347        &mut inner.data[start..end]
348    }
349}
350
351impl fmt::Debug for PacketBuf {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        f.debug_struct("PacketBuf")
354            .field("headroom", &self.headroom())
355            .field("len", &self.len())
356            .finish()
357    }
358}
359
360#[cfg(feature = "defmt")]
361impl defmt::Format for PacketBuf {
362    fn format(&self, f: defmt::Formatter<'_>) {
363        defmt::write!(f, "PacketBuf {{ headroom: {}, len: {} }}", self.headroom(), self.len());
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use crate::config::PACKET_BUF_ALIGN;
371
372    #[test]
373    fn push_pull() {
374        let mut buf = PacketBuf::try_new().unwrap();
375        assert_eq!(buf.len(), 0);
376        assert_eq!(buf.headroom(), 0);
377        assert_eq!(buf.tailroom(), PACKET_BUF_SIZE);
378
379        buf.reserve(42);
380        assert_eq!(buf.headroom(), 42);
381        buf.set_len(100);
382        assert_eq!(buf.len(), 100);
383        assert_eq!(buf.tailroom(), PACKET_BUF_SIZE - 142);
384        buf.fill(0xaa);
385
386        buf.push_front(20);
387        assert_eq!(buf.headroom(), 22);
388        assert_eq!(buf.len(), 120);
389        assert_eq!(buf[20], 0xaa);
390
391        buf.pull_front(20);
392        assert_eq!(buf.headroom(), 42);
393        assert_eq!(buf.len(), 100);
394        assert_eq!(buf[0], 0xaa);
395    }
396
397    #[test]
398    fn ensure_headroom() {
399        let mut buf = PacketBuf::try_new().unwrap();
400        buf.reserve(10);
401        buf.set_len(4);
402        buf.copy_from_slice(&[1, 2, 3, 4]);
403
404        // Already enough: nothing moves.
405        assert!(buf.ensure_headroom(4));
406        assert_eq!(buf.headroom(), 10);
407        assert_eq!(&*buf, &[1, 2, 3, 4]);
408
409        // Not enough: the payload moves back, unchanged.
410        assert!(buf.ensure_headroom(20));
411        assert_eq!(buf.headroom(), 20);
412        assert_eq!(buf.len(), 4);
413        assert_eq!(&*buf, &[1, 2, 3, 4]);
414
415        // The headroom overlapping the payload is fine, it's a move not a copy.
416        assert!(buf.ensure_headroom(22));
417        assert_eq!(&*buf, &[1, 2, 3, 4]);
418
419        // Doesn't fit: the buffer is left alone.
420        assert!(!buf.ensure_headroom(PACKET_BUF_SIZE - 3));
421        assert_eq!(buf.headroom(), 22);
422        assert_eq!(&*buf, &[1, 2, 3, 4]);
423        assert!(buf.ensure_headroom(PACKET_BUF_SIZE - 4));
424        assert_eq!(&*buf, &[1, 2, 3, 4]);
425    }
426
427    #[test]
428    #[should_panic]
429    fn push_beyond_headroom() {
430        let mut buf = PacketBuf::try_new().unwrap();
431        buf.push_front(1);
432    }
433
434    /// The storage a driver DMAs into must stay aligned to `PACKET_BUF_ALIGN` and
435    /// a multiple of it long, whatever the metadata in front of it does to the
436    /// layout.
437    #[test]
438    fn storage_is_dma_shaped() {
439        let mut buf = PacketBuf::try_new().unwrap();
440        assert_eq!(buf.storage_mut().as_ptr() as usize % PACKET_BUF_ALIGN, 0);
441        assert_eq!(buf.storage_mut().len() % PACKET_BUF_ALIGN, 0);
442        assert!(buf.storage_mut().len() >= PACKET_BUF_SIZE);
443    }
444
445    /// A fresh buffer starts out empty with default metadata, whatever its previous
446    /// owner left behind. (Pool exhaustion and reuse are covered by xarxa's
447    /// `packet_pool` integration test, which has a process's pool to itself.)
448    #[test]
449    fn fresh_buffer_is_reset() {
450        let mut buf = PacketBuf::try_new().unwrap();
451        buf.reserve(100);
452        buf.set_len(200);
453        buf.fill(0xff);
454        drop(buf);
455
456        let buf = PacketBuf::try_new().unwrap();
457        assert_eq!(buf.len(), 0);
458        assert_eq!(buf.headroom(), 0);
459        assert_eq!(buf.meta(), PacketMeta::default());
460    }
461
462    /// Metadata rides along with the buffer, untouched by the header pushes and pulls
463    /// the packet goes through on its way up or down the stack.
464    #[cfg(feature = "packetmeta-id")]
465    #[test]
466    fn meta_travels_with_the_buffer() {
467        let mut buf = PacketBuf::try_new().unwrap();
468        assert_eq!(buf.meta(), PacketMeta::default());
469
470        buf.meta_mut().id = 0xdead_beef;
471        buf.reserve(20);
472        buf.set_len(10);
473        buf.push_front(20);
474        buf.pull_front(4);
475        assert_eq!(buf.meta().id, 0xdead_beef);
476
477        buf.set_meta(PacketMeta::default());
478        assert_eq!(buf.meta().id, 0);
479    }
480}