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}