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`, …) after successful
96/// 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// ---------------------------------------------------------------------------
104// strate-net wakeup on NIC IRQ (point 1)
105// ---------------------------------------------------------------------------
106
107/// Cached task ID of the strate-net process, registered from the boot
108/// sequence after strate-net is spawned.  Zero = not yet known; the NIC
109/// handler will skip the wakeup and rely on strate-net's periodic polling.
110///
111/// # TODO
112///
113/// Wire up registration : either via a syscall from strate-net itself or
114/// by scanning `get_all_tasks()` from a safe (non-IRQ) context after init
115/// spawns strate-net.
116static STRATE_NET_TID: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
117
118/// Register the strate-net task ID so the NIC IRQ handler can wake it.
119pub fn register_strate_net_tid(tid: u64) {
120    STRATE_NET_TID.store(tid, core::sync::atomic::Ordering::Relaxed);
121    log::info!("NIC: strate-net task {} registered for IRQ wakeup", tid);
122}
123
124/// Called from `idt.rs:nic_handler`.  Dispatches to the registered NIC's
125/// `handle_interrupt()` (reads ICR, coalescing, TX reclaim).
126/// If the N2 data plane is active, drains received packets into the RX
127/// ring before waking strate-net (zero-syscall data path).
128pub fn handle_interrupt() {
129    if let Some(ref dev) = *NIC_DEVICE.lock() {
130        dev.handle_interrupt();
131        // Drain pending RX packets into the N2 data plane ring.
132        if let Some(ref dp) = *NIC_DATA_PLANE.lock() {
133            let mut buf = [0u8; 2048];
134            while let Ok(n) = dev.receive(&mut buf) {
135                if dp.push_rx(0, &buf[..n]).is_err() {
136                    break; // ring full : backpressure
137                }
138            }
139        }
140    }
141    let tid_u64 = STRATE_NET_TID.load(core::sync::atomic::Ordering::Relaxed);
142    if tid_u64 != 0 {
143        let _ = crate::process::scheduler::wake_task(crate::process::TaskId(tid_u64));
144    }
145}
146
147/// Performs the register device operation.
148pub fn register_device(device: Arc<dyn NetworkDevice>) -> String {
149    let prefix = bsd_prefix(device.name());
150    let idx = next_index_for(prefix);
151    let iface = format!("{}{}", prefix, idx);
152    let mac = device.mac_address();
153    log::info!(
154        "[net] {} -> {} (MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})",
155        device.name(),
156        iface,
157        mac[0],
158        mac[1],
159        mac[2],
160        mac[3],
161        mac[4],
162        mac[5],
163    );
164    let mut devs = NET_DEVICES.write();
165    devs.push(NetDeviceEntry {
166        iface: iface.clone(),
167        device,
168    });
169    iface
170}
171
172/// Returns device.
173pub fn get_device(name: &str) -> Option<Arc<dyn NetworkDevice>> {
174    NET_DEVICES
175        .read()
176        .iter()
177        .find(|e| e.iface == name)
178        .map(|e| e.device.clone())
179}
180
181/// Returns default device.
182pub fn get_default_device() -> Option<Arc<dyn NetworkDevice>> {
183    NET_DEVICES.read().first().map(|e| e.device.clone())
184}
185
186/// Performs the list interfaces operation.
187pub fn list_interfaces() -> Vec<String> {
188    NET_DEVICES.read().iter().map(|e| e.iface.clone()).collect()
189}
190
191/// Call `poll()` on every registered NIC (watchdog + link check).
192/// Safe to call from any non-IRQ context (allocates via get_all_tasks).
193///
194/// # TODO
195///
196/// Hook this into a kernel workqueue or periodic thread instead of
197/// the APIC timer tick : SpinLock::lock reads percpu data (GS:[0])
198/// which is unsafe during the swapgs→iretq window.
199pub fn poll_all() {
200    let guard = NET_DEVICES.read();
201    for entry in guard.iter() {
202        entry.device.poll();
203    }
204}
205
206/// Try to discover strate-net and cache its task ID.
207pub fn try_register_strate_net() {
208    if STRATE_NET_TID.load(core::sync::atomic::Ordering::Relaxed) != 0 {
209        return;
210    }
211    if let Some(tasks) = crate::process::get_all_tasks() {
212        for t in &tasks {
213            if t.name == "strate-net" {
214                register_strate_net_tid(t.id.0);
215                return;
216            }
217        }
218    }
219}
220
221/// Performs the init operation.
222pub fn init() {
223    log::info!("[net] Scanning for network devices...");
224    // Probe modern Intel first, then legacy fallback.
225    e1000e_drv::init();
226    igc_drv::init();
227    e1000_drv::init();
228    pcnet_drv::init();
229    rtl8139_drv::init();
230    virtio_net::init();
231    if let Err(e) = scheme::register_net_scheme() {
232        log::warn!("[net] Failed to register net scheme: {:?}", e);
233    }
234    // Initialise the N2 data plane if any NIC was registered.
235    init_data_plane();
236}
237
238// ── N2 data-plane global instance ─────────────────────────────────────────
239
240use data_plane::NicDataPlane;
241use spin::Mutex;
242
243/// Global NIC data plane, lazily initialised after NIC detection.
244static NIC_DATA_PLANE: Mutex<Option<NicDataPlane>> = Mutex::new(None);
245
246/// Initialise the N2 data plane with one ring pair per registered device.
247/// Each device gets a single RX/TX pair (RSS queues extend this).
248fn init_data_plane() {
249    let count = NET_DEVICES.read().len();
250    if count == 0 {
251        log::debug!("[net] No NIC devices found : skipping data plane init");
252        return;
253    }
254    // One ring pair per NIC for now; RSS would create one per queue.
255    match NicDataPlane::new(count, 256, 2048) {
256        Ok(dp) => {
257            *NIC_DATA_PLANE.lock() = Some(dp);
258            log::info!("[net] N2 data plane initialised with {} queue(s)", count);
259        }
260        Err(e) => {
261            log::warn!("[net] Failed to initialise N2 data plane: {}", e);
262        }
263    }
264}
265
266/// Access the global N2 data plane (returns None if not yet initialised).
267pub fn data_plane() -> &'static Mutex<Option<NicDataPlane>> {
268    &NIC_DATA_PLANE
269}