Skip to main content

esp_emac/dma/descriptor/
mod.rs

1// SPDX-License-Identifier: GPL-2.0-or-later OR Apache-2.0
2// Copyright (c) Viacheslav Bocharov <v@baodeep.com> and JetHome (r)
3
4//! TX and RX DMA descriptor structures.
5//!
6//! The crate runs the **enhanced 8-word descriptor layout** (32 bytes
7//! per descriptor) selected by `DMABUSMODE.ATDS = 1`. Words 4-7 carry
8//! the extended status / timestamp fields; the CPU never reads them
9//! today, but they exist in memory so the DMA engine doesn't stomp
10//! adjacent descriptors when chained at a 32-byte stride.
11//!
12//! | Word | TX (TDES)              | RX (RDES)              |
13//! |------|------------------------|------------------------|
14//! | 0    | Status / control       | Status                 |
15//! | 1    | Buffer 1 size + flags  | Buffer 1 size + flags  |
16//! | 2    | Buffer 1 address       | Buffer 1 address       |
17//! | 3    | Next-descriptor addr   | Next-descriptor addr   |
18//! | 4    | Reserved / extended    | Extended status        |
19//! | 5    | Reserved               | Reserved               |
20//! | 6    | Timestamp low          | Timestamp low          |
21//! | 7    | Timestamp high         | Timestamp high         |
22//!
23//! The OWN bit (bit 31 of word 0) governs ownership: when set the DMA
24//! engine owns the descriptor; when clear the CPU may access it.
25//!
26//! The legacy 4-word/16-byte layout (`ATDS = 0`) isn't supported by
27//! this crate — the enhanced layout matches what `ph-esp32-mac` /
28//! ESP-IDF use and is required for the timestamp / IPv4 checksum
29//! offload features even if the crate doesn't currently surface them.
30
31pub mod bits;
32
33use bits::{rdes0, rdes1, tdes0, tdes1};
34
35// =============================================================================
36// VolatileCell
37// =============================================================================
38
39/// Volatile cell wrapper for DMA descriptor fields.
40///
41/// Prevents the compiler from reordering or caching register-like memory
42/// accesses. All reads and writes go through `core::ptr::{read,write}_volatile`.
43#[repr(transparent)]
44pub struct VolatileCell<T: Copy> {
45    value: core::cell::UnsafeCell<T>,
46}
47
48// SAFETY: DMA descriptors are accessed from ISR context and main context.
49// Volatile access + OWN-bit protocol ensures correctness.
50unsafe impl<T: Copy> Sync for VolatileCell<T> {}
51
52impl<T: Copy> VolatileCell<T> {
53    /// Create a new volatile cell with the given initial value.
54    #[inline(always)]
55    pub const fn new(value: T) -> Self {
56        Self {
57            value: core::cell::UnsafeCell::new(value),
58        }
59    }
60
61    /// Read the value (volatile read).
62    #[inline(always)]
63    pub fn get(&self) -> T {
64        // SAFETY: Volatile access to a valid UnsafeCell-backed pointer.
65        unsafe { core::ptr::read_volatile(self.value.get()) }
66    }
67
68    /// Write a value (volatile write).
69    #[inline(always)]
70    pub fn set(&self, value: T) {
71        // SAFETY: Volatile access to a valid UnsafeCell-backed pointer.
72        unsafe { core::ptr::write_volatile(self.value.get(), value) }
73    }
74
75    /// Update the value using a function (read-modify-write).
76    #[inline(always)]
77    pub fn update<F>(&self, f: F)
78    where
79        F: FnOnce(T) -> T,
80    {
81        let old = self.get();
82        self.set(f(old));
83    }
84}
85
86impl<T: Copy + Default> Default for VolatileCell<T> {
87    fn default() -> Self {
88        Self::new(T::default())
89    }
90}
91
92// =============================================================================
93// TX Descriptor
94// =============================================================================
95
96/// TX DMA descriptor — enhanced 8-word layout (32 bytes).
97///
98/// The ESP32 GMAC requires the enhanced descriptor format when
99/// `DMABUSMODE.ATDS = 1` (which is what the IDF / ph-esp32-mac driver
100/// runs with). Reserved fields below are written by the DMA but unused
101/// by the CPU; they exist purely so the descriptor stride is 32 bytes
102/// and the DMA does not stomp adjacent descriptors when chained.
103#[repr(C, align(4))]
104pub struct TxDescriptor {
105    /// TDES0: Status and control bits (OWN, first/last segment, etc.).
106    tdes0: VolatileCell<u32>,
107    /// TDES1: Buffer 1 size and control flags.
108    tdes1: VolatileCell<u32>,
109    /// TDES2: Buffer 1 address.
110    buffer_addr: VolatileCell<u32>,
111    /// TDES3: Next descriptor address (chained mode).
112    next_desc_addr: VolatileCell<u32>,
113    /// TDES4: Reserved (extended status on ESP32-P4 / ATDS-enabled devices).
114    _reserved4: VolatileCell<u32>,
115    /// TDES5: Reserved.
116    _reserved5: VolatileCell<u32>,
117    /// TDES6: Timestamp low (when timestamping is enabled).
118    _ts_low: VolatileCell<u32>,
119    /// TDES7: Timestamp high (when timestamping is enabled).
120    _ts_high: VolatileCell<u32>,
121}
122
123#[allow(dead_code)]
124impl TxDescriptor {
125    /// Descriptor size in bytes (enhanced 8-word layout).
126    pub const SIZE: usize = 32;
127
128    /// Create a new zeroed TX descriptor.
129    #[must_use]
130    pub const fn new() -> Self {
131        Self {
132            tdes0: VolatileCell::new(0),
133            tdes1: VolatileCell::new(0),
134            buffer_addr: VolatileCell::new(0),
135            next_desc_addr: VolatileCell::new(0),
136            _reserved4: VolatileCell::new(0),
137            _reserved5: VolatileCell::new(0),
138            _ts_low: VolatileCell::new(0),
139            _ts_high: VolatileCell::new(0),
140        }
141    }
142
143    /// Initialize descriptor for chained mode.
144    ///
145    /// Sets the buffer pointer, next-descriptor pointer, and the
146    /// `SECOND_ADDR_CHAINED` flag. The descriptor is left CPU-owned.
147    pub fn setup_chained(&self, buffer: *const u8, next_desc: *const TxDescriptor) {
148        self.buffer_addr.set(buffer as u32);
149        self.next_desc_addr.set(next_desc as u32);
150        self.tdes0.set(tdes0::SECOND_ADDR_CHAINED);
151        self.tdes1.set(0);
152    }
153
154    /// Check if DMA owns this descriptor.
155    #[inline(always)]
156    #[must_use]
157    pub fn is_owned(&self) -> bool {
158        (self.tdes0.get() & tdes0::OWN) != 0
159    }
160
161    /// Give ownership to DMA.
162    #[inline(always)]
163    pub fn set_owned(&self) {
164        self.tdes0.update(|v| v | tdes0::OWN);
165    }
166
167    /// Take ownership from DMA.
168    #[inline(always)]
169    pub fn clear_owned(&self) {
170        self.tdes0.update(|v| v & !tdes0::OWN);
171    }
172
173    /// Prepare descriptor for transmission with segment flags.
174    ///
175    /// Sets the buffer length and first/last segment flags.
176    /// Does **not** set the OWN bit — call [`set_owned`](Self::set_owned)
177    /// afterwards to submit to DMA.
178    pub fn prepare(&self, len: usize, first: bool, last: bool) {
179        let mut flags = tdes0::SECOND_ADDR_CHAINED;
180
181        if first {
182            flags |= tdes0::FIRST_SEGMENT;
183        }
184        if last {
185            flags |= tdes0::LAST_SEGMENT | tdes0::INTERRUPT_ON_COMPLETE;
186        }
187
188        self.tdes1.set((len as u32) & tdes1::BUFFER1_SIZE_MASK);
189        self.tdes0.set(flags);
190    }
191
192    /// Prepare and submit to DMA in one operation.
193    pub fn prepare_and_submit(&self, len: usize, first: bool, last: bool) {
194        self.prepare(len, first, last);
195        self.set_owned();
196    }
197
198    /// Check if transmission had errors (error summary bit).
199    #[inline(always)]
200    #[must_use]
201    pub fn has_error(&self) -> bool {
202        (self.tdes0.get() & tdes0::ERR_SUMMARY) != 0
203    }
204
205    /// Get all error flags from TDES0.
206    #[inline(always)]
207    #[must_use]
208    pub fn error_flags(&self) -> u32 {
209        self.tdes0.get() & tdes0::ALL_ERRORS
210    }
211
212    /// Get buffer address (TDES2).
213    #[inline(always)]
214    #[must_use]
215    pub fn buffer_addr(&self) -> u32 {
216        self.buffer_addr.get()
217    }
218
219    /// Get next descriptor address (TDES3, chained mode).
220    #[inline(always)]
221    #[must_use]
222    pub fn next_desc_addr(&self) -> u32 {
223        self.next_desc_addr.get()
224    }
225
226    /// Reset descriptor to initial state, preserving the chain pointer.
227    pub fn reset(&self) {
228        let next = self.next_desc_addr.get();
229        self.tdes0.set(tdes0::SECOND_ADDR_CHAINED);
230        self.tdes1.set(0);
231        self.next_desc_addr.set(next);
232    }
233
234    /// Raw TDES0 value (for debugging / tests).
235    #[inline(always)]
236    #[must_use]
237    pub fn raw_tdes0(&self) -> u32 {
238        self.tdes0.get()
239    }
240
241    /// Raw TDES1 value (for debugging / tests).
242    #[inline(always)]
243    #[must_use]
244    pub fn raw_tdes1(&self) -> u32 {
245        self.tdes1.get()
246    }
247}
248
249impl Default for TxDescriptor {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255// SAFETY: TxDescriptor uses volatile cells for all DMA-accessed fields.
256unsafe impl Sync for TxDescriptor {}
257// SAFETY: TxDescriptor can be sent between threads.
258unsafe impl Send for TxDescriptor {}
259
260// =============================================================================
261// RX Descriptor
262// =============================================================================
263
264/// RX DMA descriptor — enhanced 8-word layout (32 bytes).
265///
266/// See [`TxDescriptor`] for why we run the enhanced layout.
267#[repr(C, align(4))]
268pub struct RxDescriptor {
269    /// RDES0: Status bits (OWN, first/last, frame length, errors).
270    rdes0: VolatileCell<u32>,
271    /// RDES1: Buffer 1 size and control flags.
272    rdes1: VolatileCell<u32>,
273    /// RDES2: Buffer 1 address.
274    buffer_addr: VolatileCell<u32>,
275    /// RDES3: Next descriptor address (chained mode).
276    next_desc_addr: VolatileCell<u32>,
277    /// RDES4: Extended status (when enabled).
278    _ext_status: VolatileCell<u32>,
279    /// RDES5: Reserved.
280    _reserved5: VolatileCell<u32>,
281    /// RDES6: Timestamp low (when timestamping is enabled).
282    _ts_low: VolatileCell<u32>,
283    /// RDES7: Timestamp high (when timestamping is enabled).
284    _ts_high: VolatileCell<u32>,
285}
286
287#[allow(dead_code)]
288impl RxDescriptor {
289    /// Descriptor size in bytes (enhanced 8-word layout).
290    pub const SIZE: usize = 32;
291
292    /// Create a new zeroed RX descriptor. Call [`setup_chained`](Self::setup_chained) before use.
293    #[must_use]
294    pub const fn new() -> Self {
295        Self {
296            rdes0: VolatileCell::new(0),
297            rdes1: VolatileCell::new(0),
298            buffer_addr: VolatileCell::new(0),
299            next_desc_addr: VolatileCell::new(0),
300            _ext_status: VolatileCell::new(0),
301            _reserved5: VolatileCell::new(0),
302            _ts_low: VolatileCell::new(0),
303            _ts_high: VolatileCell::new(0),
304        }
305    }
306
307    /// Configure descriptor in chained mode and give to DMA.
308    ///
309    /// Sets the buffer pointer, buffer size, next-descriptor pointer,
310    /// the `SECOND_ADDR_CHAINED` flag, and the OWN bit.
311    pub fn setup_chained(
312        &self,
313        buffer: *mut u8,
314        buffer_size: usize,
315        next_desc: *const RxDescriptor,
316    ) {
317        self.buffer_addr.set(buffer as u32);
318        self.next_desc_addr.set(next_desc as u32);
319        self.rdes1
320            .set(rdes1::SECOND_ADDR_CHAINED | ((buffer_size as u32) & rdes1::BUFFER1_SIZE_MASK));
321        // Give ownership to DMA.
322        self.rdes0.set(rdes0::OWN);
323    }
324
325    /// Check if DMA owns this descriptor.
326    #[inline(always)]
327    #[must_use]
328    pub fn is_owned(&self) -> bool {
329        (self.rdes0.get() & rdes0::OWN) != 0
330    }
331
332    /// Give ownership to DMA.
333    #[inline(always)]
334    pub fn set_owned(&self) {
335        self.rdes0.set(rdes0::OWN);
336    }
337
338    /// Take ownership from DMA.
339    #[inline(always)]
340    pub fn clear_owned(&self) {
341        self.rdes0.update(|v| v & !rdes0::OWN);
342    }
343
344    /// First descriptor of a frame.
345    #[inline(always)]
346    #[must_use]
347    pub fn is_first(&self) -> bool {
348        (self.rdes0.get() & rdes0::FIRST_DESC) != 0
349    }
350
351    /// Last descriptor of a frame.
352    #[inline(always)]
353    #[must_use]
354    pub fn is_last(&self) -> bool {
355        (self.rdes0.get() & rdes0::LAST_DESC) != 0
356    }
357
358    /// Complete frame in a single descriptor (both first and last).
359    #[inline(always)]
360    #[must_use]
361    pub fn is_complete_frame(&self) -> bool {
362        let status = self.rdes0.get();
363        (status & (rdes0::FIRST_DESC | rdes0::LAST_DESC)) == (rdes0::FIRST_DESC | rdes0::LAST_DESC)
364    }
365
366    /// Check if the error summary bit is set.
367    #[inline(always)]
368    #[must_use]
369    pub fn has_error(&self) -> bool {
370        (self.rdes0.get() & rdes0::ERR_SUMMARY) != 0
371    }
372
373    /// Raw error flags from RDES0.
374    #[inline(always)]
375    #[must_use]
376    pub fn error_flags(&self) -> u32 {
377        self.rdes0.get() & rdes0::ALL_ERRORS
378    }
379
380    /// Frame length including CRC (valid on last descriptor).
381    #[inline(always)]
382    #[must_use]
383    pub fn frame_length(&self) -> usize {
384        ((self.rdes0.get() & rdes0::FRAME_LEN_MASK) >> rdes0::FRAME_LEN_SHIFT) as usize
385    }
386
387    /// Frame length excluding the 4-byte CRC.
388    #[inline(always)]
389    #[must_use]
390    pub fn payload_length(&self) -> usize {
391        self.frame_length().saturating_sub(4)
392    }
393
394    /// Buffer address (RDES2).
395    #[inline(always)]
396    #[must_use]
397    pub fn buffer_addr(&self) -> u32 {
398        self.buffer_addr.get()
399    }
400
401    /// Next descriptor address (RDES3, chained mode).
402    #[inline(always)]
403    #[must_use]
404    pub fn next_desc_addr(&self) -> u32 {
405        self.next_desc_addr.get()
406    }
407
408    /// Configured buffer size from RDES1.
409    #[inline(always)]
410    #[must_use]
411    pub fn buffer_size(&self) -> usize {
412        (self.rdes1.get() & rdes1::BUFFER1_SIZE_MASK) as usize
413    }
414
415    /// Clear status and return the descriptor to DMA for reuse.
416    pub fn recycle(&self) {
417        self.rdes0.set(rdes0::OWN);
418    }
419
420    /// Raw RDES0 value (for debugging / tests).
421    #[inline(always)]
422    #[must_use]
423    pub fn raw_rdes0(&self) -> u32 {
424        self.rdes0.get()
425    }
426
427    /// Raw RDES1 value (for debugging / tests).
428    #[inline(always)]
429    #[must_use]
430    pub fn raw_rdes1(&self) -> u32 {
431        self.rdes1.get()
432    }
433
434    /// Set raw RDES0 value (test only — simulates DMA hardware writes).
435    #[cfg(test)]
436    pub fn set_raw_rdes0(&self, val: u32) {
437        self.rdes0.set(val);
438    }
439}
440
441impl Default for RxDescriptor {
442    fn default() -> Self {
443        Self::new()
444    }
445}
446
447// SAFETY: RxDescriptor uses volatile cells for all DMA-accessed fields.
448unsafe impl Sync for RxDescriptor {}
449// SAFETY: RxDescriptor can be sent between threads.
450unsafe impl Send for RxDescriptor {}
451
452// =============================================================================
453// Tests
454// =============================================================================
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    // =========================================================================
461    // VolatileCell Tests
462    // =========================================================================
463
464    #[test]
465    fn volatile_cell_new() {
466        let cell = VolatileCell::new(42u32);
467        assert_eq!(cell.get(), 42);
468    }
469
470    #[test]
471    fn volatile_cell_get_set() {
472        let cell = VolatileCell::new(0u32);
473        assert_eq!(cell.get(), 0);
474        cell.set(0xDEAD_BEEF);
475        assert_eq!(cell.get(), 0xDEAD_BEEF);
476    }
477
478    #[test]
479    fn volatile_cell_update() {
480        let cell = VolatileCell::new(0x0000_00FFu32);
481        cell.update(|v| v | 0xFF00_0000);
482        assert_eq!(cell.get(), 0xFF00_00FF);
483    }
484
485    #[test]
486    fn volatile_cell_default() {
487        let cell = VolatileCell::<u32>::default();
488        assert_eq!(cell.get(), 0);
489    }
490
491    // =========================================================================
492    // TX Descriptor Layout Tests
493    // =========================================================================
494
495    #[test]
496    fn tx_descriptor_size() {
497        assert_eq!(core::mem::size_of::<TxDescriptor>(), 32);
498        assert_eq!(TxDescriptor::SIZE, core::mem::size_of::<TxDescriptor>());
499    }
500
501    #[test]
502    fn tx_descriptor_alignment() {
503        assert_eq!(core::mem::align_of::<TxDescriptor>(), 4);
504    }
505
506    // =========================================================================
507    // TX Descriptor Ownership Tests
508    // =========================================================================
509
510    #[test]
511    fn tx_descriptor_new_not_owned() {
512        let desc = TxDescriptor::new();
513        assert!(!desc.is_owned());
514    }
515
516    #[test]
517    fn tx_descriptor_is_owned() {
518        let desc = TxDescriptor::new();
519        desc.set_owned();
520        assert!(desc.is_owned());
521        desc.clear_owned();
522        assert!(!desc.is_owned());
523    }
524
525    #[test]
526    fn tdes0_own_bit() {
527        // OWN bit must be bit 31.
528        let desc = TxDescriptor::new();
529        desc.set_owned();
530        assert_eq!(desc.raw_tdes0() & tdes0::OWN, tdes0::OWN);
531        assert_eq!(tdes0::OWN, 1 << 31);
532    }
533
534    // =========================================================================
535    // TX Descriptor Setup / Prepare Tests
536    // =========================================================================
537
538    #[test]
539    fn tx_descriptor_setup_chained() {
540        let desc = TxDescriptor::new();
541        let buf = [0u8; 64];
542        let next = TxDescriptor::new();
543
544        desc.setup_chained(buf.as_ptr(), &next as *const TxDescriptor);
545
546        assert_eq!(desc.buffer_addr(), buf.as_ptr() as u32);
547        assert_eq!(desc.next_desc_addr(), &next as *const TxDescriptor as u32);
548        assert!(desc.raw_tdes0() & tdes0::SECOND_ADDR_CHAINED != 0);
549        assert!(!desc.is_owned());
550    }
551
552    #[test]
553    fn tx_descriptor_prepare_single_frame() {
554        let desc = TxDescriptor::new();
555        desc.prepare(1500, true, true);
556
557        let raw0 = desc.raw_tdes0();
558        assert!(raw0 & tdes0::FIRST_SEGMENT != 0);
559        assert!(raw0 & tdes0::LAST_SEGMENT != 0);
560        assert!(raw0 & tdes0::INTERRUPT_ON_COMPLETE != 0);
561        assert!(raw0 & tdes0::OWN == 0, "prepare must not set OWN");
562
563        let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
564        assert_eq!(len, 1500);
565    }
566
567    #[test]
568    fn tdes0_first_last_bits() {
569        let desc = TxDescriptor::new();
570
571        // First segment only.
572        desc.prepare(100, true, false);
573        let raw = desc.raw_tdes0();
574        assert!(raw & tdes0::FIRST_SEGMENT != 0);
575        assert!(raw & tdes0::LAST_SEGMENT == 0);
576
577        // Last segment only.
578        desc.prepare(100, false, true);
579        let raw = desc.raw_tdes0();
580        assert!(raw & tdes0::FIRST_SEGMENT == 0);
581        assert!(raw & tdes0::LAST_SEGMENT != 0);
582    }
583
584    #[test]
585    fn tx_descriptor_prepare_and_submit() {
586        let desc = TxDescriptor::new();
587        desc.prepare_and_submit(256, true, true);
588        assert!(desc.is_owned());
589        assert_eq!(desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK, 256);
590    }
591
592    #[test]
593    fn tx_descriptor_no_errors_initially() {
594        let desc = TxDescriptor::new();
595        assert!(!desc.has_error());
596        assert_eq!(desc.error_flags(), 0);
597    }
598
599    #[test]
600    fn tx_descriptor_error_detection() {
601        let desc = TxDescriptor::new();
602        desc.tdes0.set(tdes0::ERR_SUMMARY | tdes0::UNDERFLOW_ERR);
603        assert!(desc.has_error());
604        assert!(desc.error_flags() & tdes0::UNDERFLOW_ERR != 0);
605    }
606
607    #[test]
608    fn tx_descriptor_reset_preserves_chain() {
609        let desc = TxDescriptor::new();
610        let next_addr = 0x1234_5678u32;
611        desc.next_desc_addr.set(next_addr);
612        desc.prepare_and_submit(1000, true, true);
613
614        desc.reset();
615
616        assert!(!desc.is_owned());
617        assert_eq!(desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK, 0);
618        assert_eq!(desc.next_desc_addr(), next_addr);
619        assert!(desc.raw_tdes0() & tdes0::SECOND_ADDR_CHAINED != 0);
620    }
621
622    // =========================================================================
623    // RX Descriptor Layout Tests
624    // =========================================================================
625
626    #[test]
627    fn rx_descriptor_size() {
628        assert_eq!(core::mem::size_of::<RxDescriptor>(), 32);
629        assert_eq!(RxDescriptor::SIZE, core::mem::size_of::<RxDescriptor>());
630    }
631
632    #[test]
633    fn rx_descriptor_alignment() {
634        assert_eq!(core::mem::align_of::<RxDescriptor>(), 4);
635    }
636
637    // =========================================================================
638    // RX Descriptor Ownership Tests
639    // =========================================================================
640
641    #[test]
642    fn rx_descriptor_new_not_owned() {
643        let desc = RxDescriptor::new();
644        assert!(!desc.is_owned());
645    }
646
647    #[test]
648    fn rdes0_own_bit() {
649        let desc = RxDescriptor::new();
650        desc.set_owned();
651        assert_eq!(desc.raw_rdes0() & rdes0::OWN, rdes0::OWN);
652        assert_eq!(rdes0::OWN, 1 << 31);
653    }
654
655    // =========================================================================
656    // RX Descriptor Setup / Chained Tests
657    // =========================================================================
658
659    #[test]
660    fn rx_descriptor_setup_chained() {
661        let desc = RxDescriptor::new();
662        let mut buf = [0u8; 1600];
663        let next = RxDescriptor::new();
664
665        desc.setup_chained(buf.as_mut_ptr(), 1600, &next as *const RxDescriptor);
666
667        assert_eq!(desc.buffer_addr(), buf.as_ptr() as u32);
668        assert_eq!(desc.next_desc_addr(), &next as *const RxDescriptor as u32);
669        assert_eq!(desc.buffer_size(), 1600);
670        assert!(desc.is_owned(), "setup_chained gives to DMA");
671        assert!(desc.raw_rdes1() & rdes1::SECOND_ADDR_CHAINED != 0);
672    }
673
674    // =========================================================================
675    // RX Descriptor Status Tests
676    // =========================================================================
677
678    #[test]
679    fn rx_descriptor_first_last_flags() {
680        let desc = RxDescriptor::new();
681        assert!(!desc.is_first());
682        assert!(!desc.is_last());
683
684        desc.rdes0.set(rdes0::FIRST_DESC | rdes0::LAST_DESC);
685        assert!(desc.is_first());
686        assert!(desc.is_last());
687        assert!(desc.is_complete_frame());
688    }
689
690    #[test]
691    fn rx_descriptor_payload_length() {
692        let desc = RxDescriptor::new();
693
694        // Frame length 1504 (including CRC), payload = 1500.
695        desc.rdes0.set(1504 << rdes0::FRAME_LEN_SHIFT);
696        assert_eq!(desc.frame_length(), 1504);
697        assert_eq!(desc.payload_length(), 1500);
698    }
699
700    #[test]
701    fn rx_descriptor_payload_length_short_frame() {
702        let desc = RxDescriptor::new();
703        // Frame shorter than CRC — saturating_sub prevents underflow.
704        desc.rdes0.set(2 << rdes0::FRAME_LEN_SHIFT);
705        assert_eq!(desc.payload_length(), 0);
706    }
707
708    #[test]
709    fn rx_descriptor_error_detection() {
710        let desc = RxDescriptor::new();
711        assert!(!desc.has_error());
712
713        desc.rdes0
714            .set(rdes0::ERR_SUMMARY | rdes0::CRC_ERR | rdes0::OVERFLOW_ERR);
715        assert!(desc.has_error());
716        assert!(desc.error_flags() & rdes0::CRC_ERR != 0);
717        assert!(desc.error_flags() & rdes0::OVERFLOW_ERR != 0);
718    }
719
720    // =========================================================================
721    // RX Descriptor Recycle Test
722    // =========================================================================
723
724    #[test]
725    fn rx_descriptor_recycle() {
726        let desc = RxDescriptor::new();
727        desc.rdes1.set(1600);
728        desc.rdes0
729            .set(rdes0::FIRST_DESC | rdes0::LAST_DESC | (100 << rdes0::FRAME_LEN_SHIFT));
730
731        desc.recycle();
732
733        assert!(desc.is_owned());
734        // Buffer size in RDES1 is preserved.
735        assert_eq!(desc.buffer_size(), 1600);
736    }
737}