diff options
Diffstat (limited to 'crates/imxrt/src/drivers/enet.rs')
| -rw-r--r-- | crates/imxrt/src/drivers/enet.rs | 849 |
1 files changed, 849 insertions, 0 deletions
diff --git a/crates/imxrt/src/drivers/enet.rs b/crates/imxrt/src/drivers/enet.rs new file mode 100644 index 0000000..611fa51 --- /dev/null +++ b/crates/imxrt/src/drivers/enet.rs @@ -0,0 +1,849 @@ +use crate::Instance; +use crate::ral::enet; +use core::{ + num::NonZeroU32, + pin::Pin, + sync::atomic::{self, Ordering}, +}; + +use fusible::{ + event_flags::{EventFlags, EventFlagsContext, GetOption, SetOption}, + netx_duo::{ + driver, + interface::{Capability, Interface}, + ip::Ip, + packet::{self, PacketChainer, PacketList, PacketListChainView, PacketPool}, + }, +}; + +use rust_threadx_net_phys::{Mdio, Phy}; + +use super::gpio::GpioOutput; + +pub use enet::{RxRing, TxRing, rx_bd::RxBD, tx_bd::TxBD}; + +pub struct EnetContext { + flags: EventFlagsContext<'static>, +} + +const MII_EVENT: NonZeroU32 = NonZeroU32::new(enet::EIR::MII::mask).unwrap(); + +impl EnetContext { + pub const fn new() -> Self { + Self { + flags: EventFlags::context(), + } + } + + #[inline(always)] + pub unsafe fn on_interrupt( + &'static self, + enet: Instance<enet::RegisterBlock>, + ip: &'static Ip, + ) { + unsafe { + let eir = crate::read_reg!(enet, enet, EIR); + crate::write_reg!(enet, enet, EIR, eir); + + if eir & DEFERRED_INTERRUPTS.get() != 0 { + ip.deferred_processing(); + } + + Pin::static_ref(&self.flags) + .assume_created() + .set(eir, SetOption::Or); + } + } + + pub unsafe fn create<P: Phy<EnetMdio>>( + &'static self, + enet: Instance<enet::RegisterBlock>, + tx_ring: &'static [TxBD], + rx_ring: &'static [RxBD], + phy: P, + rst: GpioOutput, + mdio_source_clock_hz: u32, + ) -> Enet<P> { + let flags = EventFlags::create(Pin::static_ref(&self.flags), &Default::default()).unwrap(); + + crate::write_reg!(enet, enet, ECR, RESET: 1); + + const SMI_MDC_FREQUENCY_HZ: u32 = 2_500_000; + let mii_speed = mdio_source_clock_hz.div_ceil(2 * SMI_MDC_FREQUENCY_HZ) - 1; + let hold_time = 10_u32.div_ceil(1_000_000_000 / mdio_source_clock_hz) - 1; + crate::modify_reg!(enet, enet, MSCR, HOLDTIME: hold_time, MII_SPEED: mii_speed); + + Enet { + enet, + phy, + rst, + flags, + + tx_ring: TransmitRing::new(tx_ring), + rx_ring: ReceiveRing::new(rx_ring), + } + } +} + +pub struct EnetMdio { + enet: Instance<enet::RegisterBlock>, + flags: &'static EventFlags, +} + +pub struct Enet<P: Phy<EnetMdio>> { + enet: Instance<enet::RegisterBlock>, + phy: P, + rst: GpioOutput, + flags: &'static EventFlags, + + tx_ring: TransmitRing, + rx_ring: ReceiveRing, +} + +unsafe impl<P: Phy<EnetMdio>> Send for Enet<P> {} + +impl Mdio for EnetMdio { + type Error = core::convert::Infallible; + + fn read(&mut self, ctrl: u16) -> Result<u16, Self::Error> { + let mmfr = (ctrl as u32) << 16; + crate::write_reg!(enet, self.enet, MMFR, mmfr); + + self.flags.get(MII_EVENT, GetOption::AndClear).unwrap(); + + // Automatically discards control bits. + let data = crate::read_reg!(enet, self.enet, MMFR, DATA) as u16; + Ok(data) + } + + fn write(&mut self, ctrl: u16, data: u16) -> Result<(), Self::Error> { + let mmfr = (ctrl as u32) << 16 | data as u32; + crate::write_reg!(enet, self.enet, MMFR, mmfr); + + self.flags.get(MII_EVENT, GetOption::AndClear).unwrap(); + + Ok(()) + } +} + +impl<P: Phy<EnetMdio>> driver::Driver<'static> for Enet<P> { + fn attach_interface( + self: Pin<&'static mut Self>, + mut extras: driver::DriverExtras<'static>, + ) -> Result<(), driver::DriverError> { + extras + .interface + .set_capability( + extras.ip, + Capability::IPV4_RX_CHECKSUM + | Capability::IPV4_TX_CHECKSUM + | Capability::UDP_RX_CHECKSUM + | Capability::UDP_TX_CHECKSUM + | Capability::TCP_RX_CHECKSUM + | Capability::TCP_TX_CHECKSUM, + ) + .unwrap(); + + // TODO: this may need to be bound by MRBR. Otherwise, + // we're lying to the upper levels. + extras.interface.set_mtu(extras.ip, 1500); + extras.interface.driver_needs_mapping(extras.ip); + + Ok(()) + } + + fn initialize_link( + self: Pin<&'static mut Self>, + _: driver::DriverExtras<'static>, + ) -> Result<(), driver::DriverError> { + // Safety: not relocating anything in memory. + let this = unsafe { self.get_unchecked_mut() }; + initialize_enet( + this.enet, + this.tx_ring.buffer_descriptors, + this.rx_ring.buffer_descriptors, + true, + ); + + this.rst.clear(); + fusible::thread::sleep(10); + this.rst.set(); + fusible::thread::sleep(10); + + let mut mdio = EnetMdio { + enet: this.enet.clone(), + flags: this.flags, + }; + + this.phy.initialize(&mut mdio).unwrap(); + + Ok(()) + } + + fn enable_link( + self: Pin<&'static mut Self>, + mut extras: driver::DriverExtras<'static>, + ) -> Result<(), driver::DriverError> { + // Safety: not moving out of self. + let this = unsafe { self.get_unchecked_mut() }; + let mrbr = schedule_receives(&mut this.rx_ring, extras.ip.default_packet_pool()); + assert_ne!(mrbr, 0); + + crate::write_reg!(enet, this.enet, MRBR, mrbr as u32); + crate::write_reg!(enet, this.enet, RDAR, RDAR: 1); + + extras.interface.set_link_up(true); + + Ok(()) + } + + fn set_physical_address( + self: Pin<&'static mut Self>, + extras: driver::DriverExtras<'static>, + ) -> Result<(), driver::DriverError> { + crate::write_reg!(enet, self.enet, PALR, extras.msw << 16 | (extras.lsw >> 16)); + crate::write_reg!(enet, self.enet, PAUR, extras.lsw << 16); + Ok(()) + } + + fn deferred_processing( + self: Pin<&'static mut Self>, + extras: driver::DriverExtras<'static>, + ) -> Result<(), driver::DriverError> { + // Safety: not moving out of this object. + let this = unsafe { self.get_unchecked_mut() }; + let eir = this.flags.try_get(DEFERRED_INTERRUPTS, GetOption::OrClear); + let eir = eir.map_or(0, NonZeroU32::get); + + if eir & TRANSMIT_INTERRUPT_EVENTS != 0 { + this.tx_ring.deallocate_completions(); + } + + if eir & RECEIVE_INTERRUPT_EVENTS != 0 { + while let Some(packet) = this.rx_ring.try_frame_receive() { + enqueue_rx_packet_to_ip(packet, extras.ip, extras.interface); + } + } + + // Make sure receive operations are primed. Otherwise, + // we drop data. Once all descriptors are saturated, + // try transmitting pending packets. + if 0 != schedule_receives(&mut this.rx_ring, extras.ip.default_packet_pool()) { + crate::write_reg!(enet, this.enet, RDAR, RDAR: 1); + } + + this.tx_ring.schedule_transmits(); + crate::write_reg!(enet, this.enet, TDAR, TDAR: 1); + + Ok(()) + } + + fn send_packet( + self: Pin<&'static mut Self>, + extras: driver::DriverExtras<'static>, + ethertype: u16, + ) -> Result<(), driver::DriverError> { + let Some(mut packet) = extras.packet else { + return Err(driver::DriverError::Unhandled); + }; + + prepare_transmit_head(&mut packet, ethertype, extras.msw, extras.lsw); + + // Safety: we're not moving out of this object. + let this = unsafe { self.get_unchecked_mut() }; + this.tx_ring.fifo_pending.push_back(packet); + this.tx_ring.schedule_transmits(); + crate::write_reg!(enet, this.enet, TDAR, TDAR: 1); + + Ok(()) + } +} + +const RECEIVE_INTERRUPT_EVENTS: u32 = enet::EIR::RXB::mask | enet::EIR::RXF::mask; +const TRANSMIT_INTERRUPT_EVENTS: u32 = enet::EIR::TXB::mask | enet::EIR::TXF::mask; + +const DEFERRED_INTERRUPTS: NonZeroU32 = + NonZeroU32::new(RECEIVE_INTERRUPT_EVENTS | TRANSMIT_INTERRUPT_EVENTS).unwrap(); + +/// Initialize the ENET IP block. +/// +/// This assumes that you've already reset the IP block. When this +/// call returns, the ENET block is enabled. This is required for +/// MDIO interrupt activation. Although the IP block is enabled, +/// the call does not initialize any I/O. +fn initialize_enet( + enet: Instance<enet::RegisterBlock>, + tx_ring: &'static [TxBD], + rx_ring: &'static [RxBD], + rmii: bool, +) { + crate::modify_reg!(enet, enet, ECR, + DBSWP: 1, // Swap data for this little endian device. + EN1588: 1, // Use enhanced buffer descriptors. + RESET: 0, // I think this auto-clears, but just in case... + DBGEN: 0, // Keep running the MAC in debug mode. + ); + + // Clear all interrupt flags. + crate::write_reg!(enet, enet, EIR, u32::MAX); + + // Unmask interrupts. + // + // Make sure to update DEFERRED_INTERRUPTS if the event + // needs to signal the IP thread. + crate::write_reg!(enet, enet, EIMR, + // MDIO completions. + MII: 1, + // Receive buffer complete. + RXB: 1, + // Receive frame complete. + RXF: 1, + // Transmit buffer complete. + TXB: 1, + // Transmit frame complete. + TXF: 1, + ); + + // Tell the DMA engine which descriptors are the last + // ones in the ring. + if let Some(tx_tail) = tx_ring.last() { + tx_tail + .flags + .fetch_or(enet::tx_bd::FLAGS_WRAP, Ordering::Relaxed); + } + if let Some(rx_tail) = rx_ring.last() { + rx_tail + .flags + .fetch_or(enet::rx_bd::FLAGS_WRAP, Ordering::Relaxed); + } + + // Establish the ring starting addresses. + crate::write_reg!(enet, enet, TDSR, tx_ring.as_ptr() as u32); + crate::write_reg!(enet, enet, RDSR, rx_ring.as_ptr() as u32); + + crate::modify_reg!(enet, enet, RCR, + // Default max frame length without VLAN tags. + MAX_FL: 1518, + // Disable loopback by default. If we expose half-duplex to + // the user, we wouldn't be able to support this. + LOOP: 0, + // No need to snoop. + PROM: 0, + // Do not reject broadcast frames; we might be interested + // in these. + BC_REJ: 0, + // The MAC doesn't supply pause frames to the application. + PAUFWD: 0, + // Drop padding, along with the CRC, when supplying frames + // to our software. This configuration implicitly includes + // the CRC, so the CRCFWD below has no effect. + PADEN: 1, + // Drop the CRC in received frames. This doesn't turn off + // CRC checking at the hardware level. + // + // If PADEN is set, this configuration does nothing. + CRCFWD: 1, + // Check the payload length based on the expected frame type / + // frame length (encoded in the frame). + NLC: 1, + // Enable flow control; react to pause frames by pausing the data + // transmit paths. + FCE: 1, + // MII or RMII mode; must be set. + MII_MODE: 1, + // Is this an RMII interface? + RMII_MODE: rmii as u32, + // Default to 100Mbit/sec. + RMII_10T: 0, + // For prototyping purposes, we're strictly a full-duplex MAC. + DRT: 0, + ); + + crate::modify_reg!(enet, enet, TCR, + // We told the IP thread to skip software CRCs. Let + // the hardware handle it. + CRCFWD: 0, + // We'll program our source MAC into the PADDR registers. + // It's our job to set up the frame, so we can choose to + // have the hardware inject the source address. + ADDINS: 1, + // For prototyping purposes, we're strictly + // a full-duplex MAC. + FDEN: 1, + ); + + // Enable store-and-forward: start transmitting once you have a complete + // frame in the FIFO. + crate::modify_reg!(enet, enet, TFWR, STRFWD: 1); + // Maintain store-and-forward on the receive path: use the receive queue + // as a buffer until an entire frame is received. + crate::write_reg!(enet, enet, RSFL, 0); + + // These accelerator options assume store-and-forward operations on both + // data paths. See above. + crate::modify_reg!(enet, enet, RACC, + // Discard frames with MAC errors (checksumming, length, PHY errors). + LINEDIS: 1, + // Discard frames with the wrong checksums for the protocol and headers. + PRODIS: 1, + IPDIS: 1, + // Discard any padding within a short IP datagram. + PADREM: 1, + // Insert two extra bytes so that the data section is four byte aligned. + SHIFT16: 1, + ); + crate::modify_reg!(enet, enet, TACC, + // Enable protocol checksums. Assumes that the netx-duo sets these fields + // to zero on our behalf. + PROCHK: 1, + // Enable IP checksum injection into the IPv4 header. Assumes that netx-duo + // sets these fields to zero on our behalf. + IPCHK: 1, + // Expect two extra bytes when transmitting data. + SHIFT16: 1, + ); + + // Enable the IP block. + crate::modify_reg!(enet, enet, ECR, ETHEREN: 1); +} + +/// Set up receive DMA operations for packet reception. +fn schedule_receives(rx_ring: &mut ReceiveRing, packet_pool: &'static PacketPool) -> usize { + let mut data_capacity = 0; + while rx_ring.is_schedulable() + && let Some(packet) = packet_pool + .try_allocate(packet::PacketType::Receive) + .unwrap() + { + data_capacity = packet.data_capacity(); + assert_ne!(data_capacity, 0); + assert!(data_capacity % 64 == 0); + + rx_ring.schedule_next(packet); + } + + data_capacity +} + +/// Give the received packet to the IP instance. +fn enqueue_rx_packet_to_ip( + mut packet: packet::Packet<'static>, + ip: &'static Ip, + interface: Interface<'static>, +) { + // Safety: Interface and packet have same lifetime. + unsafe { packet.set_ip_interface(interface) }; + + // Safety: Assuming no 802.1 tag, the Ethertype enum + // starts at 6 + 6 bytes from the start of the data. + // It uses network byte order. + let ethertype = unsafe { + let prepend_ptr = packet.prepend_ptr().add(12); + (prepend_ptr.read() as u16) << 8 | prepend_ptr.add(1).read() as u16 + }; + + if ![ + driver::ETHERTYPE_ARP, + driver::ETHERTYPE_IPV4, + driver::ETHERTYPE_IPV6, + driver::ETHERTYPE_RARP, + ] + .contains(ðertype) + { + return; // Packet drop deallocates the packet. + } + + // We know how to handle this! Hide the Ethernet header from + // the network stack. + // + // Safety: we know that we just recieved this data and that the + // RX ring removes the two byte padding that starts this data. + // + // Safety: data remains in bounds. We're removing pointers and + // info to access valid data. + unsafe { + packet.set_prepend_ptr(packet.prepend_ptr().add(driver::ETHERNET_FRAME_SIZE)); + packet.set_len(packet.len() - driver::ETHERNET_FRAME_SIZE); + } + + if ethertype == driver::ETHERTYPE_IPV4 || ethertype == driver::ETHERTYPE_IPV6 { + ip.defer_ip_receive(packet); + } else if ethertype == driver::ETHERTYPE_ARP { + ip.defer_arp_receive(packet); + } else if ethertype == driver::ETHERTYPE_RARP { + ip.defer_rarp_receive(packet); + } else { + unreachable!(); + } +} + +fn prepare_transmit_head(packet: &mut packet::Packet<'_>, ethertype: u16, msw: u32, lsw: u32) { + // Make space for an Ethernet header. + // + // Safety: user expected to have allocated this packet + // appropriately. + unsafe { + packet.set_prepend_ptr(packet.prepend_ptr().sub(driver::ETHERNET_FRAME_SIZE)); + packet.set_len(packet.len() + driver::ETHERNET_FRAME_SIZE); + } + + // Fill in the destination MAC and ethertype. + // + // Safety: pointer is in range for all accesses. + unsafe { + let prepend_ptr = packet.prepend_ptr(); + + prepend_ptr.add(0).write((msw >> 8) as u8); + prepend_ptr.add(1).write(msw as u8); + prepend_ptr.add(2).write((lsw >> 24) as u8); + prepend_ptr.add(3).write((lsw >> 16) as u8); + prepend_ptr.add(4).write((lsw >> 8) as u8); + prepend_ptr.add(5).write(lsw as u8); + + // MAC fills in the source address. + + prepend_ptr.add(12).write((ethertype >> 8) as u8); + prepend_ptr.add(13).write(ethertype as u8); + } + + // Make space for our two byte padding. + // + // Safety: NetX assumes a 16 byte allocation for the Ethernet + // header. Therefore, this remains in range. + unsafe { + packet.set_prepend_ptr(packet.prepend_ptr().sub(2)); + packet.set_len(packet.len() + 2); + } +} + +/// Ring state for managing receive operations. +struct ReceiveRing { + buffer_descriptors: &'static [RxBD], + idx_in_flight: usize, + idx_schedulable: usize, + + fifo_in_flight: PacketList<'static>, + fifo_chaining: PacketChainer<'static>, +} + +impl ReceiveRing { + const fn new(buffer_descriptors: &'static [RxBD]) -> Self { + Self { + buffer_descriptors, + idx_in_flight: 0, + idx_schedulable: 0, + fifo_in_flight: PacketList::empty(), + fifo_chaining: PacketChainer::empty(), + } + } + + /// Do we have a descriptor for scheduling a receive? + fn is_schedulable(&self) -> bool { + let rx_bd = &self.buffer_descriptors[self.idx_schedulable]; + let flags = rx_bd.flags.load(Ordering::Relaxed); + flags & enet::rx_bd::FLAGS_EMPTY == 0 && flags & enet::rx_bd::FLAGS_RECEIVE_OWNERSHP_1 == 0 + } + + /// Schedule a receive into the given packet. + /// + /// Assumes we have a schedulable descriptor. This updates + /// the next schedulable descriptor. + fn schedule_next(&mut self, packet: packet::Packet<'static>) { + let rx_bd = &self.buffer_descriptors[self.idx_schedulable]; + + rx_bd.data_length.store(0, Ordering::Relaxed); + rx_bd + .data_buffer_pointer + .store(packet.prepend_ptr() as u32, Ordering::Relaxed); + rx_bd + .control + .fetch_or(enet::rx_bd::CONTROL_INT, Ordering::Relaxed); + rx_bd.flags.fetch_or( + enet::rx_bd::FLAGS_EMPTY | enet::rx_bd::FLAGS_RECEIVE_OWNERSHP_1, + Ordering::Relaxed, + ); + + atomic::fence(Ordering::Release); + + self.fifo_in_flight.push_back(packet); + + self.idx_schedulable = (self.idx_schedulable + 1) % self.buffer_descriptors.len(); + } + + /// Try to receive a frame with one or more packets. + /// + /// If this returns a packet, you should try calling it + /// again; it may be able to produce another packet. + fn try_frame_receive(&mut self) -> Option<packet::Packet<'static>> { + // Show the borrow checker what we're doing. + let Self { + buffer_descriptors, + idx_in_flight, + fifo_in_flight, + fifo_chaining, + .. + } = self; + + // Which receive operations have complete? Pop those packets so we can + // start to form a packet chain. + let completions = core::iter::from_fn(|| { + let rx_bd = &buffer_descriptors[*idx_in_flight]; + let flags = rx_bd.flags.load(Ordering::Relaxed); + let ready = !fifo_in_flight.is_empty() + && flags & enet::rx_bd::FLAGS_EMPTY == 0 + && flags & enet::rx_bd::FLAGS_RECEIVE_OWNERSHP_1 != 0; + + ready.then(|| { + rx_bd + .flags + .fetch_and(!enet::rx_bd::FLAGS_RECEIVE_OWNERSHP_1, Ordering::Relaxed); + + // Panic unlikely. We checked if the FIFO is empty + // when deciding if it's ready. + let packet = fifo_in_flight.pop_front().unwrap(); + *idx_in_flight = (*idx_in_flight + 1) % buffer_descriptors.len(); + + let is_last = flags & enet::rx_bd::FLAGS_LAST != 0; + let total_data_length: usize = rx_bd.data_length.load(Ordering::Relaxed).into(); + + (total_data_length, packet, is_last) + }) + }) + // Loop bound by the number of receive descriptors that could + // possibly be filled. Without this, we could enter the loop, + // observe that all descriptors are filled, and loop forever. + // We must eventually break to prime another batch of descriptors. + .take(buffer_descriptors.len()); + + for (total_data_length, mut packet, is_last) in completions { + // Per the ENET docs, when the last flag is set, the data length describes + // the total length of the entire frame. We only use this when figuring out + // information for the last packet. Note that this includes the two bytes + // of padding. + + if is_last { + // The number of other packets in the chain, + // besides this one. This packet hasn't been + // inserted into the chain, yet, so this can + // be zero, signaling "only packet." + let other_packets = fifo_chaining.len(); + + // If there are other packets, they're saturated + // to capacity. + let data_in_other_packets = other_packets * packet.data_capacity(); + + // The data in the final (only) packet is the total length + // without the data in other packets. This includes the + // two bytes of padding. + let our_data_len = total_data_length - data_in_other_packets; + + // Safety: computation of data in the final packet maintains + // an in-bounds offset into the packet's data. It's known that + // a receive packet is allocated with its data start and prepend + // pointers pointing at the same place. + // + // If this is an only packet, then the offset already accounts + // for the two byte padding that we'll strip from the prepend + // pointer, later. If this is the tail of the packet chain, then + // we already need to handle the extra two bytes that aren't in + // the chain's head packet. + unsafe { + packet.set_append_ptr(packet.prepend_ptr().add(our_data_len)); + } + } else { + // Safety: This is an intermediate packet in the chain. + // Since it's not a last packet, it's not an only packet. + // Therefore, it's been filled to capacity by the DMA + // engine. + // + // Since it's been filled to capacity, the data end pointer + // represents valid data. Module inspection shows that the + // maximum buffer size is the packet capacity. It's known + // that a receive type pointer has its data start and prepend + // pointers pointing at the same address. + unsafe { + packet.set_append_ptr(packet.data_end()); + } + } + + // Insert all packets into the current chain. + // Once we see the last packet, we'll pop the + // chains head, clean it up, and hand it off. + fifo_chaining.push_back(packet); + if is_last { + // Panic unlikely. We just pushed a packet into the FIFO, before + // the branch. + let mut packet = fifo_chaining.pop().unwrap(); + + // Safety: We're trusting the hardware to represent the total + // frame size in this descriptor. We remove the two bytes of + // padding inserted as data. + unsafe { packet.set_len(total_data_length - 2) }; + + // Safety: prepend pointer and its two byte offset are part of + // the same head packet. We're simply telling the netstack + // to skip the invalid two bytes in the front of the packet. + unsafe { packet.set_prepend_ptr(packet.prepend_ptr().add(2)) }; + + // We formed a chained packet. Return it to the user. + // + // If there's more data to process, the user can keep + // calling us. + return Some(packet); + } + } + + // Nothing was ready. + None + } +} + +/// Ring state for managing transmits. +struct TransmitRing { + buffer_descriptors: &'static [TxBD], + + idx_schedulable: usize, + idx_in_flight: usize, + + /// Packets that need to be assigned + /// to a descriptor. + fifo_pending: PacketListChainView<'static>, + /// Packets that are in flight. + fifo_in_flight: PacketList<'static>, +} + +impl TransmitRing { + const fn new(buffer_descriptors: &'static [TxBD]) -> Self { + Self { + buffer_descriptors, + idx_schedulable: 0, + idx_in_flight: 0, + fifo_pending: PacketListChainView::empty(), + fifo_in_flight: PacketList::empty(), + } + } + + /// Try scheduling transmits for a chain of packets. + /// + /// Returns the packet once all packets in its chain + /// have been scheduled. Otherwise, returns `None` if + /// there are packets in the chain that still need + /// scheduling. Keep calling this in a loop to drive + /// packet scheduling. + fn try_schedule_packet_chain(&mut self) -> Option<packet::Packet<'static>> { + // Flags set for the last packet in the frame. + const LAST_FRAME_FLAGS: u16 = + enet::tx_bd::FLAGS_LAST_IN_FRAME | enet::tx_bd::FLAGS_TRANSMIT_CRC; + + let Self { + buffer_descriptors, + idx_schedulable, + fifo_pending, + .. + } = self; + + // True while we're iterating over + // a chain of packets. + while fifo_pending.has_chain_link() { + let tx_bd = &buffer_descriptors[*idx_schedulable]; + + if tx_bd.flags.load(Ordering::Relaxed) & enet::tx_bd::FLAGS_READY != 0 { + // Descriptor is waiting to transmit. + return None; + } + + // Schedule a DMA transfer from the chained packet we're + // looking at. + fifo_pending.with_chain_link(|packet| { + if packet.has_chained_packet() { + // Clear any "last packet" flags set by the prior transfer. + tx_bd.flags.fetch_and(!LAST_FRAME_FLAGS, Ordering::Relaxed); + } else { + // This is the final packet in the chain. + tx_bd.flags.fetch_or(LAST_FRAME_FLAGS, Ordering::Relaxed); + } + + tx_bd + .data_buffer_pointer + .store(packet.prepend_ptr() as u32, Ordering::Relaxed); + tx_bd + .data_length + .store(packet.data_length() as u16, Ordering::Relaxed); + tx_bd + .control + .fetch_or(enet::tx_bd::CONTROL_INT, Ordering::Relaxed); + tx_bd + .flags + .fetch_or(enet::tx_bd::FLAGS_READY, Ordering::Relaxed); + + atomic::fence(Ordering::Release); + }); + + // We used a buffer descriptor. Go to the next one. + *idx_schedulable = (*idx_schedulable + 1) % buffer_descriptors.len(); + + // If this was the last packet in the chain, + // we'll break the loop. Otherwise, we'll keep + // looping while we have ready descriptors. + fifo_pending.advance_chain_link(); + } + + // Either pops the packet chain that's been + // scheduled. Or, it pops None when there's + // nothing in the FIFO. + return fifo_pending.pop_front(); + } + + /// Try to schedule transmit operations on pending packets. + fn schedule_transmits(&mut self) { + while let Some(packet) = self.try_schedule_packet_chain() { + self.fifo_in_flight.push_back(packet); + } + } + + /// Release descriptors and packets that have completed transmission. + fn deallocate_completions(&mut self) { + let completions = core::iter::from_fn(|| { + let tx_bd = &self.buffer_descriptors[self.idx_in_flight]; + let flags = tx_bd.flags.load(Ordering::Relaxed); + + let complete = !self.fifo_in_flight.is_empty() && flags & enet::tx_bd::FLAGS_READY == 0; + + complete.then(|| { + self.idx_in_flight = (self.idx_in_flight + 1) % self.buffer_descriptors.len(); + + let last = flags & enet::tx_bd::FLAGS_LAST_IN_FRAME != 0; + last.then(|| self.fifo_in_flight.pop_front().unwrap()) + }) + }) + // Bound the number of descriptors that could possibly + // complete in one evaluation. Without this, there's a + // chance to loop endlessly when the number of packets + // exceeds the number of available descriptors. Break + // the loop to continue packet scheduling. + .take(self.buffer_descriptors.len()); + + // Loop runs for every complete packet, which may + // not be the last packet. Drive the completions as + // far as possible so we can deallocate the packet + // chain ASAP. + for packet in completions { + if let Some(mut packet) = packet { + // Strip the Ethernet frame and padding before deallocation. + // + // Safety: pointer remains in bounds of an allocation. + unsafe { + packet + .set_prepend_ptr(packet.prepend_ptr().add(driver::ETHERNET_FRAME_SIZE + 2)); + packet.set_len(packet.len() - driver::ETHERNET_FRAME_SIZE - 2); + } + + // Try to release a packet that may need retransmission. + // + // If the release didn't happen, we'll be given back the + // packet through another send call. + packet.transmit_release(); + } + } + } +} |
