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