Skip to main content

strat9_kernel/hardware/nic/
common.rs

1//! Shared utilities for Intel E1000-family NIC drivers.
2//!
3//! Contains the kernel DMA allocator, the common PCI probe + MMIO
4//! mapping logic, and a generic `KernelE1000Adapter` that implements
5//! `NetworkDevice` for both the legacy e1000 and the e1000e drivers.
6
7use crate::{
8    arch::xshim::VirtAddr,
9    hardware::pci_client::{self as pci, Bar, PciDevice},
10    memory::{self},
11    sync::SpinLock,
12};
13use alloc::sync::Arc;
14use e1000::E1000Nic;
15use net_core::{NetError, NetworkDevice};
16use nic_buffers::{DmaAllocator, DmaRegion};
17
18/// Kernel-side DMA allocator backed by the physical buddy allocator.
19pub struct KernelDma;
20
21impl DmaAllocator for KernelDma {
22    /// Allocates a physically contiguous DMA region of at least `size` bytes.
23    fn alloc_dma(&self, size: usize) -> Result<DmaRegion, nic_buffers::DmaAllocError> {
24        let pages = (size + 4095) / 4096;
25        let order = pages.next_power_of_two().trailing_zeros() as u8;
26        let frame = crate::sync::with_irqs_disabled(|token| {
27            crate::memory::allocate_phys_contiguous(token, order)
28        })
29        .map_err(|_| nic_buffers::DmaAllocError)?;
30        let phys = frame.start_address.as_u64();
31        let virt = memory::phys_to_virt(phys) as *mut u8;
32        Ok(DmaRegion {
33            phys,
34            virt,
35            size: pages * 4096,
36        })
37    }
38
39    /// Releases a previously allocated DMA region back to the buddy allocator.
40    ///
41    /// # Safety
42    ///
43    /// The caller must ensure that `region` was previously allocated by this
44    /// allocator and has not already been freed.
45    unsafe fn free_dma(&self, region: DmaRegion) {
46        let pages = (region.size + 4095) / 4096;
47        let order = pages.next_power_of_two().trailing_zeros() as u8;
48        let frame = crate::memory::PhysFrame::containing_address(
49            crate::arch::xshim::PhysAddr::new(region.phys),
50        );
51        crate::sync::with_irqs_disabled(|token| {
52            crate::memory::free_phys_contiguous(token, frame, order);
53        });
54    }
55}
56
57/// Result of a successful PCI probe for an E1000-family device.
58pub struct ProbeResult {
59    /// The PCI device handle (already configured for bus-master + memory space).
60    pub pci_dev: PciDevice,
61    /// Identity-mapped virtual address of the MMIO BAR region.
62    pub mmio_virt: u64,
63    /// Physical address of the MMIO BAR region.
64    pub mmio_phys: u64,
65}
66
67/// Probe PCI for an E1000-family NIC, map its MMIO BAR, and return a
68/// `ProbeResult` ready for hardware init.
69///
70/// `device_ids` lists the PCI device IDs this driver supports.  Only devices
71/// whose subclass is `ETHERNET` or `OTHER` are accepted.
72///
73/// Returns `None` when no matching device is found or when MMIO mapping fails
74/// for every candidate.
75pub fn probe_e1000_pci(device_ids: &[u16]) -> Option<ProbeResult> {
76    if !memory::paging::is_initialized() {
77        log::warn!("e1000-family: paging not initialized, deferring probe");
78        return None;
79    }
80
81    let candidates = pci::probe_all(pci::ProbeCriteria {
82        vendor_id: Some(pci::vendor::INTEL),
83        device_id: None,
84        class_code: Some(pci::class::NETWORK),
85        subclass: None,
86        prog_if: None,
87    });
88
89    for pci_dev in candidates.into_iter() {
90        if pci_dev.subclass != pci::net_subclass::ETHERNET
91            && pci_dev.subclass != pci::net_subclass::OTHER
92        {
93            continue;
94        }
95        if !device_ids.contains(&pci_dev.device_id) {
96            continue;
97        }
98
99        log::info!(
100            "e1000-family: PCI {:04x}:{:04x} at {:?}",
101            pci_dev.vendor_id,
102            pci_dev.device_id,
103            pci_dev.address
104        );
105
106        pci_dev.enable_bus_master();
107        pci_dev.enable_memory_space();
108        let mut cmd = pci_dev.read_config_u16(pci::config::COMMAND);
109        cmd &= !pci::command::INTERRUPT_DISABLE;
110        pci_dev.write_config_u16(pci::config::COMMAND, cmd);
111
112        let mmio_phys = match pci_dev.read_bar(0).or_else(|| pci_dev.read_bar(1)) {
113            Some(Bar::Memory32 { addr, .. }) => addr as u64,
114            Some(Bar::Memory64 { addr, .. }) => addr,
115            _ => {
116                log::error!(
117                    "e1000-family: no MMIO BAR (BAR0/BAR1) for {:04x}:{:04x}",
118                    pci_dev.vendor_id,
119                    pci_dev.device_id
120                );
121                continue;
122            }
123        };
124
125        memory::paging::ensure_identity_map_range(mmio_phys, 0x2_0000);
126        let mmio_virt = memory::phys_to_virt(mmio_phys);
127        let mmio_page_phys = mmio_phys & !0xFFF;
128        let mmio_page_virt = mmio_virt & !0xFFF;
129        let mapped = memory::paging::translate(VirtAddr::new(mmio_page_virt))
130            .map(|p| p.as_u64())
131            .unwrap_or(0);
132        if mapped != mmio_page_phys {
133            log::error!(
134                "e1000-family: MMIO not mapped for {:04x}:{:04x} phys={:#x} virt={:#x} mapped={:#x}",
135                pci_dev.vendor_id,
136                pci_dev.device_id,
137                mmio_phys,
138                mmio_virt,
139                mapped
140            );
141            continue;
142        }
143
144        return Some(ProbeResult {
145            pci_dev,
146            mmio_virt,
147            mmio_phys,
148        });
149    }
150
151    None
152}
153
154// ---------------------------------------------------------------------------
155// Generic kernel NIC adapter : removes duplication between e1000 and e1000e
156// kernel drivers (point 4).
157// ---------------------------------------------------------------------------
158
159/// Wraps `E1000Nic` behind a `SpinLock` and implements `NetworkDevice`.
160///
161/// Both `e1000_drv` and `e1000e_drv` instantiate this adapter with their own
162/// device ID list and driver name string instead of duplicating the trait impl.
163pub struct KernelE1000Adapter {
164    inner: SpinLock<E1000Nic>,
165    mac: [u8; 6],
166    name: &'static str,
167}
168
169impl KernelE1000Adapter {
170    /// Wrap an initialised `E1000Nic` and produce an `Arc<dyn NetworkDevice>`.
171    pub fn new(nic: E1000Nic, driver_name: &'static str) -> Arc<Self> {
172        let mac = nic.mac_address();
173        Arc::new(Self {
174            inner: SpinLock::new(nic),
175            mac,
176            name: driver_name,
177        })
178    }
179}
180
181impl NetworkDevice for KernelE1000Adapter {
182    fn name(&self) -> &str {
183        self.name
184    }
185
186    fn mac_address(&self) -> [u8; 6] {
187        self.mac
188    }
189
190    fn link_up(&self) -> bool {
191        self.inner.lock().link_up()
192    }
193
194    fn receive(&self, buf: &mut [u8]) -> Result<usize, NetError> {
195        self.inner.lock().receive(buf)
196    }
197
198    fn transmit(&self, buf: &[u8]) -> Result<(), NetError> {
199        self.inner.lock().transmit(buf)
200    }
201
202    fn handle_interrupt(&self) {
203        let _icr = self.inner.lock().handle_interrupt();
204    }
205
206    fn poll(&self) {
207        let mut nic = self.inner.lock();
208        nic.watchdog_tick(&KernelDma);
209    }
210}