Skip to main content

esp_p4_eth/
descriptors.rs

1//! DMA descriptor and ring management.
2//!
3//! Descriptors and backing buffers are aligned to 128 bytes to match the P4
4//! L2 cache line size (`CONFIG_CACHE_L2_CACHE_LINE_128B=y` in the IDF default
5//! sdkconfig). 128-byte alignment ensures each descriptor / buffer occupies
6//! its own L2 line, so a `Cache_Invalidate_Addr` on one entry never drops
7//! cached writes from a neighbour. P4 L1 D-cache line is 64 bytes, but
8//! coherency must be maintained at the wider of the two. That alignment is
9//! important because cache maintenance is
10//! performed on whole cache lines, not individual descriptor words.
11
12use core::mem::{align_of, offset_of, size_of};
13use core::sync::atomic::{fence, AtomicU32, Ordering};
14
15/// Diagnostic: total runt frames dropped (len < MIN_RX_FRAME_SIZE).
16pub static RX_RUNT_FRAMES_TOTAL: AtomicU32 = AtomicU32::new(0);
17/// Diagnostic: total oversized frames dropped (len > BUF_SIZE).
18pub static RX_OVERSIZED_FRAMES_TOTAL: AtomicU32 = AtomicU32::new(0);
19/// Diagnostic: total error frames dropped (RDES0_ES set or incomplete).
20pub static RX_ERROR_FRAMES_TOTAL: AtomicU32 = AtomicU32::new(0);
21/// Diagnostic: frame length of the last successfully-received RX frame.
22pub static RX_LAST_FRAME_LEN: AtomicU32 = AtomicU32::new(0);
23/// Diagnostic: RDES0 of the last RX descriptor we inspected (any path).
24pub static RX_LAST_RDES0: AtomicU32 = AtomicU32::new(0);
25/// Diagnostic: count of frames where len >= 200 (size-cutoff investigation).
26pub static RX_LARGE_FRAMES: AtomicU32 = AtomicU32::new(0);
27
28use vcell::VolatileCell;
29
30use crate::dma::Dma;
31#[cfg(test)]
32use crate::regs;
33
34/// Default number of TX descriptors in the ring.
35pub const TX_DESC_COUNT: usize = 8;
36/// Default number of RX descriptors in the ring.
37pub const RX_DESC_COUNT: usize = 8;
38/// Size of each packet buffer in bytes.
39pub const BUF_SIZE: usize = 1536;
40/// Minimum accepted RX frame length for non-empty packets.
41pub const MIN_RX_FRAME_SIZE: usize = 64;
42
43/// TX descriptor ownership bit.
44pub const TDES0_OWN: u32 = 1 << 31;
45/// TX interrupt-on-completion flag for a descriptor.
46pub const TDES0_IC: u32 = 1 << 30;
47/// TX last-segment flag.
48pub const TDES0_LS: u32 = 1 << 29;
49/// TX first-segment flag.
50pub const TDES0_FS: u32 = 1 << 28;
51/// TX chained-descriptor mode bit.
52pub const TDES0_CHAINED: u32 = 1 << 20;
53
54/// RX descriptor ownership bit.
55pub const RDES0_OWN: u32 = 1 << 31;
56/// Shift for the RX frame-length field inside `RDES0`.
57pub const RDES0_FL_SHIFT: u32 = 16;
58/// Mask for the RX frame-length field inside `RDES0`.
59pub const RDES0_FL_MASK: u32 = 0x3fff << RDES0_FL_SHIFT;
60/// RX error-summary bit.
61pub const RDES0_ES: u32 = 1 << 15;
62/// RX first-segment flag.
63pub const RDES0_FS: u32 = 1 << 9;
64/// RX last-segment flag.
65pub const RDES0_LS: u32 = 1 << 8;
66
67/// RX buffer-1 size field mask in `RDES1`.
68pub const RDES1_BUF1_SIZE_MASK: u32 = 0x1fff;
69/// RX chained-descriptor mode bit.
70pub const RDES1_CHAINED: u32 = 1 << 14;
71
72/// Current descriptor owner.
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub enum OwnedBy {
75    /// The CPU owns the descriptor.
76    Cpu,
77    /// The DMA engine owns the descriptor.
78    Dma,
79}
80
81/// Error returned when a packet does not fit into one DMA buffer.
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub enum BufferTooLarge {
84    /// Frame length exceeded the supported single-buffer size.
85    Frame { len: usize, max: usize },
86}
87
88/// Ring-level transmit/receive submission errors.
89#[derive(Clone, Copy, Debug, Eq, PartialEq)]
90pub enum DescriptorError {
91    /// Frame did not fit in the backing DMA buffer.
92    BufferTooLarge(BufferTooLarge),
93    /// The ring has no CPU-owned descriptor available right now.
94    RingFull,
95    /// No frame is available in the current RX descriptor.
96    NoFrame,
97}
98
99/// Cumulative TX ring counters useful during bring-up and soak testing.
100#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
101pub struct TxRingStats {
102    /// Number of frames successfully queued to DMA.
103    pub transmitted_frames: u32,
104    /// Number of times TX found no CPU-owned descriptor.
105    pub ring_full_events: u32,
106}
107
108/// Cumulative RX ring counters useful during bring-up and soak testing.
109#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
110pub struct RxRingStats {
111    /// Number of frames returned to software successfully.
112    pub received_frames: u32,
113    /// Number of frames dropped because the descriptor reported errors.
114    pub error_frames: u32,
115    /// Number of frames dropped for being shorter than [`MIN_RX_FRAME_SIZE`].
116    pub runt_frames: u32,
117    /// Number of frames dropped for exceeding the backing DMA buffer size.
118    pub oversized_frames: u32,
119    /// Number of times the RX ring was rebuilt after overflow/recovery.
120    pub overflow_resets: u32,
121}
122
123/// Raw DMA descriptor payload size before alignment padding.
124pub const DMA_DESCRIPTOR_WORDS_SIZE: usize = 16;
125/// Required cache-line alignment for descriptors and static buffers.
126/// Matches `CONFIG_CACHE_L2_CACHE_LINE_SIZE = 128` on ESP32-P4.
127pub const DMA_DESCRIPTOR_ALIGN: usize = 128;
128
129/// Cache-line aligned packet buffer block.
130#[repr(C, align(128))]
131pub struct DmaBuffers<const N: usize> {
132    /// Backing packet storage for one DMA ring.
133    pub inner: [[u8; BUF_SIZE]; N],
134}
135
136impl<const N: usize> DmaBuffers<N> {
137    /// Zero-initialized packet storage block.
138    pub const ZERO: Self = Self {
139        inner: [[0; BUF_SIZE]; N],
140    };
141}
142
143/// Borrowed view into the descriptor and packet-buffer arrays owned by [`StaticDmaResources`].
144pub type SplitDmaResources<'a> = (
145    &'a mut [TDes; TX_DESC_COUNT],
146    &'a mut [RDes; RX_DESC_COUNT],
147    &'a mut [[u8; BUF_SIZE]; TX_DESC_COUNT],
148    &'a mut [[u8; BUF_SIZE]; RX_DESC_COUNT],
149);
150
151/// Safe owner for all static DMA memory used by the driver.
152pub struct StaticDmaResources {
153    tx_desc: [TDes; TX_DESC_COUNT],
154    rx_desc: [RDes; RX_DESC_COUNT],
155    tx_buf: DmaBuffers<TX_DESC_COUNT>,
156    rx_buf: DmaBuffers<RX_DESC_COUNT>,
157}
158
159impl StaticDmaResources {
160    /// Creates a zero-initialized static DMA resource block.
161    pub const fn new() -> Self {
162        Self {
163            tx_desc: zeroed_tx_desc_array(),
164            rx_desc: zeroed_rx_desc_array(),
165            tx_buf: DmaBuffers::ZERO,
166            rx_buf: DmaBuffers::ZERO,
167        }
168    }
169
170    /// Splits the block into the descriptor and packet-buffer slices expected by the driver.
171    pub fn split(&mut self) -> SplitDmaResources<'_> {
172        (
173            &mut self.tx_desc,
174            &mut self.rx_desc,
175            &mut self.tx_buf.inner,
176            &mut self.rx_buf.inner,
177        )
178    }
179}
180
181impl Default for StaticDmaResources {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187/// Raw transmit descriptor layout as seen by the DMA engine.
188#[repr(C, align(128))]
189pub struct TDes {
190    tdes0: VolatileCell<u32>,
191    tdes1: VolatileCell<u32>,
192    buf_addr: VolatileCell<u32>,
193    next_desc: VolatileCell<u32>,
194}
195
196const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); size_of::<TDes>()];
197const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); align_of::<TDes>()];
198const _: [(); 0] = [(); offset_of!(TDes, tdes0)];
199const _: [(); 4] = [(); offset_of!(TDes, tdes1)];
200const _: [(); 8] = [(); offset_of!(TDes, buf_addr)];
201const _: [(); 12] = [(); offset_of!(TDes, next_desc)];
202
203impl TDes {
204    /// Creates a zero-initialized descriptor value suitable for static storage.
205    pub const fn new_zeroed() -> Self {
206        Self {
207            tdes0: VolatileCell::new(0),
208            tdes1: VolatileCell::new(0),
209            buf_addr: VolatileCell::new(0),
210            next_desc: VolatileCell::new(0),
211        }
212    }
213
214    /// Sets the software-visible ownership bit.
215    pub fn set_owned_by(&self, owner: OwnedBy) {
216        let value = match owner {
217            OwnedBy::Cpu => self.tdes0.get() & !TDES0_OWN,
218            OwnedBy::Dma => self.tdes0.get() | TDES0_OWN,
219        };
220        self.tdes0.set(value);
221    }
222
223    /// Returns the current descriptor owner according to the ownership bit.
224    pub fn owned_by(&self) -> OwnedBy {
225        if self.tdes0.get() & TDES0_OWN != 0 {
226            OwnedBy::Dma
227        } else {
228            OwnedBy::Cpu
229        }
230    }
231
232    fn set_len_and_flags(&self, len: usize) {
233        self.tdes1.set((len as u32) & RDES1_BUF1_SIZE_MASK);
234        self.tdes0
235            .set((self.tdes0.get() & TDES0_CHAINED) | TDES0_FS | TDES0_LS | TDES0_IC);
236    }
237
238    fn set_chained(&self) {
239        self.tdes0.set(self.tdes0.get() | TDES0_CHAINED);
240    }
241
242    fn set_buffer_addr(&self, addr: *const u8) {
243        self.buf_addr.set(addr as usize as u32);
244    }
245
246    fn set_next_desc(&self, addr: *const TDes) {
247        self.next_desc.set(addr as usize as u32);
248    }
249}
250
251/// Snapshot of what CPU last wrote into RX descriptor 0 — exposed for debug
252/// to compare against memory state read via `Dma::peek_rdes` (after invalidate).
253#[cfg(target_arch = "riscv32")]
254pub static CPU_DESC0_SNAPSHOT: [core::sync::atomic::AtomicU32; 4] = [
255    core::sync::atomic::AtomicU32::new(0),
256    core::sync::atomic::AtomicU32::new(0),
257    core::sync::atomic::AtomicU32::new(0),
258    core::sync::atomic::AtomicU32::new(0),
259];
260
261/// Last TX descriptor address handed to DMA.
262#[cfg(target_arch = "riscv32")]
263pub static TX_LAST_DESC_ADDR: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
264/// Last TX packet buffer address handed to DMA (DMA reads frame from here).
265#[cfg(target_arch = "riscv32")]
266pub static TX_LAST_BUF_ADDR: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
267/// Last TX descriptor word 0 (status/flags) — should have OWN=1 (DMA), FS=1, LS=1, IC=1, CHAINED=1.
268#[cfg(target_arch = "riscv32")]
269pub static TX_LAST_TDES0: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
270/// Last TX descriptor word 1 (lengths). Low 13 bits = buffer 1 size = frame length.
271#[cfg(target_arch = "riscv32")]
272pub static TX_LAST_TDES1: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
273
274/// Raw receive descriptor layout as seen by the DMA engine.
275#[repr(C, align(128))]
276pub struct RDes {
277    rdes0: VolatileCell<u32>,
278    rdes1: VolatileCell<u32>,
279    buf_addr: VolatileCell<u32>,
280    next_desc: VolatileCell<u32>,
281}
282
283const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); size_of::<RDes>()];
284const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); align_of::<RDes>()];
285const _: [(); 0] = [(); offset_of!(RDes, rdes0)];
286const _: [(); 4] = [(); offset_of!(RDes, rdes1)];
287const _: [(); 8] = [(); offset_of!(RDes, buf_addr)];
288const _: [(); 12] = [(); offset_of!(RDes, next_desc)];
289
290const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); align_of::<DmaBuffers<TX_DESC_COUNT>>()];
291const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); align_of::<DmaBuffers<RX_DESC_COUNT>>()];
292const _: [(); DMA_DESCRIPTOR_ALIGN] = [(); align_of::<StaticDmaResources>()];
293
294impl RDes {
295    /// Creates a zero-initialized descriptor value suitable for static storage.
296    pub const fn new_zeroed() -> Self {
297        Self {
298            rdes0: VolatileCell::new(0),
299            rdes1: VolatileCell::new(0),
300            buf_addr: VolatileCell::new(0),
301            next_desc: VolatileCell::new(0),
302        }
303    }
304
305    /// Sets the software-visible ownership bit.
306    pub fn set_owned_by(&self, owner: OwnedBy) {
307        let value = match owner {
308            OwnedBy::Cpu => self.rdes0.get() & !RDES0_OWN,
309            OwnedBy::Dma => self.rdes0.get() | RDES0_OWN,
310        };
311        self.rdes0.set(value);
312    }
313
314    /// Returns the current descriptor owner according to the ownership bit.
315    pub fn owned_by(&self) -> OwnedBy {
316        if self.rdes0.get() & RDES0_OWN != 0 {
317            OwnedBy::Dma
318        } else {
319            OwnedBy::Cpu
320        }
321    }
322
323    /// Extracts the hardware-reported frame length from `RDES0`.
324    pub fn frame_len(&self) -> usize {
325        ((self.rdes0.get() & RDES0_FL_MASK) >> RDES0_FL_SHIFT) as usize
326    }
327
328    /// Returns `true` when the descriptor marks both first and last segment.
329    pub fn is_complete_frame(&self) -> bool {
330        let status = self.rdes0.get();
331        status & RDES0_FS != 0 && status & RDES0_LS != 0
332    }
333
334    fn set_buffer_addr(&self, addr: *const u8) {
335        self.buf_addr.set(addr as usize as u32);
336    }
337
338    fn set_next_desc(&self, addr: *const RDes) {
339        self.next_desc.set(addr as usize as u32);
340    }
341
342    fn configure_buffer(&self, len: usize) {
343        self.rdes1
344            .set(((len as u32) & RDES1_BUF1_SIZE_MASK) | RDES1_CHAINED);
345    }
346}
347
348const fn zeroed_tx_desc_array() -> [TDes; TX_DESC_COUNT] {
349    [const { TDes::new_zeroed() }; TX_DESC_COUNT]
350}
351
352const fn zeroed_rx_desc_array() -> [RDes; RX_DESC_COUNT] {
353    [const { RDes::new_zeroed() }; RX_DESC_COUNT]
354}
355
356/// Builds a zeroed TX descriptor array for stack allocation and tests.
357pub fn zeroed_tx_descriptors() -> [TDes; TX_DESC_COUNT] {
358    zeroed_tx_desc_array()
359}
360
361/// Builds a zeroed RX descriptor array for stack allocation and tests.
362pub fn zeroed_rx_descriptors() -> [RDes; RX_DESC_COUNT] {
363    zeroed_rx_desc_array()
364}
365
366/// TX descriptor ring plus its backing packet buffers.
367pub struct TDesRing<'a> {
368    descriptors: &'a mut [TDes],
369    buffers: &'a mut [[u8; BUF_SIZE]],
370    index: usize,
371    stats: TxRingStats,
372}
373
374impl<'a> TDesRing<'a> {
375    /// Builds and initializes a TX descriptor ring.
376    pub fn new(descriptors: &'a mut [TDes], buffers: &'a mut [[u8; BUF_SIZE]]) -> Self {
377        assert!(
378            !descriptors.is_empty(),
379            "TX descriptor ring must not be empty"
380        );
381        assert_eq!(
382            descriptors.len(),
383            buffers.len(),
384            "TX descriptors and buffers must have identical lengths"
385        );
386
387        let mut ring = Self {
388            descriptors,
389            buffers,
390            index: 0,
391            stats: TxRingStats::default(),
392        };
393        ring.reset();
394        ring
395    }
396
397    /// Rebuilds the ring links and returns all descriptors to CPU ownership.
398    ///
399    /// **Does not** program the DMA `TX_DESC_LIST` peripheral register —
400    /// that must happen separately via [`Dma::set_descriptor_lists`] AFTER
401    /// [`Dma::dma_reset`]. Writing it from here at construction time hangs
402    /// the AHB bus when the EMAC has not yet completed reset (intermittent,
403    /// raced ~10 % of warm reboots before the split — see 2026-04-27 cold-
404    /// boot stress notes).
405    ///
406    /// Per-descriptor cache writeback is intentionally moved out of this
407    /// loop into `flush_all` so that the **first** ring construction (which
408    /// runs before the L1/L2 cache writeback ROM helper is reachable —
409    /// 2026-04-27 stress runs reproduced an AHB hang inside the cache ROM
410    /// call here when invoked too early after warm reboot) does not race.
411    /// Initial construction relies on `mac_init -> Dma::dma_reset` having
412    /// already ensured DMA is idle, so the descriptors only need to become
413    /// DMA-visible once `start()` flips the ring on; that is what the
414    /// `flush_all` call inside `Ethernet::reset_dma` covers.
415    pub fn reset(&mut self) {
416        let desc_len = self.descriptors.len();
417        for i in 0..desc_len {
418            let desc = &self.descriptors[i];
419            desc.tdes0.set(0);
420            desc.tdes1.set(0);
421            desc.set_chained();
422            desc.set_buffer_addr(self.buffers[i].as_ptr());
423            let next = if i + 1 == desc_len {
424                &self.descriptors[0]
425            } else {
426                &self.descriptors[i + 1]
427            };
428            desc.set_next_desc(next as *const TDes);
429            desc.set_owned_by(OwnedBy::Cpu);
430        }
431        self.index = 0;
432    }
433
434    /// Pushes every descriptor's CPU-side state out to DMA-visible memory.
435    /// Call this **after** `Dma::dma_reset` has put the engine into a known
436    /// state — invoking the cache ROM helper while the EMAC carries stale
437    /// DMA state from a previous binary intermittently hangs the AHB bus.
438    pub fn flush_all(&self) {
439        for desc in self.descriptors.iter() {
440            Dma::flush_descriptor(desc);
441        }
442    }
443
444    /// Returns the descriptor base pointer (used by the DMA programming step).
445    pub fn descriptors_ptr(&self) -> *const TDes {
446        self.descriptors.as_ptr()
447    }
448
449    /// Copies `frame` into the current TX buffer and hands the descriptor to DMA.
450    pub fn transmit(&mut self, frame: &[u8]) -> Result<(), DescriptorError> {
451        if frame.len() > BUF_SIZE {
452            return Err(DescriptorError::BufferTooLarge(BufferTooLarge::Frame {
453                len: frame.len(),
454                max: BUF_SIZE,
455            }));
456        }
457
458        let desc = &self.descriptors[self.index];
459        Dma::invalidate_descriptor(desc);
460        if desc.owned_by() != OwnedBy::Cpu {
461            self.stats.ring_full_events = self.stats.ring_full_events.saturating_add(1);
462            return Err(DescriptorError::RingFull);
463        }
464
465        self.buffers[self.index][..frame.len()].copy_from_slice(frame);
466        desc.set_len_and_flags(frame.len());
467        Dma::flush_buffer(&self.buffers[self.index]);
468        Dma::flush_descriptor(desc);
469        fence(Ordering::Release);
470        desc.set_owned_by(OwnedBy::Dma);
471        Dma::flush_descriptor(desc);
472        Dma::demand_tx_poll();
473
474        // Debug snapshot: capture descriptor + buffer address + first 14 bytes.
475        #[cfg(target_arch = "riscv32")]
476        {
477            use core::sync::atomic::Ordering::Relaxed;
478            TX_LAST_DESC_ADDR
479                .store(desc as *const _ as usize as u32, Relaxed);
480            TX_LAST_BUF_ADDR
481                .store(self.buffers[self.index].as_ptr() as usize as u32, Relaxed);
482            TX_LAST_TDES0.store(desc.tdes0.get(), Relaxed);
483            TX_LAST_TDES1.store(desc.tdes1.get(), Relaxed);
484        }
485
486        self.index = (self.index + 1) % self.descriptors.len();
487        self.stats.transmitted_frames = self.stats.transmitted_frames.saturating_add(1);
488        Ok(())
489    }
490
491    /// Returns `true` when the current TX descriptor is CPU-owned.
492    pub fn has_capacity(&self) -> bool {
493        let desc = &self.descriptors[self.index];
494        Dma::invalidate_descriptor(desc);
495        desc.owned_by() == OwnedBy::Cpu
496    }
497
498    /// Returns a snapshot of cumulative TX statistics.
499    pub fn stats(&self) -> TxRingStats {
500        self.stats
501    }
502
503    /// Test/fuzz helper that returns the current ring cursor.
504    #[cfg(any(test, feature = "fuzzing"))]
505    #[doc(hidden)]
506    pub fn fuzz_current_index(&self) -> usize {
507        self.index
508    }
509}
510
511/// RX descriptor ring plus its backing packet buffers.
512pub struct RDesRing<'a> {
513    descriptors: &'a mut [RDes],
514    buffers: &'a mut [[u8; BUF_SIZE]],
515    index: usize,
516    stats: RxRingStats,
517}
518
519impl<'a> RDesRing<'a> {
520    /// Builds and initializes an RX descriptor ring.
521    pub fn new(descriptors: &'a mut [RDes], buffers: &'a mut [[u8; BUF_SIZE]]) -> Self {
522        assert!(
523            !descriptors.is_empty(),
524            "RX descriptor ring must not be empty"
525        );
526        assert_eq!(
527            descriptors.len(),
528            buffers.len(),
529            "RX descriptors and buffers must have identical lengths"
530        );
531
532        let mut ring = Self {
533            descriptors,
534            buffers,
535            index: 0,
536            stats: RxRingStats::default(),
537        };
538        ring.reset();
539        ring
540    }
541
542    /// Rebuilds the ring links and returns all descriptors to DMA ownership.
543    ///
544    /// **Does not** program the DMA `RX_DESC_LIST` peripheral register —
545    /// that must happen separately via [`Dma::set_descriptor_lists`] AFTER
546    /// [`Dma::dma_reset`]. See `TDesRing::reset` for the rationale, plus
547    /// the explanation of why the cache writeback loop is split into a
548    /// separate `flush_all`.
549    pub fn reset(&mut self) {
550        let desc_len = self.descriptors.len();
551        for i in 0..desc_len {
552            let desc = &self.descriptors[i];
553            desc.rdes0.set(0);
554            desc.configure_buffer(BUF_SIZE);
555            desc.set_buffer_addr(self.buffers[i].as_ptr());
556            let next = if i + 1 == desc_len {
557                &self.descriptors[0]
558            } else {
559                &self.descriptors[i + 1]
560            };
561            desc.set_next_desc(next as *const RDes);
562            desc.set_owned_by(OwnedBy::Dma);
563        }
564        self.index = 0;
565
566        // Snapshot what CPU just wrote into descriptor[0] so external debug
567        // code can compare CPU-view vs memory-view (peek_rdes after invalidate).
568        // If snapshot != peek, the cache writeback path is broken.
569        #[cfg(target_arch = "riscv32")]
570        {
571            let d = &self.descriptors[0];
572            CPU_DESC0_SNAPSHOT[0].store(d.rdes0.get(), core::sync::atomic::Ordering::Relaxed);
573            CPU_DESC0_SNAPSHOT[1].store(d.rdes1.get(), core::sync::atomic::Ordering::Relaxed);
574            CPU_DESC0_SNAPSHOT[2].store(d.buf_addr.get(), core::sync::atomic::Ordering::Relaxed);
575            CPU_DESC0_SNAPSHOT[3].store(d.next_desc.get(), core::sync::atomic::Ordering::Relaxed);
576        }
577    }
578
579    /// Returns the descriptor base pointer (used by the DMA programming step).
580    pub fn descriptors_ptr(&self) -> *const RDes {
581        self.descriptors.as_ptr()
582    }
583
584    /// Pushes every descriptor's CPU-side state out to DMA-visible memory.
585    /// See `TDesRing::flush_all` for the cold-boot rationale.
586    pub fn flush_all(&self) {
587        for desc in self.descriptors.iter() {
588            Dma::flush_descriptor(desc);
589        }
590    }
591
592    /// Returns the current received frame when DMA has completed one.
593    pub fn receive(&mut self) -> Option<(usize, &[u8])> {
594        let desc = &self.descriptors[self.index];
595        Dma::invalidate_descriptor(desc);
596        if desc.owned_by() != OwnedBy::Cpu {
597            return None;
598        }
599
600        let status = desc.rdes0.get();
601        RX_LAST_RDES0.store(status, Ordering::Relaxed);
602        if status & RDES0_ES != 0 || !desc.is_complete_frame() {
603            self.stats.error_frames = self.stats.error_frames.saturating_add(1);
604            RX_ERROR_FRAMES_TOTAL.fetch_add(1, Ordering::Relaxed);
605            self.recycle_current();
606            return None;
607        }
608
609        let len = desc.frame_len();
610        if len != 0 && len < MIN_RX_FRAME_SIZE {
611            self.stats.runt_frames = self.stats.runt_frames.saturating_add(1);
612            RX_RUNT_FRAMES_TOTAL.fetch_add(1, Ordering::Relaxed);
613            self.recycle_current();
614            return None;
615        }
616
617        if len > BUF_SIZE {
618            self.stats.oversized_frames = self.stats.oversized_frames.saturating_add(1);
619            RX_OVERSIZED_FRAMES_TOTAL.fetch_add(1, Ordering::Relaxed);
620            self.recycle_current();
621            return None;
622        }
623
624        Dma::invalidate_buffer_prefix(&self.buffers[self.index], len);
625        fence(Ordering::Acquire);
626        self.stats.received_frames = self.stats.received_frames.saturating_add(1);
627        RX_LAST_FRAME_LEN.store(len as u32, Ordering::Relaxed);
628        if len >= 200 {
629            RX_LARGE_FRAMES.fetch_add(1, Ordering::Relaxed);
630        }
631        Some((len, &self.buffers[self.index][..len]))
632    }
633
634    /// Releases the current RX descriptor back to DMA ownership.
635    pub fn pop(&mut self) {
636        self.recycle_current();
637    }
638
639    fn recycle_current(&mut self) {
640        let desc = &self.descriptors[self.index];
641        desc.rdes0.set(RDES0_OWN);
642        Dma::flush_descriptor(desc);
643        self.index = (self.index + 1) % self.descriptors.len();
644    }
645
646    /// Returns `true` when the current RX descriptor contains a CPU-owned frame.
647    pub fn has_packet(&self) -> bool {
648        let desc = &self.descriptors[self.index];
649        Dma::invalidate_descriptor(desc);
650        desc.owned_by() == OwnedBy::Cpu
651    }
652
653    /// Reinitializes the RX ring after DMA overflow or RX stopped conditions.
654    pub fn handle_overflow(&mut self) {
655        self.stats.overflow_resets = self.stats.overflow_resets.saturating_add(1);
656        self.reset();
657        Dma::demand_rx_poll();
658    }
659
660    /// Returns a snapshot of cumulative RX statistics.
661    pub fn stats(&self) -> RxRingStats {
662        self.stats
663    }
664
665    /// Test/fuzz helper that seeds the current RX descriptor with synthetic data.
666    #[cfg(any(test, feature = "fuzzing"))]
667    #[doc(hidden)]
668    pub fn fuzz_seed_current(&mut self, status: u32, owner: OwnedBy, payload: &[u8]) {
669        let index = self.index;
670        let used = payload.len().min(BUF_SIZE);
671        self.buffers[index][..used].copy_from_slice(&payload[..used]);
672        self.descriptors[index].rdes0.set(status);
673        self.descriptors[index].set_owned_by(owner);
674    }
675
676    /// Test/fuzz helper that returns the current ring cursor.
677    #[cfg(any(test, feature = "fuzzing"))]
678    #[doc(hidden)]
679    pub fn fuzz_current_index(&self) -> usize {
680        self.index
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687
688    const STRESS_ITERATIONS: usize = 10_000;
689    const FUZZ_ITERATIONS: usize = 4_096;
690
691    fn tx_fixture() -> ([TDes; TX_DESC_COUNT], [[u8; BUF_SIZE]; TX_DESC_COUNT]) {
692        (zeroed_tx_descriptors(), [[0; BUF_SIZE]; TX_DESC_COUNT])
693    }
694
695    fn rx_fixture() -> ([RDes; RX_DESC_COUNT], [[u8; BUF_SIZE]; RX_DESC_COUNT]) {
696        (zeroed_rx_descriptors(), [[0; BUF_SIZE]; RX_DESC_COUNT])
697    }
698
699    fn advance_lcg(state: &mut u32) -> u32 {
700        *state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
701        *state
702    }
703
704    #[test]
705    fn tx_reset_builds_closed_ring() {
706        let (mut descriptors, mut buffers) = tx_fixture();
707        let ring = TDesRing::new(&mut descriptors, &mut buffers);
708
709        assert_eq!(ring.index, 0);
710        assert_eq!(
711            ring.descriptors[0].next_desc.get(),
712            (&ring.descriptors[1] as *const TDes as usize) as u32
713        );
714        assert_eq!(
715            ring.descriptors[TX_DESC_COUNT - 1].next_desc.get(),
716            (&ring.descriptors[0] as *const TDes as usize) as u32
717        );
718        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Cpu);
719    }
720
721    #[test]
722    fn tx_transmit_copies_payload_and_advances() {
723        let (mut descriptors, mut buffers) = tx_fixture();
724        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
725        let payload = [0xAB; 64];
726
727        ring.transmit(&payload).unwrap();
728
729        assert_eq!(&ring.buffers[0][..payload.len()], &payload);
730        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Dma);
731        assert_eq!(ring.index, 1);
732        assert_eq!(
733            ring.descriptors[0].tdes0.get(),
734            TDES0_OWN | TDES0_FS | TDES0_LS | TDES0_IC | TDES0_CHAINED
735        );
736        assert_eq!(ring.descriptors[0].tdes1.get(), payload.len() as u32);
737    }
738
739    #[test]
740    fn tx_transmit_keeps_descriptor_chained() {
741        let (mut descriptors, mut buffers) = tx_fixture();
742        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
743
744        ring.transmit(&[0xAB; 64]).unwrap();
745
746        assert_ne!(ring.descriptors[0].tdes0.get() & TDES0_CHAINED, 0);
747    }
748
749    #[test]
750    fn tx_transmit_fails_when_ring_is_full() {
751        let (mut descriptors, mut buffers) = tx_fixture();
752        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
753
754        for descriptor in ring.descriptors.iter() {
755            descriptor.set_owned_by(OwnedBy::Dma);
756        }
757
758        let err = ring.transmit(&[1, 2, 3]).unwrap_err();
759        assert_eq!(err, DescriptorError::RingFull);
760        assert_eq!(ring.stats().ring_full_events, 1);
761    }
762
763    #[test]
764    fn tx_transmit_rejects_oversized_frame_without_touching_ring() {
765        let (mut descriptors, mut buffers) = tx_fixture();
766        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
767        let payload = [0xA5; BUF_SIZE + 1];
768
769        let err = ring.transmit(&payload).unwrap_err();
770
771        assert_eq!(
772            err,
773            DescriptorError::BufferTooLarge(BufferTooLarge::Frame {
774                len: BUF_SIZE + 1,
775                max: BUF_SIZE,
776            })
777        );
778        assert_eq!(ring.index, 0);
779        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Cpu);
780        assert_eq!(ring.stats(), TxRingStats::default());
781        assert!(ring.buffers[0].iter().all(|byte| *byte == 0));
782    }
783
784    #[test]
785    fn tx_ring_full_does_not_advance_or_copy_payload() {
786        let (mut descriptors, mut buffers) = tx_fixture();
787        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
788
789        ring.descriptors[0].set_owned_by(OwnedBy::Dma);
790
791        assert_eq!(ring.transmit(&[0x5A; 64]), Err(DescriptorError::RingFull));
792        assert_eq!(ring.index, 0);
793        assert!(ring.buffers[0][..64].iter().all(|byte| *byte == 0));
794        assert_eq!(ring.stats().transmitted_frames, 0);
795    }
796
797    #[test]
798    fn tx_has_capacity_tracks_current_descriptor_owner() {
799        let (mut descriptors, mut buffers) = tx_fixture();
800        let ring = TDesRing::new(&mut descriptors, &mut buffers);
801
802        assert!(ring.has_capacity());
803
804        ring.descriptors[0].set_owned_by(OwnedBy::Dma);
805        assert!(!ring.has_capacity());
806
807        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
808        assert!(ring.has_capacity());
809    }
810
811    #[test]
812    fn tx_transmit_wraps_index_after_last_descriptor() {
813        let (mut descriptors, mut buffers) = tx_fixture();
814        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
815
816        for expected_index in 0..TX_DESC_COUNT {
817            ring.descriptors[expected_index].set_owned_by(OwnedBy::Cpu);
818            ring.transmit(&[expected_index as u8]).unwrap();
819        }
820
821        assert_eq!(ring.index, 0);
822    }
823
824    #[test]
825    fn tx_transmit_accepts_min_and_max_frame_sizes() {
826        let (mut descriptors, mut buffers) = tx_fixture();
827        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
828
829        ring.transmit(&[]).unwrap();
830        ring.descriptors[1].set_owned_by(OwnedBy::Cpu);
831
832        let payload = [0x5A; BUF_SIZE];
833        ring.transmit(&payload).unwrap();
834
835        assert_eq!(ring.descriptors[0].tdes1.get(), 0);
836        assert_eq!(ring.descriptors[1].tdes1.get(), BUF_SIZE as u32);
837        assert_eq!(&ring.buffers[1], &payload);
838        assert_eq!(ring.stats().transmitted_frames, 2);
839    }
840
841    #[test]
842    fn tx_transmit_recovers_after_descriptor_returns_to_cpu() {
843        let (mut descriptors, mut buffers) = tx_fixture();
844        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
845
846        for descriptor in ring.descriptors.iter() {
847            descriptor.set_owned_by(OwnedBy::Dma);
848        }
849
850        assert_eq!(ring.transmit(&[0xA5; 64]), Err(DescriptorError::RingFull));
851
852        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
853        ring.transmit(&[0x5A; 64]).unwrap();
854
855        assert_eq!(ring.index, 1);
856        assert_eq!(ring.stats().ring_full_events, 1);
857        assert_eq!(ring.stats().transmitted_frames, 1);
858    }
859
860    #[test]
861    fn tx_transmit_survives_ten_thousand_cycles() {
862        let (mut descriptors, mut buffers) = tx_fixture();
863        let mut ring = TDesRing::new(&mut descriptors, &mut buffers);
864        let mut payload = [0u8; BUF_SIZE];
865
866        for cycle in 0..STRESS_ITERATIONS {
867            let slot = ring.index;
868            let len = if cycle % 17 == 0 {
869                0
870            } else {
871                MIN_RX_FRAME_SIZE + (cycle % 257)
872            };
873
874            for (offset, byte) in payload[..len].iter_mut().enumerate() {
875                *byte = cycle.wrapping_add(offset) as u8;
876            }
877
878            ring.transmit(&payload[..len]).unwrap();
879
880            assert_eq!(ring.descriptors[slot].tdes1.get(), len as u32);
881            assert_eq!(&ring.buffers[slot][..len], &payload[..len]);
882
883            ring.descriptors[slot].set_owned_by(OwnedBy::Cpu);
884        }
885
886        assert_eq!(ring.index, STRESS_ITERATIONS % TX_DESC_COUNT);
887        assert_eq!(ring.stats().transmitted_frames, STRESS_ITERATIONS as u32);
888        assert_eq!(ring.stats().ring_full_events, 0);
889    }
890
891    #[test]
892    fn rx_receive_reads_frame_and_pop_returns_it_to_dma() {
893        let (mut descriptors, mut buffers) = rx_fixture();
894        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
895        let payload = [0x11; MIN_RX_FRAME_SIZE];
896
897        ring.buffers[0][..payload.len()].copy_from_slice(&payload);
898        ring.descriptors[0]
899            .rdes0
900            .set(RDES0_FS | RDES0_LS | ((payload.len() as u32) << RDES0_FL_SHIFT));
901        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
902
903        {
904            let (len, packet) = ring.receive().unwrap();
905            assert_eq!(len, payload.len());
906            assert_eq!(packet, &payload);
907        }
908
909        ring.pop();
910        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Dma);
911        assert_eq!(ring.index, 1);
912        assert_eq!(ring.stats().received_frames, 1);
913    }
914
915    #[test]
916    fn rx_receive_valid_frame_waits_for_explicit_pop() {
917        let (mut descriptors, mut buffers) = rx_fixture();
918        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
919        let payload = [0x22; MIN_RX_FRAME_SIZE];
920
921        ring.buffers[0][..payload.len()].copy_from_slice(&payload);
922        ring.descriptors[0]
923            .rdes0
924            .set(RDES0_FS | RDES0_LS | ((payload.len() as u32) << RDES0_FL_SHIFT));
925        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
926
927        let (len, frame) = ring.receive().unwrap();
928
929        assert_eq!(len, payload.len());
930        assert_eq!(frame, &payload);
931        assert_eq!(ring.index, 0);
932        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Cpu);
933    }
934
935    #[test]
936    fn rx_receive_returns_none_for_dma_owned_descriptor() {
937        let (mut descriptors, mut buffers) = rx_fixture();
938        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
939
940        assert!(ring.receive().is_none());
941    }
942
943    #[test]
944    fn rx_has_packet_tracks_current_descriptor_owner() {
945        let (mut descriptors, mut buffers) = rx_fixture();
946        let ring = RDesRing::new(&mut descriptors, &mut buffers);
947
948        assert!(!ring.has_packet());
949
950        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
951        assert!(ring.has_packet());
952
953        ring.descriptors[0].set_owned_by(OwnedBy::Dma);
954        assert!(!ring.has_packet());
955    }
956
957    #[test]
958    fn rx_receive_wraps_index_after_pop() {
959        let (mut descriptors, mut buffers) = rx_fixture();
960        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
961
962        ring.index = RX_DESC_COUNT - 1;
963        ring.descriptors[RX_DESC_COUNT - 1]
964            .rdes0
965            .set(RDES0_FS | RDES0_LS);
966        ring.descriptors[RX_DESC_COUNT - 1].set_owned_by(OwnedBy::Cpu);
967
968        let _ = ring.receive().unwrap();
969        ring.pop();
970
971        assert_eq!(ring.index, 0);
972    }
973
974    #[test]
975    fn rx_receive_skips_error_frame_and_recycles_descriptor() {
976        let (mut descriptors, mut buffers) = rx_fixture();
977        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
978
979        ring.descriptors[0]
980            .rdes0
981            .set(RDES0_ES | RDES0_FS | RDES0_LS | (32 << RDES0_FL_SHIFT));
982        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
983
984        assert!(ring.receive().is_none());
985        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Dma);
986        assert_eq!(ring.index, 1);
987        assert_eq!(ring.stats().error_frames, 1);
988    }
989
990    #[test]
991    fn rx_receive_recycles_incomplete_frame() {
992        let (mut descriptors, mut buffers) = rx_fixture();
993        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
994
995        ring.descriptors[0]
996            .rdes0
997            .set(RDES0_FS | ((MIN_RX_FRAME_SIZE as u32) << RDES0_FL_SHIFT));
998        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
999
1000        assert!(ring.receive().is_none());
1001        assert_eq!(ring.index, 1);
1002        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Dma);
1003        assert_eq!(ring.stats().error_frames, 1);
1004    }
1005
1006    #[test]
1007    fn rx_receive_handles_zero_length_frame() {
1008        let (mut descriptors, mut buffers) = rx_fixture();
1009        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1010
1011        ring.descriptors[0].rdes0.set(RDES0_FS | RDES0_LS);
1012        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
1013
1014        let (len, packet) = ring.receive().unwrap();
1015        assert_eq!(len, 0);
1016        assert!(packet.is_empty());
1017    }
1018
1019    #[test]
1020    fn rx_receive_accepts_max_buffer_sized_frame() {
1021        let (mut descriptors, mut buffers) = rx_fixture();
1022        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1023        let payload = [0x7E; BUF_SIZE];
1024
1025        ring.buffers[0].copy_from_slice(&payload);
1026        ring.descriptors[0]
1027            .rdes0
1028            .set(RDES0_FS | RDES0_LS | ((BUF_SIZE as u32) << RDES0_FL_SHIFT));
1029        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
1030
1031        let (len, packet) = ring.receive().unwrap();
1032
1033        assert_eq!(len, BUF_SIZE);
1034        assert_eq!(packet, &payload);
1035    }
1036
1037    #[test]
1038    fn rx_error_flag_takes_precedence_over_runt_classification() {
1039        let (mut descriptors, mut buffers) = rx_fixture();
1040        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1041
1042        ring.descriptors[0].rdes0.set(
1043            RDES0_ES | RDES0_FS | RDES0_LS | (((MIN_RX_FRAME_SIZE - 1) as u32) << RDES0_FL_SHIFT),
1044        );
1045        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
1046
1047        assert!(ring.receive().is_none());
1048        assert_eq!(ring.stats().error_frames, 1);
1049        assert_eq!(ring.stats().runt_frames, 0);
1050        assert_eq!(ring.index, 1);
1051    }
1052
1053    #[test]
1054    fn rx_receive_handles_consecutive_frames() {
1055        let (mut descriptors, mut buffers) = rx_fixture();
1056        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1057        let payload0 = [0xAA; MIN_RX_FRAME_SIZE];
1058        let payload1 = [0xCC; 96];
1059
1060        ring.buffers[0][..payload0.len()].copy_from_slice(&payload0);
1061        ring.buffers[1][..payload1.len()].copy_from_slice(&payload1);
1062        ring.descriptors[0]
1063            .rdes0
1064            .set(RDES0_FS | RDES0_LS | ((payload0.len() as u32) << RDES0_FL_SHIFT));
1065        ring.descriptors[1]
1066            .rdes0
1067            .set(RDES0_FS | RDES0_LS | ((payload1.len() as u32) << RDES0_FL_SHIFT));
1068        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
1069        ring.descriptors[1].set_owned_by(OwnedBy::Cpu);
1070
1071        let (len0, frame0) = ring.receive().unwrap();
1072        assert_eq!(len0, payload0.len());
1073        assert_eq!(frame0, &payload0);
1074        ring.pop();
1075
1076        let (len1, frame1) = ring.receive().unwrap();
1077        assert_eq!(len1, payload1.len());
1078        assert_eq!(frame1, &payload1);
1079        ring.pop();
1080
1081        assert_eq!(ring.index, 2);
1082        assert_eq!(ring.stats().received_frames, 2);
1083    }
1084
1085    #[test]
1086    fn rx_receive_drops_runt_frame_and_counts_it() {
1087        let (mut descriptors, mut buffers) = rx_fixture();
1088        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1089
1090        ring.descriptors[0]
1091            .rdes0
1092            .set(RDES0_FS | RDES0_LS | (((MIN_RX_FRAME_SIZE - 1) as u32) << RDES0_FL_SHIFT));
1093        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
1094
1095        assert!(ring.receive().is_none());
1096        assert_eq!(ring.index, 1);
1097        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Dma);
1098        assert_eq!(ring.stats().runt_frames, 1);
1099    }
1100
1101    #[test]
1102    fn rx_receive_drops_oversized_frame_without_touching_buffer() {
1103        let (mut descriptors, mut buffers) = rx_fixture();
1104        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1105
1106        ring.descriptors[0]
1107            .rdes0
1108            .set(RDES0_FS | RDES0_LS | (((BUF_SIZE + 1) as u32) << RDES0_FL_SHIFT));
1109        ring.descriptors[0].set_owned_by(OwnedBy::Cpu);
1110
1111        assert!(ring.receive().is_none());
1112        assert_eq!(ring.index, 1);
1113        assert_eq!(ring.descriptors[0].owned_by(), OwnedBy::Dma);
1114        assert_eq!(ring.stats().oversized_frames, 1);
1115    }
1116
1117    #[test]
1118    fn rx_handle_overflow_rebuilds_ring_and_restarts_from_zero() {
1119        let (mut descriptors, mut buffers) = rx_fixture();
1120        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1121
1122        ring.index = 2;
1123        ring.descriptors[2].set_owned_by(OwnedBy::Cpu);
1124        ring.descriptors[2].rdes0.set(0);
1125
1126        ring.handle_overflow();
1127
1128        assert_eq!(ring.index, 0);
1129        assert_eq!(ring.stats().overflow_resets, 1);
1130        for descriptor in ring.descriptors.iter() {
1131            assert_eq!(descriptor.owned_by(), OwnedBy::Dma);
1132            assert_eq!(
1133                descriptor.rdes1.get() & RDES1_BUF1_SIZE_MASK,
1134                BUF_SIZE as u32
1135            );
1136            assert_ne!(descriptor.rdes1.get() & RDES1_CHAINED, 0);
1137        }
1138    }
1139
1140    #[test]
1141    fn rx_handle_overflow_preserves_ring_invariants_under_repetition() {
1142        let (mut descriptors, mut buffers) = rx_fixture();
1143        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1144        let mut rng = 0x51F1_AA77u32;
1145
1146        for expected_resets in 1..=FUZZ_ITERATIONS {
1147            let random = advance_lcg(&mut rng) as usize;
1148            ring.index = random % RX_DESC_COUNT;
1149
1150            for (slot, descriptor) in ring.descriptors.iter().enumerate() {
1151                descriptor.set_owned_by(if (random + slot) & 1 == 0 {
1152                    OwnedBy::Cpu
1153                } else {
1154                    OwnedBy::Dma
1155                });
1156                descriptor
1157                    .rdes0
1158                    .set(((random + slot) as u32) << RDES0_FL_SHIFT);
1159            }
1160
1161            ring.handle_overflow();
1162
1163            assert_eq!(ring.index, 0);
1164            assert_eq!(ring.stats().overflow_resets, expected_resets as u32);
1165            assert_eq!(regs::read(regs::dma::RX_POLL_DEMAND), 1);
1166
1167            for slot in 0..RX_DESC_COUNT {
1168                let descriptor = &ring.descriptors[slot];
1169                assert_eq!(descriptor.owned_by(), OwnedBy::Dma);
1170                assert_eq!(
1171                    descriptor.rdes1.get() & RDES1_BUF1_SIZE_MASK,
1172                    BUF_SIZE as u32
1173                );
1174                assert_ne!(descriptor.rdes1.get() & RDES1_CHAINED, 0);
1175                let expected_next = if slot + 1 == RX_DESC_COUNT {
1176                    &ring.descriptors[0] as *const RDes as usize as u32
1177                } else {
1178                    &ring.descriptors[slot + 1] as *const RDes as usize as u32
1179                };
1180                assert_eq!(descriptor.next_desc.get(), expected_next);
1181            }
1182        }
1183    }
1184
1185    #[test]
1186    fn rx_receive_survives_ten_thousand_cycles() {
1187        let (mut descriptors, mut buffers) = rx_fixture();
1188        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1189        let mut payload = [0u8; BUF_SIZE];
1190
1191        for cycle in 0..STRESS_ITERATIONS {
1192            let slot = ring.index;
1193            let len = MIN_RX_FRAME_SIZE + (cycle % 321);
1194
1195            for (offset, byte) in payload[..len].iter_mut().enumerate() {
1196                *byte = cycle.wrapping_mul(3).wrapping_add(offset) as u8;
1197            }
1198
1199            ring.buffers[slot][..len].copy_from_slice(&payload[..len]);
1200            ring.descriptors[slot]
1201                .rdes0
1202                .set(RDES0_FS | RDES0_LS | ((len as u32) << RDES0_FL_SHIFT));
1203            ring.descriptors[slot].set_owned_by(OwnedBy::Cpu);
1204
1205            let (received_len, frame) = ring.receive().unwrap();
1206            assert_eq!(received_len, len);
1207            assert_eq!(frame, &payload[..len]);
1208
1209            ring.pop();
1210            assert_eq!(ring.descriptors[slot].owned_by(), OwnedBy::Dma);
1211        }
1212
1213        assert_eq!(ring.index, STRESS_ITERATIONS % RX_DESC_COUNT);
1214        assert_eq!(ring.stats().received_frames, STRESS_ITERATIONS as u32);
1215        assert_eq!(ring.stats().error_frames, 0);
1216        assert_eq!(ring.stats().runt_frames, 0);
1217        assert_eq!(ring.stats().oversized_frames, 0);
1218    }
1219
1220    #[test]
1221    fn rx_receive_fuzz_preserves_ring_invariants() {
1222        let (mut descriptors, mut buffers) = rx_fixture();
1223        let mut ring = RDesRing::new(&mut descriptors, &mut buffers);
1224        let mut rng = 0xC0DE_1234u32;
1225
1226        let mut expected_received = 0u32;
1227        let mut expected_errors = 0u32;
1228        let mut expected_runts = 0u32;
1229        let mut expected_oversized = 0u32;
1230
1231        for _ in 0..FUZZ_ITERATIONS {
1232            let slot = ring.index;
1233            let random = advance_lcg(&mut rng);
1234            let len = (random as usize) % (BUF_SIZE + 256);
1235            let owner_dma = random & 0x1 != 0;
1236            let set_error = random & 0x2 != 0;
1237            let set_fs = random & 0x4 != 0;
1238            let set_ls = random & 0x8 != 0;
1239
1240            if !owner_dma {
1241                let fill_len = len.min(BUF_SIZE);
1242                for (offset, byte) in ring.buffers[slot][..fill_len].iter_mut().enumerate() {
1243                    *byte = random.wrapping_add(offset as u32) as u8;
1244                }
1245            }
1246
1247            let mut status = ((len as u32) << RDES0_FL_SHIFT) & RDES0_FL_MASK;
1248            if set_error {
1249                status |= RDES0_ES;
1250            }
1251            if set_fs {
1252                status |= RDES0_FS;
1253            }
1254            if set_ls {
1255                status |= RDES0_LS;
1256            }
1257
1258            ring.descriptors[slot].rdes0.set(status);
1259            ring.descriptors[slot].set_owned_by(if owner_dma {
1260                OwnedBy::Dma
1261            } else {
1262                OwnedBy::Cpu
1263            });
1264
1265            let expected = if owner_dma {
1266                None
1267            } else if set_error || !(set_fs && set_ls) {
1268                expected_errors = expected_errors.saturating_add(1);
1269                Some(("error", None))
1270            } else if len != 0 && len < MIN_RX_FRAME_SIZE {
1271                expected_runts = expected_runts.saturating_add(1);
1272                Some(("runt", None))
1273            } else if len > BUF_SIZE {
1274                expected_oversized = expected_oversized.saturating_add(1);
1275                Some(("oversized", None))
1276            } else {
1277                expected_received = expected_received.saturating_add(1);
1278                Some(("ok", Some(len)))
1279            };
1280
1281            let start_index = ring.index;
1282            match (expected, ring.receive()) {
1283                (None, None) => {
1284                    assert_eq!(ring.index, start_index);
1285                    assert_eq!(ring.descriptors[slot].owned_by(), OwnedBy::Dma);
1286                }
1287                (Some((_kind, None)), None) => {
1288                    assert_eq!(ring.index, (start_index + 1) % RX_DESC_COUNT);
1289                    let recycled_slot = start_index;
1290                    assert_eq!(ring.descriptors[recycled_slot].owned_by(), OwnedBy::Dma);
1291                }
1292                (Some(("ok", Some(expected_len))), Some((len, frame))) => {
1293                    assert_eq!(len, expected_len);
1294                    assert_eq!(frame.len(), expected_len);
1295                    assert_eq!(ring.index, start_index);
1296                    ring.pop();
1297                    assert_eq!(ring.index, (start_index + 1) % RX_DESC_COUNT);
1298                }
1299                other => panic!("unexpected fuzz outcome: {other:?}"),
1300            }
1301
1302            let stats = ring.stats();
1303            assert_eq!(stats.received_frames, expected_received);
1304            assert_eq!(stats.error_frames, expected_errors);
1305            assert_eq!(stats.runt_frames, expected_runts);
1306            assert_eq!(stats.oversized_frames, expected_oversized);
1307            assert!(ring.index < RX_DESC_COUNT);
1308        }
1309    }
1310
1311    #[test]
1312    fn descriptor_layout_matches_checklist() {
1313        assert_eq!(size_of::<TDes>(), 128);
1314        assert_eq!(align_of::<TDes>(), 128);
1315        assert_eq!(size_of::<RDes>(), 128);
1316        assert_eq!(align_of::<RDes>(), 128);
1317        assert_eq!(offset_of!(TDes, tdes0), 0);
1318        assert_eq!(offset_of!(TDes, tdes1), 4);
1319        assert_eq!(offset_of!(TDes, buf_addr), 8);
1320        assert_eq!(offset_of!(TDes, next_desc), 12);
1321        assert_eq!(offset_of!(RDes, rdes0), 0);
1322        assert_eq!(offset_of!(RDes, rdes1), 4);
1323        assert_eq!(offset_of!(RDes, buf_addr), 8);
1324        assert_eq!(offset_of!(RDes, next_desc), 12);
1325    }
1326
1327    #[test]
1328    fn descriptor_bit_constants_match_idf_layout() {
1329        assert_eq!(TDES0_OWN, 1 << 31);
1330        assert_eq!(TDES0_IC, 1 << 30);
1331        assert_eq!(TDES0_LS, 1 << 29);
1332        assert_eq!(TDES0_FS, 1 << 28);
1333        assert_eq!(RDES0_OWN, 1 << 31);
1334        assert_eq!(RDES0_ES, 1 << 15);
1335        assert_eq!(RDES0_FS, 1 << 9);
1336        assert_eq!(RDES0_LS, 1 << 8);
1337        assert_eq!(RDES0_FL_MASK, 0x3fff << 16);
1338        assert_eq!(RDES1_CHAINED, 1 << 14);
1339        assert_eq!(RDES1_BUF1_SIZE_MASK, 0x1fff);
1340    }
1341
1342    #[test]
1343    fn dma_buffer_wrapper_is_cacheline_aligned() {
1344        let buffers = DmaBuffers::<TX_DESC_COUNT>::ZERO;
1345
1346        assert_eq!(align_of::<DmaBuffers<TX_DESC_COUNT>>(), 128);
1347        assert_eq!((buffers.inner.as_ptr() as usize) % 128, 0);
1348    }
1349
1350    #[test]
1351    fn static_dma_resources_split_exposes_all_zeroed_dma_storage() {
1352        let mut resources = StaticDmaResources::new();
1353        let (tx_desc, rx_desc, tx_buf, rx_buf) = resources.split();
1354
1355        assert_eq!(tx_desc.len(), TX_DESC_COUNT);
1356        assert_eq!(rx_desc.len(), RX_DESC_COUNT);
1357        assert_eq!(tx_buf.len(), TX_DESC_COUNT);
1358        assert_eq!(rx_buf.len(), RX_DESC_COUNT);
1359        assert!(tx_desc.iter().all(|desc| desc.owned_by() == OwnedBy::Cpu));
1360        assert!(rx_desc.iter().all(|desc| desc.owned_by() == OwnedBy::Cpu));
1361        assert!(tx_buf.iter().flatten().all(|byte| *byte == 0));
1362        assert!(rx_buf.iter().flatten().all(|byte| *byte == 0));
1363    }
1364}