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}