use super::VolatileCell;
use super::bits::{tdes0, tdes1};
#[repr(C)]
#[cfg_attr(not(feature = "esp32p4"), repr(align(4)))]
#[cfg_attr(feature = "esp32p4", repr(align(64)))]
pub struct TxDescriptor {
tdes0: VolatileCell<u32>,
tdes1: VolatileCell<u32>,
buffer1_addr: VolatileCell<u32>,
buffer2_next_desc: VolatileCell<u32>,
_reserved1: u32,
_reserved2: u32,
timestamp_low: VolatileCell<u32>,
timestamp_high: VolatileCell<u32>,
}
#[allow(dead_code)]
impl TxDescriptor {
#[cfg(not(feature = "esp32p4"))]
pub const SIZE: usize = 32;
#[cfg(feature = "esp32p4")]
pub const SIZE: usize = 64;
#[must_use]
pub const fn new() -> Self {
Self {
tdes0: VolatileCell::new(0),
tdes1: VolatileCell::new(0),
buffer1_addr: VolatileCell::new(0),
buffer2_next_desc: VolatileCell::new(0),
_reserved1: 0,
_reserved2: 0,
timestamp_low: VolatileCell::new(0),
timestamp_high: VolatileCell::new(0),
}
}
pub fn setup_chained(&self, buffer: *const u8, next_desc: *const TxDescriptor) {
self.buffer1_addr.set(buffer as u32);
self.buffer2_next_desc.set(next_desc as u32);
self.tdes0.set(tdes0::SECOND_ADDR_CHAINED);
self.tdes1.set(0);
}
pub fn setup_end_of_ring(&self, buffer: *const u8, first_desc: *const TxDescriptor) {
self.buffer1_addr.set(buffer as u32);
self.buffer2_next_desc.set(first_desc as u32);
self.tdes0
.set(tdes0::SECOND_ADDR_CHAINED | tdes0::TX_END_OF_RING);
self.tdes1.set(0);
}
#[inline(always)]
#[must_use]
pub fn is_owned(&self) -> bool {
(self.tdes0.get() & tdes0::OWN) != 0
}
#[inline(always)]
pub fn set_owned(&self) {
self.tdes0.update(|v| v | tdes0::OWN);
}
#[inline(always)]
pub fn clear_owned(&self) {
self.tdes0.update(|v| v & !tdes0::OWN);
}
pub fn prepare(&self, len: usize, first: bool, last: bool) {
let mut flags = tdes0::SECOND_ADDR_CHAINED;
if first {
flags |= tdes0::FIRST_SEGMENT;
}
if last {
flags |= tdes0::LAST_SEGMENT | tdes0::INTERRUPT_ON_COMPLETE;
}
self.tdes1.set((len as u32) & tdes1::BUFFER1_SIZE_MASK);
self.tdes0.set(flags);
}
pub fn prepare_and_submit(&self, len: usize, first: bool, last: bool) {
self.prepare(len, first, last);
self.set_owned();
}
pub fn set_checksum_mode(&self, mode: u32) {
self.tdes0.update(|v| {
(v & !tdes0::CHECKSUM_INSERT_MASK)
| ((mode << tdes0::CHECKSUM_INSERT_SHIFT) & tdes0::CHECKSUM_INSERT_MASK)
});
}
pub fn enable_timestamp(&self) {
self.tdes0.update(|v| v | tdes0::TX_TIMESTAMP_EN);
}
#[inline(always)]
#[must_use]
pub fn has_error(&self) -> bool {
(self.tdes0.get() & tdes0::ERR_SUMMARY) != 0
}
#[inline(always)]
#[must_use]
pub fn error_flags(&self) -> u32 {
self.tdes0.get() & tdes0::ALL_ERRORS
}
#[inline(always)]
#[must_use]
pub fn collision_count(&self) -> u8 {
((self.tdes0.get() & tdes0::COLLISION_COUNT_MASK) >> tdes0::COLLISION_COUNT_SHIFT) as u8
}
#[inline(always)]
#[must_use]
pub fn has_timestamp(&self) -> bool {
(self.tdes0.get() & tdes0::TX_TIMESTAMP_STATUS) != 0
}
#[inline(always)]
#[must_use]
pub fn timestamp_low(&self) -> u32 {
self.timestamp_low.get()
}
#[inline(always)]
#[must_use]
pub fn timestamp_high(&self) -> u32 {
self.timestamp_high.get()
}
#[inline(always)]
#[must_use]
pub fn timestamp(&self) -> u64 {
((self.timestamp_high.get() as u64) << 32) | (self.timestamp_low.get() as u64)
}
#[inline(always)]
#[must_use]
pub fn buffer_addr(&self) -> u32 {
self.buffer1_addr.get()
}
#[inline(always)]
#[must_use]
pub fn next_desc_addr(&self) -> u32 {
self.buffer2_next_desc.get()
}
pub fn reset(&self) {
let next = self.buffer2_next_desc.get();
self.tdes0.set(tdes0::SECOND_ADDR_CHAINED);
self.tdes1.set(0);
self.buffer2_next_desc.set(next);
}
#[inline(always)]
#[must_use]
pub fn raw_tdes0(&self) -> u32 {
self.tdes0.get()
}
#[inline(always)]
#[must_use]
pub fn raw_tdes1(&self) -> u32 {
self.tdes1.get()
}
}
impl Default for TxDescriptor {
fn default() -> Self {
Self::new()
}
}
unsafe impl Sync for TxDescriptor {}
unsafe impl Send for TxDescriptor {}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::dma::descriptor::bits::{checksum_mode, tdes0, tdes1};
#[test]
fn tx_descriptor_size() {
#[cfg(not(feature = "esp32p4"))]
assert_eq!(core::mem::size_of::<TxDescriptor>(), 32);
#[cfg(feature = "esp32p4")]
assert_eq!(core::mem::size_of::<TxDescriptor>(), 64);
}
#[test]
fn tx_descriptor_alignment() {
#[cfg(not(feature = "esp32p4"))]
assert_eq!(core::mem::align_of::<TxDescriptor>(), 4);
#[cfg(feature = "esp32p4")]
assert_eq!(core::mem::align_of::<TxDescriptor>(), 64);
}
#[test]
fn tx_descriptor_const_size() {
assert_eq!(TxDescriptor::SIZE, core::mem::size_of::<TxDescriptor>());
}
#[test]
fn tx_descriptor_new_not_owned() {
let desc = TxDescriptor::new();
assert!(
!desc.is_owned(),
"New descriptor should not be owned by DMA"
);
}
#[test]
fn tx_descriptor_set_owned() {
let desc = TxDescriptor::new();
desc.set_owned();
assert!(
desc.is_owned(),
"Descriptor should be owned after set_owned()"
);
}
#[test]
fn tx_descriptor_clear_owned() {
let desc = TxDescriptor::new();
desc.set_owned();
assert!(desc.is_owned());
desc.clear_owned();
assert!(
!desc.is_owned(),
"Descriptor should not be owned after clear_owned()"
);
}
#[test]
fn tx_descriptor_own_bit_position() {
let desc = TxDescriptor::new();
desc.set_owned();
let raw = desc.raw_tdes0();
assert_eq!(raw & tdes0::OWN, tdes0::OWN, "OWN bit should be bit 31");
}
#[test]
fn tx_descriptor_prepare_first_segment() {
let desc = TxDescriptor::new();
desc.prepare(100, true, false);
let raw = desc.raw_tdes0();
assert!(
raw & tdes0::FIRST_SEGMENT != 0,
"First segment flag should be set"
);
assert!(
raw & tdes0::LAST_SEGMENT == 0,
"Last segment flag should not be set"
);
assert!(raw & tdes0::OWN == 0, "OWN should not be set by prepare()");
}
#[test]
fn tx_descriptor_prepare_last_segment() {
let desc = TxDescriptor::new();
desc.prepare(100, false, true);
let raw = desc.raw_tdes0();
assert!(
raw & tdes0::FIRST_SEGMENT == 0,
"First segment flag should not be set"
);
assert!(
raw & tdes0::LAST_SEGMENT != 0,
"Last segment flag should be set"
);
assert!(
raw & tdes0::INTERRUPT_ON_COMPLETE != 0,
"IOC should be set on last segment"
);
}
#[test]
fn tx_descriptor_prepare_complete_frame() {
let desc = TxDescriptor::new();
desc.prepare(1500, true, true);
let raw = desc.raw_tdes0();
assert!(raw & tdes0::FIRST_SEGMENT != 0);
assert!(raw & tdes0::LAST_SEGMENT != 0);
assert!(raw & tdes0::INTERRUPT_ON_COMPLETE != 0);
}
#[test]
fn tx_descriptor_frame_length() {
let desc = TxDescriptor::new();
desc.prepare(64, true, true);
let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
assert_eq!(len, 64);
desc.prepare(1500, true, true);
let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
assert_eq!(len, 1500);
desc.prepare(1518, true, true);
let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
assert_eq!(len, 1518);
}
#[test]
fn tx_descriptor_prepare_and_submit() {
let desc = TxDescriptor::new();
desc.prepare_and_submit(256, true, true);
assert!(
desc.is_owned(),
"Descriptor should be owned after prepare_and_submit()"
);
let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
assert_eq!(len, 256);
let raw = desc.raw_tdes0();
assert!(raw & tdes0::FIRST_SEGMENT != 0);
assert!(raw & tdes0::LAST_SEGMENT != 0);
}
#[test]
fn tx_descriptor_checksum_mode_disabled() {
let desc = TxDescriptor::new();
desc.prepare(100, true, true);
desc.set_checksum_mode(checksum_mode::DISABLED);
let raw = desc.raw_tdes0();
let mode = (raw & tdes0::CHECKSUM_INSERT_MASK) >> tdes0::CHECKSUM_INSERT_SHIFT;
assert_eq!(mode, 0);
}
#[test]
fn tx_descriptor_checksum_mode_ip_only() {
let desc = TxDescriptor::new();
desc.prepare(100, true, true);
desc.set_checksum_mode(checksum_mode::IP_ONLY);
let raw = desc.raw_tdes0();
let mode = (raw & tdes0::CHECKSUM_INSERT_MASK) >> tdes0::CHECKSUM_INSERT_SHIFT;
assert_eq!(mode, 1);
}
#[test]
fn tx_descriptor_checksum_mode_full() {
let desc = TxDescriptor::new();
desc.prepare(100, true, true);
desc.set_checksum_mode(checksum_mode::FULL);
let raw = desc.raw_tdes0();
let mode = (raw & tdes0::CHECKSUM_INSERT_MASK) >> tdes0::CHECKSUM_INSERT_SHIFT;
assert_eq!(mode, 3);
}
#[test]
fn tx_descriptor_no_errors_initially() {
let desc = TxDescriptor::new();
assert!(!desc.has_error());
}
#[test]
fn tx_descriptor_error_detection() {
let desc = TxDescriptor::new();
desc.tdes0.set(tdes0::ERR_SUMMARY);
assert!(desc.has_error());
desc.tdes0.set(0);
assert!(!desc.has_error());
desc.tdes0.set(tdes0::ERR_SUMMARY | tdes0::UNDERFLOW_ERR);
assert!(desc.has_error());
let errors = desc.error_flags();
assert!(errors & tdes0::UNDERFLOW_ERR != 0);
desc.tdes0
.set(tdes0::ERR_SUMMARY | tdes0::LATE_COLLISION | tdes0::UNDERFLOW_ERR);
assert!(desc.has_error());
let errors = desc.error_flags();
assert!(errors & tdes0::LATE_COLLISION != 0);
assert!(errors & tdes0::UNDERFLOW_ERR != 0);
}
#[test]
fn tx_descriptor_reset() {
let desc = TxDescriptor::new();
desc.prepare_and_submit(1000, true, true);
desc.set_checksum_mode(checksum_mode::FULL);
let next_addr = 0x1234_5678u32;
desc.buffer2_next_desc.set(next_addr);
desc.reset();
assert!(!desc.is_owned());
let len = desc.raw_tdes1() & tdes1::BUFFER1_SIZE_MASK;
assert_eq!(len, 0);
assert_eq!(desc.next_desc_addr(), next_addr);
assert!(desc.raw_tdes0() & tdes0::SECOND_ADDR_CHAINED != 0);
}
}