diff --git a/zynq7000-hal/Cargo.toml b/zynq7000-hal/Cargo.toml index b18f943..a7f6f49 100644 --- a/zynq7000-hal/Cargo.toml +++ b/zynq7000-hal/Cargo.toml @@ -35,7 +35,7 @@ critical-section = "1" libm = "0.2" log = "0.4" embassy-sync = "0.6" -embassy-net = { path = "../../../Rust/embassy/embassy-net", version = "0.7" } +embassy-net-driver = { path = "../../../Rust/embassy/embassy-net-driver", version = "0.2" } raw-slicee = "0.1" embedded-io-async = "0.6" diff --git a/zynq7000-hal/src/eth/embassy_net.rs b/zynq7000-hal/src/eth/embassy_net.rs index cf25a7f..96737c1 100644 --- a/zynq7000-hal/src/eth/embassy_net.rs +++ b/zynq7000-hal/src/eth/embassy_net.rs @@ -1,3 +1,4 @@ +use arbitrary_int::u14; use embassy_sync::waitqueue::AtomicWaker; pub use super::rx_descr; @@ -11,10 +12,12 @@ pub struct EthernetEmbassyNet { pub burst_size: usize, pub mac_addr: [u8; 6], pub rx_descr: &'static [rx_descr::Descriptor], - pub rx_index: usize, - pub tx_descr: &'static [tx_descr::Descriptor], - pub tx_index: usize, - pub link_state: embassy_net::driver::LinkState, + //pub rx_index: usize, + //pub tx_descr: &'static [tx_descr::Descriptor], + //pub tx_index: usize, + pub tx_descr: tx_descr::DescriptorList<'static>, + pub tx_bufs: &'static mut [super::AlignedBuffer], + pub link_state: embassy_net_driver::LinkState, } pub struct EmbassyNetRxToken { @@ -22,45 +25,54 @@ pub struct EmbassyNetRxToken { rx_index: usize, } -impl embassy_net::driver::RxToken for EmbassyNetRxToken { +impl embassy_net_driver::RxToken for EmbassyNetRxToken { fn consume(self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { + f(&mut []) } } -pub struct EmbassyNetTxToken { - tx_descr_ref: tx_descr::DescriptorListRef<'static>, - tx_index: usize, +pub struct EmbassyNetTxToken<'a> { + descr_list: &'a mut tx_descr::DescriptorList<'static>, + tx_bufs: &'a mut [super::AlignedBuffer], } -impl embassy_net::driver::TxToken for EmbassyNetRxToken { - fn consume(self, len: usize, f: F) -> R +impl embassy_net_driver::TxToken for EmbassyNetTxToken<'_> { + fn consume(mut self, len: usize, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { + assert!(len <= super::MTU, "packet length exceeds MTU"); + let tx_idx = self.descr_list.current_tx_idx(); + let buffer = self.tx_bufs.get_mut(tx_idx).unwrap(); + let result = f(&mut buffer.0); + // TODO: Clean and invalidate cache. + let addr = buffer.0.as_ptr() as u32; + self.descr_list.prepare_transfer_unchecked(addr, u14::new(len as u16), true, false); + result } } impl EthernetEmbassyNet { #[inline] - pub fn update_link_state(&mut self, link_state: embassy_net::driver::LinkState) { + pub fn update_link_state(&mut self, link_state: embassy_net_driver::LinkState) { self.link_state = link_state; } #[inline] pub fn set_link_state_up(&mut self) { - self.update_link_state(embassy_net::driver::LinkState::Up); + self.update_link_state(embassy_net_driver::LinkState::Up); } #[inline] pub fn set_link_state_down(&mut self) { - self.update_link_state(embassy_net::driver::LinkState::Down); + self.update_link_state(embassy_net_driver::LinkState::Down); } } -impl embassy_net::Driver for EthernetEmbassyNet { +impl embassy_net_driver::Driver for EthernetEmbassyNet { type RxToken<'a> where Self: 'a, @@ -69,38 +81,44 @@ impl embassy_net::Driver for EthernetEmbassyNet { type TxToken<'a> where Self: 'a, - = EmbassyNetTxToken; + = EmbassyNetTxToken<'a>; fn receive( &mut self, cx: &mut core::task::Context, ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { RX_WAKER.register(cx.waker()); + None } fn transmit(&mut self, cx: &mut core::task::Context) -> Option> { TX_WAKER.register(cx.waker()); + if self.tx_descr.full() { + return None; + } + Some(EmbassyNetTxToken { + descr_list: &mut self.tx_descr, + tx_bufs: &mut self.tx_bufs, + }) } - fn link_state(&mut self, cx: &mut core::task::Context) -> embassy_net::driver::LinkState { + fn link_state(&mut self, cx: &mut core::task::Context) -> embassy_net_driver::LinkState { self.link_state } - fn capabilities(&self) -> embassy_net::driver::Capabilities { - embassy_net::driver::Capabilities { - max_transmission_unit: super::MTU, - max_burst_size: Some(self.burst_size), - checksum: embassy_net::driver::ChecksumCapabilities { - ipv4: true, - udp: true, - tcp: true, - icmpv4: true, - icmpv6: true, - }, - } + fn capabilities(&self) -> embassy_net_driver::Capabilities { + let mut capabilities = embassy_net_driver::Capabilities::default(); + capabilities.max_transmission_unit = super::MTU; + capabilities.max_burst_size = Some(self.burst_size); + capabilities.checksum.ipv4 = embassy_net_driver::Checksum::Both; + capabilities.checksum.udp = embassy_net_driver::Checksum::Both; + capabilities.checksum.tcp = embassy_net_driver::Checksum::Both; + capabilities.checksum.icmpv4 = embassy_net_driver::Checksum::None; + capabilities.checksum.icmpv6 = embassy_net_driver::Checksum::None; + capabilities } - fn hardware_address(&self) -> embassy_net::driver::HardwareAddress { - embassy_net::driver::HardwareAddress::Ethernet(self.mac_addr) + fn hardware_address(&self) -> embassy_net_driver::HardwareAddress { + embassy_net_driver::HardwareAddress::Ethernet(self.mac_addr) } } diff --git a/zynq7000-hal/src/eth/mod.rs b/zynq7000-hal/src/eth/mod.rs index 98ebbcd..b55bcce 100644 --- a/zynq7000-hal/src/eth/mod.rs +++ b/zynq7000-hal/src/eth/mod.rs @@ -15,6 +15,10 @@ pub mod embassy_net; pub const MTU: usize = 1536; pub const MAX_MDC_SPEED: Hertz = Hertz::from_raw(2_500_000); +#[repr(align(32))] +#[derive(Debug, Clone, Copy)] +pub struct AlignedBuffer(pub [u8; MTU]); + #[cfg(not(feature = "7z010-7z007s-clg225"))] use crate::gpio::mio::{ Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, diff --git a/zynq7000-hal/src/eth/tx_descr.rs b/zynq7000-hal/src/eth/tx_descr.rs index 5e0e9b3..4d9e959 100644 --- a/zynq7000-hal/src/eth/tx_descr.rs +++ b/zynq7000-hal/src/eth/tx_descr.rs @@ -9,6 +9,7 @@ pub use super::shared::Ownership; /// These descriptors are shared between software and hardware and contain information /// related to frame reception. #[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct Descriptor { /// The first word of the descriptor which is the byte address of the buffer. pub word0: u32, @@ -32,6 +33,9 @@ pub enum TransmitChecksumGenerationStatus { #[bitbybit::bitfield(u32, default = 0x0)] #[derive(Debug, PartialEq, Eq)] pub struct Word1 { + /// The ownership bit must be set to [Ownership::Hardware] if a frame should be transmitted. + /// + /// The controller will set this to [Ownership::Software] once the frame has been transmitted. #[bit(31, rw)] ownership: Ownership, #[bit(30, rw)] @@ -66,22 +70,39 @@ impl Descriptor { self.word1.set_ownership(ownership); } + #[inline] + pub fn ownership(&self) -> Ownership { + self.word1.ownership() + } + /// Set the wrap bit, which should be done for the last descriptor in the descriptor list. #[inline] pub fn set_wrap_bit(&mut self) { self.word1.set_wrap(true); } + #[inline] + pub fn set_tx_transfer_addr_unchecked(&mut self, addr: u32) { + self.word0 = addr; + } + /// Set the information for a transfer. - pub fn set_tx_transfer_info( + pub fn setup_tx_transfer_unchecked( &mut self, + addr: u32, tx_len: u14, last_buffer: bool, no_crc_generation: bool, ) { - self.word1.set_tx_len(tx_len); - self.word1.set_last_buffer(last_buffer); - self.word1.set_no_crc_generation(no_crc_generation); + self.set_tx_transfer_addr_unchecked(addr); + // Perform the read-modify-write sequence manually to ensure a minimum of reads/writes + // for the uncached memory. + let mut word1 = Word1::new_with_raw_value(self.word1.raw_value()); + word1.set_tx_len(tx_len); + word1.set_last_buffer(last_buffer); + word1.set_no_crc_generation(no_crc_generation); + word1.set_ownership(Ownership::Hardware); + self.word1 = word1; } } @@ -92,19 +113,72 @@ impl Default for Descriptor { } } -pub struct DescriptorListRef<'a>(&'a mut [Descriptor]); +#[derive(Debug)] +pub struct DescriptorList<'a> { + list: &'a mut [Descriptor], + /// The head index is used to handle the transmission of new frames. + tx_idx: usize, + /// The tail index is used to track the progress of active transmissions. + busy_idx: usize, +} -impl<'a> DescriptorListRef<'a> { - #[inline] - pub fn new(descriptor: &'a mut [Descriptor]) -> Self { - Self(descriptor) +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[error("tx error: {self:?}")] +pub struct TxError { + retry_limit_exceeded: bool, + late_collisions: bool, + ahb_error: bool, + checksum_generation: Option, +} + +impl TxError { + pub fn from_word1(word1: &Word1) -> Self { + Self { + retry_limit_exceeded: word1.retry_limit_exceeded(), + late_collisions: word1.late_collision(), + ahb_error: word1.transmit_frame_corruption_ahb_error(), + checksum_generation: if word1.checksum_status() + == TransmitChecksumGenerationStatus::NoError + { + None + } else { + Some(word1.checksum_status()) + }, + } } } -impl DescriptorListRef<'_> { +pub enum IncrementResult { + Busy, + Ok, +} + +impl<'a> DescriptorList<'a> { + #[inline] + pub fn new(descr_list: &'a mut [Descriptor]) -> Self { + Self { + list: descr_list, + tx_idx: 0, + busy_idx: 0, + } + } +} + +pub enum BusyHandlingResult { + /// Handled a descriptor slot where a TX error has occured. + TxError(TxError), + /// Handled one busy descriptor slot. More calls to this function are required to handle + /// all busy slots. + SlotHandled, + /// The busy index has caught up to the transmission index (all frames have been sent), or + /// the busy index is at an active transmission index. + Complete, +} + +impl DescriptorList<'_> { #[inline] pub fn base_ptr(&self) -> *const Descriptor { - self.0.as_ptr() + self.list.as_ptr() } #[inline] @@ -112,10 +186,83 @@ impl DescriptorListRef<'_> { self.base_ptr() as u32 } - pub fn init(&mut self) { - for desc in self.0.iter_mut() { - desc.set_ownership(Ownership::Hardware); + pub fn init_or_reset(&mut self) { + self.tx_idx = 0; + self.busy_idx = 0; + for desc in self.list.iter_mut() { + desc.set_ownership(Ownership::Software); } - self.0.last_mut().unwrap().set_wrap_bit(); + self.list.last_mut().unwrap().set_wrap_bit(); + } + + #[inline] + pub fn increment_tx_idx(&mut self) { + self.tx_idx = (self.tx_idx + 1) % self.list.len(); + } + + /// Increment the busy index without checking whether it has become + /// larger than the transmission index. + #[inline] + pub fn increment_busy_idx_unchecked(&mut self) { + self.busy_idx = (self.busy_idx + 1) % self.list.len(); + } + + #[inline] + pub fn full(&self) -> bool { + self.list[self.tx_idx].ownership() == Ownership::Hardware + } + + /// Check whether a tranfer has completed and handles the descriptor accordingly. + /// + /// This should be called continuosly when a TX error or a TX completion interrupt has + /// occured until it returns [BusyHandlingResult::Complete]. + pub fn check_and_handle_completed_transfer(&mut self) -> BusyHandlingResult { + let word1 = self.list[self.busy_idx].word1; + if word1.ownership() == Ownership::Hardware || self.busy_idx == self.tx_idx { + return BusyHandlingResult::Complete; + } + if word1.transmit_frame_corruption_ahb_error() + || word1.retry_limit_exceeded() + || word1.checksum_status() != TransmitChecksumGenerationStatus::NoError + || word1.late_collision() + { + return BusyHandlingResult::TxError(TxError::from_word1(&word1)); + } + self.list[self.busy_idx].word1 = Word1::builder() + .with_ownership(Ownership::Software) + .with_wrap(word1.wrap()) + .with_retry_limit_exceeded(false) + .with_transmit_frame_corruption_ahb_error(false) + .with_late_collision(false) + .with_checksum_status(TransmitChecksumGenerationStatus::NoError) + .with_no_crc_generation(false) + .with_last_buffer(false) + .with_tx_len(u14::new(0)) + .build(); + self.increment_busy_idx_unchecked(); + return BusyHandlingResult::SlotHandled; + } + + #[inline] + pub fn current_tx_idx(&self) -> usize { + self.tx_idx + } + + /// Prepare a transfer without checking whether that particular descriptor slot is used by + /// the hardware. + pub fn prepare_transfer_unchecked( + &mut self, + addr: u32, + tx_len: u14, + last_buffer: bool, + no_crc_generation: bool, + ) { + self.list[self.tx_idx].setup_tx_transfer_unchecked( + addr, + tx_len, + last_buffer, + no_crc_generation, + ); + self.increment_tx_idx(); } }