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// Verify size and field offsets at compile time. These guard against silent
124// layout regressions if anyone reorders or adds fields — DMA hardware reads
125// descriptor words at fixed offsets, so wrong layout = silent corruption.
126// Matches upstream esp-hal pattern in src/ethernet/dma.rs:118-123.
127const _: () = assert!(core::mem::size_of::<TxDescriptor>() == 32);
128const _: () = assert!(core::mem::align_of::<TxDescriptor>() >= 4);
129const _: () = assert!(core::mem::offset_of!(TxDescriptor, tdes0) == 0);
130const _: () = assert!(core::mem::offset_of!(TxDescriptor, tdes1) == 4);
131const _: () = assert!(core::mem::offset_of!(TxDescriptor, buffer_addr) == 8);
132const _: () = assert!(core::mem::offset_of!(TxDescriptor, next_desc_addr) == 12);
133
134#[allow(dead_code)]
135impl TxDescriptor {
136    /// Descriptor size in bytes (enhanced 8-word layout).
137    pub const SIZE: usize = 32;
138
139    /// Create a new zeroed TX descriptor.
140    #[must_use]
141    pub const fn new() -> Self {
142        Self {
143            tdes0: VolatileCell::new(0),
144            tdes1: VolatileCell::new(0),
145            buffer_addr: VolatileCell::new(0),
146            next_desc_addr: VolatileCell::new(0),
147            _reserved4: VolatileCell::new(0),
148            _reserved5: VolatileCell::new(0),
149            _ts_low: VolatileCell::new(0),
150            _ts_high: VolatileCell::new(0),
151        }
152    }
153
154    /// Initialize descriptor for chained mode.
155    ///
156    /// Sets the buffer pointer, next-descriptor pointer, and the
157    /// `SECOND_ADDR_CHAINED` flag. The descriptor is left CPU-owned.
158    pub fn setup_chained(&self, buffer: *const u8, next_desc: *const TxDescriptor) {
159        self.buffer_addr.set(buffer as u32);
160        self.next_desc_addr.set(next_desc as u32);
161        self.tdes0.set(tdes0::SECOND_ADDR_CHAINED);
162        self.tdes1.set(0);
163    }
164
165    /// Check if DMA owns this descriptor.
166    #[inline(always)]
167    #[must_use]
168    pub fn is_owned(&self) -> bool {
169        (self.tdes0.get() & tdes0::OWN) != 0
170    }
171
172    /// Give ownership to DMA.
173    #[inline(always)]
174    pub fn set_owned(&self) {
175        self.tdes0.update(|v| v | tdes0::OWN);
176    }
177
178    /// Take ownership from DMA.
179    #[inline(always)]
180    pub fn clear_owned(&self) {
181        self.tdes0.update(|v| v & !tdes0::OWN);
182    }
183
184    /// Prepare descriptor for transmission with segment flags.
185    ///
186    /// Sets the buffer length and first/last segment flags.
187    /// Does **not** set the OWN bit — call [`set_owned`](Self::set_owned)
188    /// afterwards to submit to DMA.
189    ///
190    /// CIC (Checksum Insertion Control) is left at `0b00` — hardware
191    /// checksum offload is disabled because the Synopsys GMAC checksum
192    /// engine produces incorrect TCP/UDP checksums on ESP32 rev v3.1
193    /// silicon. smoltcp computes checksums in software instead (see
194    /// `Driver::capabilities` advertising `ChecksumCapabilities::default()`).
195    pub fn prepare(&self, len: usize, first: bool, last: bool) {
196        // Hardware checksum offload (CIC bits in TDES0[23:22]) is **disabled**
197        // because it produces frames with broken TCP/UDP checksums on ESP32
198        // rev v3.1 — wire-side capture shows the iperf2 client header (60 B)
199        // gets through but every subsequent bulk-data segment is dropped by
200        // the peer's IP/TCP stack with a checksum mismatch, breaking any
201        // sustained TCP flow. The v0.3.0 release enabled CIC=0b11 (full
202        // TCP/UDP/ICMP + IPv4 header offload) on the assumption that the
203        // Synopsys GMAC checksum engine matched the spec; it does not on
204        // this silicon revision. CIC=0b00 forces the MAC to leave the
205        // checksum bytes alone — smoltcp computes them in software (see
206        // ChecksumCapabilities advertised by the embassy-net Driver impl).
207        // Verified working at ~6 Mbit/s iperf2 uplink against iperf -s on
208        // the same LAN.
209        let mut flags = tdes0::SECOND_ADDR_CHAINED;
210
211        if first {
212            flags |= tdes0::FIRST_SEGMENT;
213        }
214        if last {
215            flags |= tdes0::LAST_SEGMENT | tdes0::INTERRUPT_ON_COMPLETE;
216        }
217
218        self.tdes1.set((len as u32) & tdes1::BUFFER1_SIZE_MASK);
219        self.tdes0.set(flags);
220    }
221
222    /// Prepare and submit to DMA in one operation.
223    pub fn prepare_and_submit(&self, len: usize, first: bool, last: bool) {
224        self.prepare(len, first, last);
225        self.set_owned();
226    }
227
228    /// Check if transmission had errors (error summary bit).
229    #[inline(always)]
230    #[must_use]
231    pub fn has_error(&self) -> bool {
232        (self.tdes0.get() & tdes0::ERR_SUMMARY) != 0
233    }
234
235    /// Get all error flags from TDES0.
236    #[inline(always)]
237    #[must_use]
238    pub fn error_flags(&self) -> u32 {
239        self.tdes0.get() & tdes0::ALL_ERRORS
240    }
241
242    /// Get buffer address (TDES2).
243    #[inline(always)]
244    #[must_use]
245    pub fn buffer_addr(&self) -> u32 {
246        self.buffer_addr.get()
247    }
248
249    /// Get next descriptor address (TDES3, chained mode).
250    #[inline(always)]
251    #[must_use]
252    pub fn next_desc_addr(&self) -> u32 {
253        self.next_desc_addr.get()
254    }
255
256    /// Reset descriptor to initial state, preserving the chain pointer.
257    pub fn reset(&self) {
258        let next = self.next_desc_addr.get();
259        self.tdes0.set(tdes0::SECOND_ADDR_CHAINED);
260        self.tdes1.set(0);
261        self.next_desc_addr.set(next);
262    }
263
264    /// Raw TDES0 value (for debugging / tests).
265    #[inline(always)]
266    #[must_use]
267    pub fn raw_tdes0(&self) -> u32 {
268        self.tdes0.get()
269    }
270
271    /// Raw TDES1 value (for debugging / tests).
272    #[inline(always)]
273    #[must_use]
274    pub fn raw_tdes1(&self) -> u32 {
275        self.tdes1.get()
276    }
277}
278
279impl Default for TxDescriptor {
280    fn default() -> Self {
281        Self::new()
282    }
283}
284
285// SAFETY: TxDescriptor uses volatile cells for all DMA-accessed fields.
286unsafe impl Sync for TxDescriptor {}
287// SAFETY: TxDescriptor can be sent between threads.
288unsafe impl Send for TxDescriptor {}
289
290// =============================================================================
291// RX Descriptor
292// =============================================================================
293
294/// RX DMA descriptor — enhanced 8-word layout (32 bytes).
295///
296/// See [`TxDescriptor`] for why we run the enhanced layout.
297#[repr(C, align(4))]
298pub struct RxDescriptor {
299    /// RDES0: Status bits (OWN, first/last, frame length, errors).
300    rdes0: VolatileCell<u32>,
301    /// RDES1: Buffer 1 size and control flags.
302    rdes1: VolatileCell<u32>,
303    /// RDES2: Buffer 1 address.
304    buffer_addr: VolatileCell<u32>,
305    /// RDES3: Next descriptor address (chained mode).
306    next_desc_addr: VolatileCell<u32>,
307    /// RDES4: Extended status (when enabled).
308    _ext_status: VolatileCell<u32>,
309    /// RDES5: Reserved.
310    _reserved5: VolatileCell<u32>,
311    /// RDES6: Timestamp low (when timestamping is enabled).
312    _ts_low: VolatileCell<u32>,
313    /// RDES7: Timestamp high (when timestamping is enabled).
314    _ts_high: VolatileCell<u32>,
315}
316
317// Verify size and field offsets at compile time. See note on TxDescriptor above.
318const _: () = assert!(core::mem::size_of::<RxDescriptor>() == 32);
319const _: () = assert!(core::mem::align_of::<RxDescriptor>() >= 4);
320const _: () = assert!(core::mem::offset_of!(RxDescriptor, rdes0) == 0);
321const _: () = assert!(core::mem::offset_of!(RxDescriptor, rdes1) == 4);
322const _: () = assert!(core::mem::offset_of!(RxDescriptor, buffer_addr) == 8);
323const _: () = assert!(core::mem::offset_of!(RxDescriptor, next_desc_addr) == 12);
324
325#[allow(dead_code)]
326impl RxDescriptor {
327    /// Descriptor size in bytes (enhanced 8-word layout).
328    pub const SIZE: usize = 32;
329
330    /// Create a new zeroed RX descriptor. Call [`setup_chained`](Self::setup_chained) before use.
331    #[must_use]
332    pub const fn new() -> Self {
333        Self {
334            rdes0: VolatileCell::new(0),
335            rdes1: VolatileCell::new(0),
336            buffer_addr: VolatileCell::new(0),
337            next_desc_addr: VolatileCell::new(0),
338            _ext_status: VolatileCell::new(0),
339            _reserved5: VolatileCell::new(0),
340            _ts_low: VolatileCell::new(0),
341            _ts_high: VolatileCell::new(0),
342        }
343    }
344
345    /// Configure descriptor in chained mode and give to DMA.
346    ///
347    /// Sets the buffer pointer, buffer size, next-descriptor pointer,
348    /// the `SECOND_ADDR_CHAINED` flag, and the OWN bit.
349    pub fn setup_chained(
350        &self,
351        buffer: *mut u8,
352        buffer_size: usize,
353        next_desc: *const RxDescriptor,
354    ) {
355        self.buffer_addr.set(buffer as u32);
356        self.next_desc_addr.set(next_desc as u32);
357        self.rdes1
358            .set(rdes1::SECOND_ADDR_CHAINED | ((buffer_size as u32) & rdes1::BUFFER1_SIZE_MASK));
359        // Give ownership to DMA.
360        self.rdes0.set(rdes0::OWN);
361    }
362
363    /// Check if DMA owns this descriptor.
364    #[inline(always)]
365    #[must_use]
366    pub fn is_owned(&self) -> bool {
367        (self.rdes0.get() & rdes0::OWN) != 0
368    }
369
370    /// Give ownership to DMA.
371    #[inline(always)]
372    pub fn set_owned(&self) {
373        self.rdes0.set(rdes0::OWN);
374    }
375
376    /// Take ownership from DMA.
377    #[inline(always)]
378    pub fn clear_owned(&self) {
379        self.rdes0.update(|v| v & !rdes0::OWN);
380    }
381
382    /// First descriptor of a frame.
383    #[inline(always)]
384    #[must_use]
385    pub fn is_first(&self) -> bool {
386        (self.rdes0.get() & rdes0::FIRST_DESC) != 0
387    }
388
389    /// Last descriptor of a frame.
390    #[inline(always)]
391    #[must_use]
392    pub fn is_last(&self) -> bool {
393        (self.rdes0.get() & rdes0::LAST_DESC) != 0
394    }
395
396    /// Complete frame in a single descriptor (both first and last).
397    #[inline(always)]
398    #[must_use]
399    pub fn is_complete_frame(&self) -> bool {
400        let status = self.rdes0.get();
401        (status & (rdes0::FIRST_DESC | rdes0::LAST_DESC)) == (rdes0::FIRST_DESC | rdes0::LAST_DESC)
402    }
403
404    /// Check if the error summary bit is set.
405    #[inline(always)]
406    #[must_use]
407    pub fn has_error(&self) -> bool {
408        (self.rdes0.get() & rdes0::ERR_SUMMARY) != 0
409    }
410
411    /// Raw error flags from RDES0.
412    #[inline(always)]
413    #[must_use]
414    pub fn error_flags(&self) -> u32 {
415        self.rdes0.get() & rdes0::ALL_ERRORS
416    }
417
418    /// Frame length including CRC (valid on last descriptor).
419    #[inline(always)]
420    #[must_use]
421    pub fn frame_length(&self) -> usize {
422        ((self.rdes0.get() & rdes0::FRAME_LEN_MASK) >> rdes0::FRAME_LEN_SHIFT) as usize
423    }
424
425    /// Frame length excluding the 4-byte CRC.
426    #[inline(always)]
427    #[must_use]
428    pub fn payload_length(&self) -> usize {
429        self.frame_length().saturating_sub(4)
430    }
431
432    /// Buffer address (RDES2).
433    #[inline(always)]
434    #[must_use]
435    pub fn buffer_addr(&self) -> u32 {
436        self.buffer_addr.get()
437    }
438
439    /// Next descriptor address (RDES3, chained mode).
440    #[inline(always)]
441    #[must_use]
442    pub fn next_desc_addr(&self) -> u32 {
443        self.next_desc_addr.get()
444    }
445
446    /// Configured buffer size from RDES1.
447    #[inline(always)]
448    #[must_use]
449    pub fn buffer_size(&self) -> usize {
450        (self.rdes1.get() & rdes1::BUFFER1_SIZE_MASK) as usize
451    }
452
453    /// Clear status and return the descriptor to DMA for reuse.
454    pub fn recycle(&self) {
455        self.rdes0.set(rdes0::OWN);
456    }
457
458    /// Raw RDES0 value (for debugging / tests).
459    #[inline(always)]
460    #[must_use]
461    pub fn raw_rdes0(&self) -> u32 {
462        self.rdes0.get()
463    }
464
465    /// Raw RDES1 value (for debugging / tests).
466    #[inline(always)]
467    #[must_use]
468    pub fn raw_rdes1(&self) -> u32 {
469        self.rdes1.get()
470    }
471
472    /// Set raw RDES0 value (test only — simulates DMA hardware writes).
473    #[cfg(test)]
474    pub fn set_raw_rdes0(&self, val: u32) {
475        self.rdes0.set(val);
476    }
477}
478
479impl Default for RxDescriptor {
480    fn default() -> Self {
481        Self::new()
482    }
483}
484
485// SAFETY: RxDescriptor uses volatile cells for all DMA-accessed fields.
486unsafe impl Sync for RxDescriptor {}
487// SAFETY: RxDescriptor can be sent between threads.
488unsafe impl Send for RxDescriptor {}
489
490// =============================================================================
491// Tests
492// =============================================================================
493
494#[cfg(test)]
495mod tests {
496    use super::*;
497
498    // =========================================================================
499    // VolatileCell Tests
500    // =========================================================================
501
502    #[test]
503    fn volatile_cell_new() {
504        let cell = VolatileCell::new(42u32);
505        assert_eq!(cell.get(), 42);
506    }
507
508    #[test]
509    fn volatile_cell_get_set() {
510        let cell = VolatileCell::new(0u32);
511        assert_eq!(cell.get(), 0);
512        cell.set(0xDEAD_BEEF);
513        assert_eq!(cell.get(), 0xDEAD_BEEF);
514    }
515
516    #[test]
517    fn volatile_cell_update() {
518        let cell = VolatileCell::new(0x0000_00FFu32);
519        cell.update(|v| v | 0xFF00_0000);
520        assert_eq!(cell.get(), 0xFF00_00FF);
521    }
522
523    #[test]
524    fn volatile_cell_default() {
525        let cell = VolatileCell::<u32>::default();
526        assert_eq!(cell.get(), 0);
527    }
528
529    // =========================================================================
530    // TX Descriptor Layout Tests
531    // =========================================================================
532
533    #[test]
534    fn tx_descriptor_size() {
535        assert_eq!(core::mem::size_of::<TxDescriptor>(), 32);
536        assert_eq!(TxDescriptor::SIZE, core::mem::size_of::<TxDescriptor>());
537    }
538
539    #[test]
540    fn tx_descriptor_alignment() {
541        assert_eq!(core::mem::align_of::<TxDescriptor>(), 4);
542    }
543
544    // =========================================================================
545    // TX Descriptor Ownership Tests
546    // =========================================================================
547
548    #[test]
549    fn tx_descriptor_new_not_owned() {
550        let desc = TxDescriptor::new();
551        assert!(!desc.is_owned());
552    }
553
554    #[test]
555    fn tx_descriptor_is_owned() {
556        let desc = TxDescriptor::new();
557        desc.set_owned();
558        assert!(desc.is_owned());
559        desc.clear_owned();
560        assert!(!desc.is_owned());
561    }
562
563    #[test]
564    fn tdes0_own_bit() {
565        // OWN bit must be bit 31.
566        let desc = TxDescriptor::new();
567        desc.set_owned();
568        assert_eq!(desc.raw_tdes0() & tdes0::OWN, tdes0::OWN);
569        assert_eq!(tdes0::OWN, 1 << 31);
570    }
571
572    // =========================================================================
573    // TX Descriptor Setup / Prepare Tests
574    // =========================================================================
575
576    #[test]
577    fn tx_descriptor_setup_chained() {
578        let desc = TxDescriptor::new();
579        let buf = [0u8; 64];
580        let next = TxDescriptor::new();
581
582        desc.setup_chained(buf.as_ptr(), &next as *const TxDescriptor);
583
584        assert_eq!(desc.buffer_addr(), buf.as_ptr() as u32);
585        assert_eq!(desc.next_desc_addr(), &next as *const TxDescriptor as u32);
586        assert!(desc.raw_tdes0() & tdes0::SECOND_ADDR_CHAINED != 0);
587        assert!(!desc.is_owned());
588    }
589
590    #[test]
591    fn tx_descriptor_prepare_single_frame() {
592        let desc = TxDescriptor::new();
593        desc.prepare(1500, true, true);
594
595        let raw0 = desc.raw_tdes0();
596        assert!(raw0 & tdes0::FIRST_SEGMENT != 0);
597        assert!(raw0 & tdes0::LAST_SEGMENT != 0);
598        assert!(raw0 & tdes0::INTERRUPT_ON_COMPLETE != 0);
599        assert!(raw0 & tdes0::OWN == 0, "prepare must not set OWN");
600
601        let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
602        assert_eq!(len, 1500);
603    }
604
605    #[test]
606    fn tdes0_first_last_bits() {
607        let desc = TxDescriptor::new();
608
609        // First segment only.
610        desc.prepare(100, true, false);
611        let raw = desc.raw_tdes0();
612        assert!(raw & tdes0::FIRST_SEGMENT != 0);
613        assert!(raw & tdes0::LAST_SEGMENT == 0);
614
615        // Last segment only.
616        desc.prepare(100, false, true);
617        let raw = desc.raw_tdes0();
618        assert!(raw & tdes0::FIRST_SEGMENT == 0);
619        assert!(raw & tdes0::LAST_SEGMENT != 0);
620    }
621
622    #[test]
623    fn tx_descriptor_prepare_disables_cic() {
624        // CIC bits 23:22 = 0b00: the MAC must NOT touch the checksum field
625        // because hardware insertion is broken on ESP32 rev v3.1 — see
626        // `TxDescriptor::prepare` doc-comment for the wire-side evidence.
627        // smoltcp computes checksums in software (Driver::capabilities
628        // advertises `ChecksumCapabilities::default()`).
629        let desc = TxDescriptor::new();
630        desc.prepare(64, true, true);
631        let raw = desc.raw_tdes0();
632        let cic = (raw >> tdes0::CHECKSUM_INSERT_SHIFT) & 0x3;
633        assert_eq!(cic, 0b00, "CIC must be 0b00 — HW checksum offload disabled");
634    }
635
636    #[test]
637    fn tx_descriptor_prepare_and_submit() {
638        let desc = TxDescriptor::new();
639        desc.prepare_and_submit(256, true, true);
640        assert!(desc.is_owned());
641        assert_eq!(desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK, 256);
642    }
643
644    #[test]
645    fn tx_descriptor_no_errors_initially() {
646        let desc = TxDescriptor::new();
647        assert!(!desc.has_error());
648        assert_eq!(desc.error_flags(), 0);
649    }
650
651    #[test]
652    fn tx_descriptor_error_detection() {
653        let desc = TxDescriptor::new();
654        desc.tdes0.set(tdes0::ERR_SUMMARY | tdes0::UNDERFLOW_ERR);
655        assert!(desc.has_error());
656        assert!(desc.error_flags() & tdes0::UNDERFLOW_ERR != 0);
657    }
658
659    #[test]
660    fn tx_descriptor_reset_preserves_chain() {
661        let desc = TxDescriptor::new();
662        let next_addr = 0x1234_5678u32;
663        desc.next_desc_addr.set(next_addr);
664        desc.prepare_and_submit(1000, true, true);
665
666        desc.reset();
667
668        assert!(!desc.is_owned());
669        assert_eq!(desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK, 0);
670        assert_eq!(desc.next_desc_addr(), next_addr);
671        assert!(desc.raw_tdes0() & tdes0::SECOND_ADDR_CHAINED != 0);
672    }
673
674    // =========================================================================
675    // RX Descriptor Layout Tests
676    // =========================================================================
677
678    #[test]
679    fn rx_descriptor_size() {
680        assert_eq!(core::mem::size_of::<RxDescriptor>(), 32);
681        assert_eq!(RxDescriptor::SIZE, core::mem::size_of::<RxDescriptor>());
682    }
683
684    #[test]
685    fn rx_descriptor_alignment() {
686        assert_eq!(core::mem::align_of::<RxDescriptor>(), 4);
687    }
688
689    // =========================================================================
690    // RX Descriptor Ownership Tests
691    // =========================================================================
692
693    #[test]
694    fn rx_descriptor_new_not_owned() {
695        let desc = RxDescriptor::new();
696        assert!(!desc.is_owned());
697    }
698
699    #[test]
700    fn rdes0_own_bit() {
701        let desc = RxDescriptor::new();
702        desc.set_owned();
703        assert_eq!(desc.raw_rdes0() & rdes0::OWN, rdes0::OWN);
704        assert_eq!(rdes0::OWN, 1 << 31);
705    }
706
707    // =========================================================================
708    // RX Descriptor Setup / Chained Tests
709    // =========================================================================
710
711    #[test]
712    fn rx_descriptor_setup_chained() {
713        let desc = RxDescriptor::new();
714        let mut buf = [0u8; 1600];
715        let next = RxDescriptor::new();
716
717        desc.setup_chained(buf.as_mut_ptr(), 1600, &next as *const RxDescriptor);
718
719        assert_eq!(desc.buffer_addr(), buf.as_ptr() as u32);
720        assert_eq!(desc.next_desc_addr(), &next as *const RxDescriptor as u32);
721        assert_eq!(desc.buffer_size(), 1600);
722        assert!(desc.is_owned(), "setup_chained gives to DMA");
723        assert!(desc.raw_rdes1() & rdes1::SECOND_ADDR_CHAINED != 0);
724    }
725
726    // =========================================================================
727    // RX Descriptor Status Tests
728    // =========================================================================
729
730    #[test]
731    fn rx_descriptor_first_last_flags() {
732        let desc = RxDescriptor::new();
733        assert!(!desc.is_first());
734        assert!(!desc.is_last());
735
736        desc.rdes0.set(rdes0::FIRST_DESC | rdes0::LAST_DESC);
737        assert!(desc.is_first());
738        assert!(desc.is_last());
739        assert!(desc.is_complete_frame());
740    }
741
742    #[test]
743    fn rx_descriptor_payload_length() {
744        let desc = RxDescriptor::new();
745
746        // Frame length 1504 (including CRC), payload = 1500.
747        desc.rdes0.set(1504 << rdes0::FRAME_LEN_SHIFT);
748        assert_eq!(desc.frame_length(), 1504);
749        assert_eq!(desc.payload_length(), 1500);
750    }
751
752    #[test]
753    fn rx_descriptor_payload_length_short_frame() {
754        let desc = RxDescriptor::new();
755        // Frame shorter than CRC — saturating_sub prevents underflow.
756        desc.rdes0.set(2 << rdes0::FRAME_LEN_SHIFT);
757        assert_eq!(desc.payload_length(), 0);
758    }
759
760    #[test]
761    fn rx_descriptor_error_detection() {
762        let desc = RxDescriptor::new();
763        assert!(!desc.has_error());
764
765        desc.rdes0
766            .set(rdes0::ERR_SUMMARY | rdes0::CRC_ERR | rdes0::OVERFLOW_ERR);
767        assert!(desc.has_error());
768        assert!(desc.error_flags() & rdes0::CRC_ERR != 0);
769        assert!(desc.error_flags() & rdes0::OVERFLOW_ERR != 0);
770    }
771
772    // =========================================================================
773    // RX Descriptor Recycle Test
774    // =========================================================================
775
776    #[test]
777    fn rx_descriptor_recycle() {
778        let desc = RxDescriptor::new();
779        desc.rdes1.set(1600);
780        desc.rdes0
781            .set(rdes0::FIRST_DESC | rdes0::LAST_DESC | (100 << rdes0::FRAME_LEN_SHIFT));
782
783        desc.recycle();
784
785        assert!(desc.is_owned());
786        // Buffer size in RDES1 is preserved.
787        assert_eq!(desc.buffer_size(), 1600);
788    }
789}