Skip to main content

ax_net/device/
driver.rs

1//! Protocol-side Ethernet frame-port contract.
2//!
3//! Hardware queue ownership lives in the queue runtime.  The single protocol
4//! executor only sees move-only DMA tokens exchanged through bounded SPSC
5//! rings; it never calls a NIC queue or an IRQ endpoint directly.
6
7use alloc::{boxed::Box, sync::Arc, vec::Vec};
8
9pub use rd_net::{DmaBuffer, RxCompletion, TxChecksumCapabilities, TxNotify, TxSubmitOptions};
10
11/// Minimum Ethernet frame length on the wire, excluding the FCS.
12pub(crate) const ETH_ZLEN: usize = 60;
13/// Maximum Ethernet frame transferred across the protocol boundary.
14pub(crate) const ETHERNET_FRAME_CAPACITY: usize = 2048;
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
17pub enum NetDeviceError {
18    /// The bounded frame ring or DMA token pool is temporarily exhausted.
19    #[error("network frame port should be retried")]
20    Again,
21    /// The port has been stopped and rejects new traffic.
22    #[error("network frame port is stopped")]
23    Stopped,
24    /// Caller supplied a frame size outside the queue contract.
25    #[error("invalid network frame size")]
26    InvalidParam,
27    /// Driver or DMA processing failed.
28    #[error("network frame port I/O failed")]
29    Io,
30    /// A required DMA allocation could not be obtained.
31    #[error("network frame port memory allocation failed")]
32    NoMemory,
33}
34
35pub type NetDeviceResult<T = ()> = Result<T, NetDeviceError>;
36
37/// Inline compatibility frame used by non-DMA ports and tests.
38///
39/// Queue-backed ports use the callback methods on [`EthernetFramePort`] so TX
40/// is filled directly in DMA storage and RX is consumed before its token is
41/// returned. This bounded object remains available for adapters that cannot
42/// expose borrowed queue storage.
43#[derive(Clone)]
44pub struct ProtocolEthernetFrame {
45    bytes: [u8; ETHERNET_FRAME_CAPACITY],
46    len: usize,
47}
48
49impl ProtocolEthernetFrame {
50    pub fn new(len: usize) -> NetDeviceResult<Self> {
51        if len > ETHERNET_FRAME_CAPACITY {
52            return Err(NetDeviceError::InvalidParam);
53        }
54        Ok(Self {
55            bytes: [0; ETHERNET_FRAME_CAPACITY],
56            len,
57        })
58    }
59
60    pub fn packet(&self) -> &[u8] {
61        &self.bytes[..self.len]
62    }
63
64    pub fn packet_mut(&mut self) -> &mut [u8] {
65        &mut self.bytes[..self.len]
66    }
67
68    pub fn packet_len(&self) -> usize {
69        self.len
70    }
71
72    pub(crate) fn copy_from_slice(packet: &[u8]) -> NetDeviceResult<Self> {
73        let mut frame = Self::new(packet.len())?;
74        frame.packet_mut().copy_from_slice(packet);
75        Ok(frame)
76    }
77}
78
79/// Queue-runtime endpoint that accepts an RX DMA token after protocol use.
80pub(crate) trait RxBufferRecycler: Send + Sync {
81    fn recycle(&self, buffer: DmaBuffer);
82}
83
84/// Complete Ethernet frame backed by an owned receive DMA token.
85///
86/// Dropping this value returns the token to its queue-local recycler. This
87/// lets the token remain owned through smoltcp's `RxToken::consume` without
88/// exposing NIC queue state to the protocol executor.
89pub struct ProtocolRxFrame {
90    completion: Option<RxCompletion>,
91    recycler: Arc<dyn RxBufferRecycler>,
92}
93
94impl ProtocolRxFrame {
95    pub(crate) fn new(completion: RxCompletion, recycler: Arc<dyn RxBufferRecycler>) -> Self {
96        debug_assert!(completion.packet_len <= completion.buffer.capacity());
97        Self {
98            completion: Some(completion),
99            recycler,
100        }
101    }
102
103    /// Returns the received L2 frame length excluding FCS.
104    pub fn packet_len(&self) -> usize {
105        self.completion
106            .as_ref()
107            .expect("owned RX frame lost its DMA token")
108            .packet_len
109    }
110
111    /// Borrows the received frame while this value retains DMA ownership.
112    pub fn read_with<R>(&self, consume: impl FnOnce(&[u8]) -> R) -> R {
113        let completion = self
114            .completion
115            .as_ref()
116            .expect("owned RX frame lost its DMA token");
117        completion
118            .buffer
119            .read_with_cpu(completion.packet_len, consume)
120    }
121}
122
123impl Drop for ProtocolRxFrame {
124    fn drop(&mut self) {
125        if let Some(completion) = self.completion.take() {
126            self.recycler.recycle(completion.buffer);
127        }
128    }
129}
130
131/// Device-level protocol endpoint backed by one or more queue-group SPSC
132/// pipelines.
133pub trait EthernetFramePort: Send + 'static {
134    /// Stable portable-driver name used for configuration matching.
135    fn device_name(&self) -> &str;
136
137    /// Link-layer address captured during atomic initialization.
138    fn mac_address(&self) -> [u8; 6];
139
140    /// Returns transport checksums available on every TX queue of this port.
141    fn checksum_capabilities(&self) -> TxChecksumCapabilities {
142        TxChecksumCapabilities::NONE
143    }
144
145    /// Publishes one complete Ethernet frame to exactly one queue group's
146    /// TX-ready ring.
147    fn transmit(&mut self, frame: &ProtocolEthernetFrame) -> NetDeviceResult;
148
149    /// Fills a queue-owned DMA token and publishes it for transmission.
150    ///
151    /// Queue-backed ports override this method so `fill` writes directly into
152    /// DMA storage. The compatibility implementation retains the inline frame
153    /// path for non-DMA ports and tests.
154    fn transmit_frame_with_options(
155        &mut self,
156        frame_len: usize,
157        options: TxSubmitOptions,
158        fill: &mut dyn FnMut(&mut [u8]),
159    ) -> NetDeviceResult {
160        if options.checksum.is_some() {
161            return Err(NetDeviceError::InvalidParam);
162        }
163        let mut frame = ProtocolEthernetFrame::new(frame_len)?;
164        fill(frame.packet_mut());
165        self.transmit(&frame)
166    }
167
168    /// Takes one completed RX frame, if any.
169    fn receive(&mut self) -> NetDeviceResult<ProtocolEthernetFrame>;
170
171    /// Takes RX drops accumulated before protocol delivery, for device statistics.
172    fn drain_rx_drops(&mut self) -> u64 {
173        0
174    }
175
176    /// Takes one completed frame with its DMA ownership token when supported.
177    ///
178    /// `Ok(None)` selects the compatibility [`receive`](Self::receive) path.
179    /// Queue-backed ports return `Err(Again)` while their owned path is idle.
180    fn receive_owned(&mut self) -> NetDeviceResult<Option<ProtocolRxFrame>> {
181        Ok(None)
182    }
183
184    /// Consumes one completed frame while its DMA token is borrowed locally.
185    ///
186    /// Queue-backed ports override this method to avoid copying into an inline
187    /// frame before the protocol adapter consumes it.
188    fn receive_with(&mut self, consume: &mut dyn FnMut(&[u8]) -> usize) -> NetDeviceResult<usize> {
189        let frame = self.receive()?;
190        Ok(consume(frame.packet()))
191    }
192}
193
194/// Protocol endpoints handed to the unique smoltcp owner during startup.
195pub type EthernetFramePortList = Vec<Box<dyn EthernetFramePort>>;