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;
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` if the ring is full
68    /// (backpressure : drop the packet).
69    pub fn push_rx(&self, queue_index: usize, data: &[u8]) -> Result<(), ()> {
70        self.queues
71            .get(queue_index)
72            .ok_or(())?
73            .rx
74            .write(data)
75            .map_err(|_| ())
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>, ()> {
82        self.queues
83            .get(queue_index)
84            .ok_or(())?
85            .rx
86            .try_read(buf)
87            .map_err(|_| ())
88    }
89
90    /// Push an outbound packet into the TX ring for `queue_index`.
91    ///
92    /// Called by strate-net.  The NIC driver will read from the TX ring
93    /// and transmit onto the wire.
94    pub fn push_tx(&self, queue_index: usize, data: &[u8]) -> Result<(), ()> {
95        self.queues
96            .get(queue_index)
97            .ok_or(())?
98            .tx
99            .write(data)
100            .map_err(|_| ())
101    }
102
103    /// Pop an outbound packet from the TX ring for `queue_index`.
104    ///
105    /// Called by the NIC driver.  Returns `Ok(None)` if the ring is empty.
106    pub fn pop_tx(&self, queue_index: usize, buf: &mut [u8]) -> Result<Option<usize>, ()> {
107        self.queues
108            .get(queue_index)
109            .ok_or(())?
110            .tx
111            .try_read(buf)
112            .map_err(|_| ())
113    }
114
115    /// Notify the consumer (strate-net) that new RX data is available.
116    pub fn notify_rx_consumer(&self, queue_index: usize) {
117        if let Some(pair) = self.queues.get(queue_index) {
118            pair.rx.notify_consumer_raw();
119        }
120    }
121
122    /// Notify the NIC driver that new TX data is available.
123    pub fn notify_tx_producer(&self, queue_index: usize) {
124        if let Some(pair) = self.queues.get(queue_index) {
125            pair.tx.notify_producer_raw();
126        }
127    }
128
129    /// Number of queues (ring pairs).
130    pub fn queue_count(&self) -> usize {
131        self.queues.len()
132    }
133}