Skip to main content

strat9_kernel/hardware/nic/
mod.rs

1//! Network driver integration layer.
2//!
3//! Thin kernel glue that wires external crates (`net-core`, `e1000`, …)
4//! to kernel services (PCI, DMA allocator, VFS schemes).
5
6pub mod common;
7pub mod data_plane;
8pub mod e1000_drv;
9pub mod e1000e_drv;
10pub mod igc_drv;
11pub mod pcnet_drv;
12pub mod rtl8139_drv;
13pub mod scheme;
14pub mod virtio_net;
15
16pub use net_core::{NetError, NetworkDevice, MTU};
17
18use alloc::{format, string::String, sync::Arc, vec::Vec};
19use spin::RwLock;
20
21struct NetDeviceEntry {
22    iface: String,
23    device: Arc<dyn NetworkDevice>,
24}
25
26static NET_DEVICES: RwLock<Vec<NetDeviceEntry>> = RwLock::new(Vec::new());
27
28/// Map a driver name to a FreeBSD-style interface prefix.
29///
30/// | Driver          | Prefix   | Example |
31/// |-----------------|----------|---------|
32/// | e1000 / Intel   | `em`     | `em0`   |
33/// | VirtIO-net      | `vtnet`  | `vtnet0`|
34/// | (other)         | `net`    | `net0`  |
35fn bsd_prefix(driver_name: &str) -> &'static str {
36    let lower = driver_name.as_bytes();
37    // Match common patterns without pulling in a full lowercase comparison
38    if lower.len() >= 4
39        && (lower[0] | 0x20) == b'e'
40        && (lower[1] | 0x20) == b'1'
41        && lower[2] == b'0'
42        && lower[3] == b'0'
43    {
44        return "em"; // Intel PRO/1000 family
45    }
46    if lower.len() >= 6
47        && (lower[0] | 0x20) == b'v'
48        && (lower[1] | 0x20) == b'i'
49        && (lower[2] | 0x20) == b'r'
50        && (lower[3] | 0x20) == b't'
51        && (lower[4] | 0x20) == b'i'
52        && (lower[5] | 0x20) == b'o'
53    {
54        return "vtnet"; // VirtIO
55    }
56    "net" // fallback
57}
58
59/// Counters per-prefix so that `em0`, `em1`, `vtnet0` are independent.
60static PREFIX_COUNTERS: RwLock<Vec<(String, usize)>> = RwLock::new(Vec::new());
61
62/// Performs the next index for operation.
63fn next_index_for(prefix: &str) -> usize {
64    let mut counters = PREFIX_COUNTERS.write();
65    for entry in counters.iter_mut() {
66        if entry.0 == prefix {
67            let idx = entry.1;
68            entry.1 += 1;
69            return idx;
70        }
71    }
72    counters.push((String::from(prefix), 1));
73    0
74}
75
76// ---------------------------------------------------------------------------
77// NIC interrupt dispatch (see idt.rs:nic_handler)
78// ---------------------------------------------------------------------------
79
80/// IRQ line of the first registered NIC.  Written once by the NIC driver's
81/// `init()`; read by `nic_handler` in the IDT to send EOI.
82pub static NIC_IRQ_LINE: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0xFF);
83
84/// Global reference to the first NIC device, used by `nic_handler` to call
85/// `handle_interrupt()`.  Set via `set_nic_device()` after PCI probe.
86///
87/// # TODO (multi-NIC)
88///
89/// This only supports a single NIC.  When multiple NICs are present the
90/// handler should iterate all registered devices or use per-IRQ dispatch.
91static NIC_DEVICE: spin::Mutex<Option<Arc<dyn NetworkDevice>>> = spin::Mutex::new(None);
92
93/// Store a NIC device reference and its IRQ line for the IDT handler.
94///
95/// Called from NIC drivers (`e1000_drv`, `e1000e_drv`, `virtio_net`, …)
96/// after successful initialisation.
97pub fn set_nic_device(dev: Arc<dyn NetworkDevice>, irq: u8) {
98    NIC_IRQ_LINE.store(irq, core::sync::atomic::Ordering::Relaxed);
99    *NIC_DEVICE.lock() = Some(dev);
100    log::info!("NIC dispatch set for IRQ {}", irq);
101}
102
103/// True when a NIC IRQ handler is registered (N2 rings will be drained).
104///
105/// Syscall TX uses the N2 ring only in this case; otherwise it transmits
106/// directly so packets are not stuck in an undrained ring.
107pub fn nic_irq_ready() -> bool {
108    NIC_DEVICE.lock().is_some()
109}
110
111// ---------------------------------------------------------------------------
112// strate-net wakeup on NIC IRQ (point 1)
113// ---------------------------------------------------------------------------
114
115/// Cached task ID of the strate-net process, registered from the boot
116/// sequence after strate-net is spawned.  Zero = not yet known; the NIC
117/// handler will skip the wakeup and rely on strate-net's periodic polling.
118///
119/// # TODO
120///
121/// Wire up registration : either via a syscall from strate-net itself or
122/// by scanning `get_all_tasks()` from a safe (non-IRQ) context after init
123/// spawns strate-net.
124static STRATE_NET_TID: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
125
126/// Register the strate-net task ID so the NIC IRQ handler can wake it.
127pub fn register_strate_net_tid(tid: u64) {
128    STRATE_NET_TID.store(tid, core::sync::atomic::Ordering::Relaxed);
129    log::info!("NIC: strate-net task {} registered for IRQ wakeup", tid);
130}
131
132/// Called from `idt.rs:nic_handler`.  Dispatches to the registered NIC's
133/// `handle_interrupt()` (reads ICR, coalescing, TX reclaim).
134/// If the N2 data plane is active, drains received packets into the RX
135/// ring before waking strate-net (zero-syscall data path).
136pub fn handle_interrupt() {
137    // Phase 1 : hardware interrupt handling (brief, must hold NIC_DEVICE lock).
138    let dev = {
139        let guard = NIC_DEVICE.lock();
140        guard.as_ref().map(|d| {
141            d.handle_interrupt();
142            d.clone()
143        })
144    };
145    let dev = match dev {
146        Some(d) => d,
147        None => {
148            crate::serial_println!("[net] IRQ: no NIC device registered");
149            return;
150        }
151    };
152
153    // Phase 2 : drain N2 rings (no NIC_DEVICE lock held, IRQs may still be
154    // disabled by the IDT entry). Holding only NIC_DATA_PLANE here.
155    if let Some(ref dp) = *NIC_DATA_PLANE.lock() {
156        // Bounded: this runs in interrupt context, so a driver that keeps
157        // reporting a packet would otherwise spin here forever.
158        const MAX_IRQ_DRAIN: usize = 32;
159        let mut buf = [0u8; 2048];
160        let mut rx_count = 0usize;
161        let mut backpressure = false;
162        while rx_count < MAX_IRQ_DRAIN {
163            let n = match dev.receive(&mut buf) {
164                Ok(0) | Err(_) => break,
165                Ok(n) => n,
166            };
167            rx_count += 1;
168            if rx_count <= 3 {
169                crate::serial_println!("[net] IRQ rx {} bytes (slot {})", n, rx_count);
170            }
171            if dp.push_rx(0, &buf[..n]).is_err() {
172                crate::serial_println!("[net] IRQ RX ring full, backpressure");
173                backpressure = true;
174                break;
175            }
176        }
177        if rx_count > 3 {
178            crate::serial_println!("[net] IRQ rx total {} packets", rx_count);
179        }
180        if rx_count == 0 {
181            crate::serial_println!("[net] IRQ rx: no packets received from HW");
182        }
183
184        // N1 : notify scheduler if the RX ring is full (backpressure).
185        if backpressure {
186            crate::ipc::n1::notify_scheduler(crate::ipc::n1::N1Event::NicBackpressure);
187        }
188
189        // Notify consumer (strate-net) that new RX data is available.
190        if rx_count > 0 {
191            dp.notify_rx_consumer(0);
192        }
193
194        // Drain pending TX packets from the N2 TX ring into HW.
195        let mut tx_buf = [0u8; 2048];
196        while let Ok(Some(n)) = dp.pop_tx(0, &mut tx_buf) {
197            if dev.transmit(&tx_buf[..n]).is_err() {
198                break;
199            }
200        }
201
202        // N1 : check for scheduler flow-control hints (non-blocking).
203        if let Some(event) = crate::ipc::n1::poll_nic_events() {
204            log::trace!("[net] N1 sched=>NIC event: {:?}", event);
205        }
206    } else {
207        crate::serial_println!("[net] IRQ: N2 data plane not initialized");
208    }
209
210    let tid_u64 = STRATE_NET_TID.load(core::sync::atomic::Ordering::Relaxed);
211    if tid_u64 != 0 {
212        let _ = crate::process::scheduler::wake_task(crate::process::TaskId(tid_u64));
213    } else {
214        crate::serial_println!("[net] IRQ: strate-net not registered (TID=0)");
215    }
216}
217
218/// Performs the register device operation.
219pub fn register_device(device: Arc<dyn NetworkDevice>) -> String {
220    let prefix = bsd_prefix(device.name());
221    let idx = next_index_for(prefix);
222    let iface = format!("{}{}", prefix, idx);
223    let mac = device.mac_address();
224    log::info!(
225        "[net] {} -> {} (MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})",
226        device.name(),
227        iface,
228        mac[0],
229        mac[1],
230        mac[2],
231        mac[3],
232        mac[4],
233        mac[5],
234    );
235    let mut devs = NET_DEVICES.write();
236    devs.push(NetDeviceEntry {
237        iface: iface.clone(),
238        device,
239    });
240    iface
241}
242
243/// Returns device.
244pub fn get_device(name: &str) -> Option<Arc<dyn NetworkDevice>> {
245    NET_DEVICES
246        .read()
247        .iter()
248        .find(|e| e.iface == name)
249        .map(|e| e.device.clone())
250}
251
252/// Returns default device.
253pub fn get_default_device() -> Option<Arc<dyn NetworkDevice>> {
254    NET_DEVICES.read().first().map(|e| e.device.clone())
255}
256
257/// Performs the list interfaces operation.
258pub fn list_interfaces() -> Vec<String> {
259    NET_DEVICES.read().iter().map(|e| e.iface.clone()).collect()
260}
261
262/// Call `poll()` on every registered NIC (watchdog + link check).
263/// Safe from deferred-work / idle / syscall-return contexts only —
264/// not from the hardirq swapgs window.
265pub fn poll_all() {
266    {
267        let guard = NET_DEVICES.read();
268        for entry in guard.iter() {
269            entry.device.poll();
270        }
271    }
272
273    // When no IRQ path is wired, opportunistically drain HW into N2
274    // (and N2 TX onto the wire) so strate-net keeps making progress.
275    if !nic_irq_ready() {
276        service_data_plane_polling();
277    }
278}
279
280/// Drain RX HW → N2 and N2 TX → HW without relying on a NIC IRQ.
281fn service_data_plane_polling() {
282    let device = match get_default_device() {
283        Some(d) => d,
284        None => return,
285    };
286    let dp_guard = NIC_DATA_PLANE.lock();
287    let dp = match dp_guard.as_ref() {
288        Some(dp) => dp,
289        None => {
290            // No N2: direct RX is handled by sys_net_recv itself.
291            return;
292        }
293    };
294
295    let mut buf = [0u8; 2048];
296    let mut rx_count = 0usize;
297    while rx_count < 8 {
298        let n = match device.receive(&mut buf) {
299            Ok(0) | Err(_) => break,
300            Ok(n) => n,
301        };
302        if dp.push_rx(0, &buf[..n]).is_err() {
303            break;
304        }
305        rx_count += 1;
306    }
307    if rx_count > 0 {
308        dp.notify_rx_consumer(0);
309    }
310
311    let mut tx_buf = [0u8; 2048];
312    while let Ok(Some(n)) = dp.pop_tx(0, &mut tx_buf) {
313        if device.transmit(&tx_buf[..n]).is_err() {
314            break;
315        }
316    }
317}
318
319/// Try to discover strate-net and cache its task ID.
320pub fn try_register_strate_net() {
321    if STRATE_NET_TID.load(core::sync::atomic::Ordering::Relaxed) != 0 {
322        return;
323    }
324    if let Some(tasks) = crate::process::get_all_tasks() {
325        for t in &tasks {
326            if t.name == "strate-net" {
327                register_strate_net_tid(t.id.0);
328                return;
329            }
330        }
331    }
332}
333
334/// Performs the init operation.
335pub fn init() {
336    log::info!("[net] Scanning for network devices...");
337    // Probe modern Intel first, then legacy fallback.
338    e1000e_drv::init();
339    igc_drv::init();
340    e1000_drv::init();
341    pcnet_drv::init();
342    rtl8139_drv::init();
343    virtio_net::init();
344    if let Err(e) = scheme::register_net_scheme() {
345        log::warn!("[net] Failed to register net scheme: {:?}", e);
346    }
347    // Initialise the N2 data plane if any NIC was registered.
348    init_data_plane();
349}
350
351// ── N2 data-plane global instance ─────────────────────────────────────────
352
353use data_plane::NicDataPlane;
354use spin::Mutex;
355
356/// Global NIC data plane, lazily initialised after NIC detection.
357static NIC_DATA_PLANE: Mutex<Option<NicDataPlane>> = Mutex::new(None);
358
359/// Initialise the N2 data plane with one ring pair per registered device.
360/// Each device gets a single RX/TX pair (RSS queues extend this).
361fn init_data_plane() {
362    let count = NET_DEVICES.read().len();
363    if count == 0 {
364        log::debug!("[net] No NIC devices found : skipping data plane init");
365        return;
366    }
367    // One ring pair per NIC for now; RSS would create one per queue.
368    match NicDataPlane::new(count, 256, 2048) {
369        Ok(dp) => {
370            *NIC_DATA_PLANE.lock() = Some(dp);
371            log::info!("[net] N2 data plane initialised with {} queue(s)", count);
372        }
373        Err(e) => {
374            log::warn!("[net] Failed to initialise N2 data plane: {}", e);
375        }
376    }
377}
378
379/// Access the global N2 data plane (returns None if not yet initialised).
380pub fn data_plane() -> &'static Mutex<Option<NicDataPlane>> {
381    &NIC_DATA_PLANE
382}