Skip to main content

strat9_kernel/hardware/nic/
data_plane.rs

1//! NIC data-plane using lock-free SPSC rings (N2).
2//!
3//! Each RSS queue gets a dedicated RX/TX `LockFreeRing` pair.  The NIC
4//! driver writes received packets into the RX ring during interrupt
5//! handling; the networking silo (strate-net) reads from the RX ring and
6//! writes to the TX ring without kernel syscalls.
7//!
8//! # Usage
9//!
10//! ```ignore
11//! let dp = NicDataPlane::new(queue_count, slot_size)?;
12//! // In IRQ handler:
13//! while let Some(pkt) = read_packet_from_hw() {
14//!     dp.push_rx(queue_id, &pkt);
15//! }
16//! dp.notify_consumer(queue_id);
17//!
18//! // In strate-net:
19//! let pkt = dp.pop_rx(0, &mut buf)?;
20//! dp.push_tx(0, &response)?;
21//! ```
22
23use alloc::{sync::Arc, vec::Vec};
24
25use crate::ipc::lockfree_ring::{LockFreeRing, RingError};
26
27/// A pair of RX/TX rings for one RSS queue.
28pub struct RingPair {
29    /// Inbound packets (NIC => strate-net).
30    pub rx: Arc<LockFreeRing>,
31    /// Outbound packets (strate-net => NIC).
32    pub tx: Arc<LockFreeRing>,
33}
34
35/// Multi-queue NIC data plane.
36///
37/// Initialised during NIC driver setup.  Each hardware queue gets one
38/// `RingPair`; the consumer (strate-net) polls all RX rings in round-robin.
39pub struct NicDataPlane {
40    /// One ring pair per RSS queue.
41    pub queues: Vec<RingPair>,
42}
43
44impl NicDataPlane {
45    /// Create a new data plane with `queue_count` ring pairs.
46    ///
47    /// Each ring has `slot_count` slots of `slot_size` bytes.
48    /// Returns an error if ring allocation fails.
49    pub fn new(
50        queue_count: usize,
51        slot_count: u32,
52        slot_size: usize,
53    ) -> Result<Self, &'static str> {
54        let mut queues = Vec::with_capacity(queue_count);
55        for _ in 0..queue_count {
56            let rx = LockFreeRing::new(slot_count, slot_size)
57                .map_err(|_| "failed to allocate RX ring")?;
58            let tx = LockFreeRing::new(slot_count, slot_size)
59                .map_err(|_| "failed to allocate TX ring")?;
60            queues.push(RingPair { rx, tx });
61        }
62        Ok(NicDataPlane { queues })
63    }
64
65    /// Push a received packet into the RX ring for `queue_index`.
66    ///
67    /// Called from the NIC IRQ handler.  Returns `Err(RingError::Full)` if
68    /// the ring is full (backpressure : drop the packet), or
69    /// `Err(RingError::InvalidParameters)` if the queue index is out of range.
70    pub fn push_rx(&self, queue_index: usize, data: &[u8]) -> Result<(), RingError> {
71        let pair = self
72            .queues
73            .get(queue_index)
74            .ok_or(RingError::InvalidParameters)?;
75        pair.rx.write(data)
76    }
77
78    /// Pop a received packet from the RX ring for `queue_index`.
79    ///
80    /// Called by strate-net.  Returns `Ok(None)` if the ring is empty.
81    pub fn pop_rx(&self, queue_index: usize, buf: &mut [u8]) -> Result<Option<usize>, RingError> {
82        let pair = self
83            .queues
84            .get(queue_index)
85            .ok_or(RingError::InvalidParameters)?;
86        pair.rx.try_read(buf)
87    }
88
89    /// Push an outbound packet into the TX ring for `queue_index`.
90    ///
91    /// Called by strate-net.  The NIC driver will read from the TX ring
92    /// and transmit onto the wire.
93    pub fn push_tx(&self, queue_index: usize, data: &[u8]) -> Result<(), RingError> {
94        let pair = self
95            .queues
96            .get(queue_index)
97            .ok_or(RingError::InvalidParameters)?;
98        pair.tx.write(data)
99    }
100
101    /// Pop an outbound packet from the TX ring for `queue_index`.
102    ///
103    /// Called by the NIC driver.  Returns `Ok(None)` if the ring is empty.
104    pub fn pop_tx(&self, queue_index: usize, buf: &mut [u8]) -> Result<Option<usize>, RingError> {
105        let pair = self
106            .queues
107            .get(queue_index)
108            .ok_or(RingError::InvalidParameters)?;
109        pair.tx.try_read(buf)
110    }
111
112    /// Notify the consumer (strate-net) that new RX data is available.
113    pub fn notify_rx_consumer(&self, queue_index: usize) {
114        if let Some(pair) = self.queues.get(queue_index) {
115            pair.rx.notify_consumer_raw();
116        }
117    }
118
119    /// Notify the NIC driver that new TX data is available.
120    pub fn notify_tx_producer(&self, queue_index: usize) {
121        if let Some(pair) = self.queues.get(queue_index) {
122            pair.tx.notify_producer_raw();
123        }
124    }
125
126    /// Number of queues (ring pairs).
127    pub fn queue_count(&self) -> usize {
128        self.queues.len()
129    }
130}