diff --git a/Cargo.toml b/Cargo.toml index 5ad3e8b..5e8c76f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ test_invalid_opcode = ["kernel/test_invalid_opcode"] test_page_fault = ["kernel/test_page_fault"] test_all_exceptions = ["kernel/test_all_exceptions"] external_test_bins = ["kernel/external_test_bins"] +vmnet = ["kernel/vmnet"] [[bin]] name = "qemu-uefi" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index ce604cd..e3101c5 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -24,6 +24,7 @@ test_page_fault = [] test_userspace = [] test_all_exceptions = [] external_test_bins = [] +vmnet = [] # Use vmnet network config (192.168.64.x) instead of SLIRP (10.0.2.x) [dependencies] bootloader_api = { git = "https://github.com/rust-osdev/bootloader.git", branch = "main" } diff --git a/kernel/src/drivers/e1000/mod.rs b/kernel/src/drivers/e1000/mod.rs new file mode 100644 index 0000000..a335d6c --- /dev/null +++ b/kernel/src/drivers/e1000/mod.rs @@ -0,0 +1,657 @@ +//! Intel 82540EM (e1000) Gigabit Ethernet Driver +//! +//! This driver supports the Intel 82540EM NIC which is the default network +//! card emulated by QEMU. It implements basic packet transmission and reception +//! using memory-mapped I/O and descriptor rings. +//! +//! # References +//! - Intel PCI/PCI-X Family of Gigabit Ethernet Controllers Software Developer's Manual +//! - OSDev Wiki: https://wiki.osdev.org/Intel_8254x + +mod regs; + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{AtomicBool, Ordering}; +use spin::Mutex; + +use crate::drivers::pci::{self, Device, INTEL_VENDOR_ID}; +use crate::memory::PhysAddrWrapper as PhysAddr; + +pub use regs::*; + +/// Intel 82540EM device ID +#[allow(dead_code)] // Used in init() for device detection +pub const E1000_DEVICE_ID: u16 = 0x100E; + +/// Number of receive descriptors (must be multiple of 8) +const RX_RING_SIZE: usize = 32; +/// Number of transmit descriptors (must be multiple of 8) +const TX_RING_SIZE: usize = 32; +/// Size of each receive buffer +const RX_BUFFER_SIZE: usize = 2048; + +/// Maximum Ethernet frame size (including header and CRC) +#[allow(dead_code)] // Public API for network stack +pub const ETH_FRAME_MAX: usize = 1518; + +/// Receive descriptor (16 bytes) +#[repr(C, align(16))] +#[derive(Clone, Copy, Debug)] +pub struct RxDesc { + /// Physical address of the receive buffer + pub addr: u64, + /// Length of received data + pub length: u16, + /// Checksum + pub checksum: u16, + /// Status bits + pub status: u8, + /// Error bits + pub errors: u8, + /// Special/VLAN tag + pub special: u16, +} + +impl RxDesc { + const fn new() -> Self { + RxDesc { + addr: 0, + length: 0, + checksum: 0, + status: 0, + errors: 0, + special: 0, + } + } + + /// Check if this descriptor has been filled by hardware + /// Uses volatile read since hardware writes to this field asynchronously + pub fn is_done(&self) -> bool { + let status = unsafe { read_volatile(&self.status) }; + status & RXD_STAT_DD != 0 + } + + /// Check if this is the end of a packet + #[allow(dead_code)] // Part of RX descriptor API for multi-packet handling + pub fn is_eop(&self) -> bool { + self.status & RXD_STAT_EOP != 0 + } +} + +/// Transmit descriptor (16 bytes) +#[repr(C, align(16))] +#[derive(Clone, Copy, Debug)] +pub struct TxDesc { + /// Physical address of the transmit buffer + pub addr: u64, + /// Length of data to transmit + pub length: u16, + /// Checksum offset + pub cso: u8, + /// Command bits + pub cmd: u8, + /// Status bits + pub status: u8, + /// Checksum start + pub css: u8, + /// Special/VLAN tag + pub special: u16, +} + +impl TxDesc { + const fn new() -> Self { + TxDesc { + addr: 0, + length: 0, + cso: 0, + cmd: 0, + status: 0, + css: 0, + special: 0, + } + } + + /// Check if this descriptor has been processed by hardware + /// Uses volatile read since hardware writes to this field asynchronously + pub fn is_done(&self) -> bool { + // Memory barrier to ensure we see the latest value from RAM + core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); + let status = unsafe { read_volatile(&self.status) }; + status & TXD_STAT_DD != 0 + } +} + +/// E1000 driver state +pub struct E1000 { + /// PCI device information + #[allow(dead_code)] // Stored for future use (interrupt routing, power management) + pci_device: Device, + /// Base address of MMIO registers + mmio_base: usize, + /// Receive descriptor ring + rx_ring: Box<[RxDesc; RX_RING_SIZE]>, + /// Receive buffers + rx_buffers: Vec>, + /// Transmit descriptor ring + tx_ring: Box<[TxDesc; TX_RING_SIZE]>, + /// Transmit buffers (pre-allocated, reused for DMA) + tx_buffers: Vec>, + /// Current receive descriptor index + rx_cur: usize, + /// Current transmit descriptor index + tx_cur: usize, + /// MAC address + mac_addr: [u8; 6], +} + +impl E1000 { + /// Read a 32-bit register + fn read_reg(&self, reg: u32) -> u32 { + unsafe { read_volatile((self.mmio_base + reg as usize) as *const u32) } + } + + /// Write a 32-bit register + fn write_reg(&self, reg: u32, value: u32) { + unsafe { write_volatile((self.mmio_base + reg as usize) as *mut u32, value) } + } + + /// Read MAC address from EEPROM + fn read_eeprom(&self, addr: u8) -> u16 { + // Write the address and start bit + self.write_reg(REG_EERD, ((addr as u32) << EERD_ADDR_SHIFT) | EERD_START); + + // Wait for completion + loop { + let val = self.read_reg(REG_EERD); + if val & EERD_DONE != 0 { + return ((val >> EERD_DATA_SHIFT) & 0xFFFF) as u16; + } + } + } + + /// Read MAC address from EEPROM or RAL/RAH registers + fn read_mac_address(&self) -> [u8; 6] { + // Try to read from EEPROM first + let word0 = self.read_eeprom(0); + let word1 = self.read_eeprom(1); + let word2 = self.read_eeprom(2); + + [ + (word0 & 0xFF) as u8, + ((word0 >> 8) & 0xFF) as u8, + (word1 & 0xFF) as u8, + ((word1 >> 8) & 0xFF) as u8, + (word2 & 0xFF) as u8, + ((word2 >> 8) & 0xFF) as u8, + ] + } + + /// Get virtual address to physical address (identity mapped for now) + fn virt_to_phys(virt: usize) -> u64 { + // In Breenix, we use identity mapping for kernel addresses + // The physical address is the virtual address minus the kernel offset + // For now, assume identity mapping in the physical memory region + PhysAddr::from_kernel_virt(virt) + } + + /// Reset the device + fn reset(&self) { + // Disable interrupts + self.write_reg(REG_IMC, 0xFFFF_FFFF); + + // Reset the device + let ctrl = self.read_reg(REG_CTRL); + self.write_reg(REG_CTRL, ctrl | CTRL_RST); + + // Wait for reset to complete (typically < 1ms) + for _ in 0..1000 { + if self.read_reg(REG_CTRL) & CTRL_RST == 0 { + break; + } + // Small delay + for _ in 0..1000 { + core::hint::spin_loop(); + } + } + + // Disable interrupts again after reset + self.write_reg(REG_IMC, 0xFFFF_FFFF); + } + + /// Initialize receive functionality + fn init_rx(&mut self) { + // Set up receive descriptor ring physical address + let rx_ring_phys = Self::virt_to_phys(self.rx_ring.as_ptr() as usize); + self.write_reg(REG_RDBAL, rx_ring_phys as u32); + self.write_reg(REG_RDBAH, (rx_ring_phys >> 32) as u32); + + // Set receive descriptor ring length + self.write_reg( + REG_RDLEN, + (RX_RING_SIZE * core::mem::size_of::()) as u32, + ); + + // Set head and tail pointers + self.write_reg(REG_RDH, 0); + self.write_reg(REG_RDT, (RX_RING_SIZE - 1) as u32); + + // Configure receive control register + // Enable receiver, accept broadcast, 2KB buffers, strip CRC + self.write_reg( + REG_RCTL, + RCTL_EN | RCTL_BAM | RCTL_SZ_2048 | RCTL_SECRC | RCTL_BSEX, + ); + + log::info!("E1000: RX initialized with {} descriptors", RX_RING_SIZE); + } + + /// Initialize transmit functionality + fn init_tx(&mut self) { + // Set up transmit descriptor ring physical address + let tx_ring_virt = self.tx_ring.as_ptr() as usize; + let tx_ring_phys = Self::virt_to_phys(tx_ring_virt); + + self.write_reg(REG_TDBAL, tx_ring_phys as u32); + self.write_reg(REG_TDBAH, (tx_ring_phys >> 32) as u32); + + // Set transmit descriptor ring length + let tdlen = (TX_RING_SIZE * core::mem::size_of::()) as u32; + self.write_reg(REG_TDLEN, tdlen); + + // Set head and tail pointers + self.write_reg(REG_TDH, 0); + self.write_reg(REG_TDT, 0); + + // Configure transmit control register + // Enable transmitter, pad short packets, collision threshold, collision distance + let tctl = TCTL_EN | TCTL_PSP | (0x10 << TCTL_CT_SHIFT) | (0x40 << TCTL_COLD_SHIFT); + self.write_reg(REG_TCTL, tctl); + + // Set inter-packet gap + // IPG transmit time: 10 + 8 + 6 (for IEEE 802.3 standard) + self.write_reg(REG_TIPG, 10 | (8 << 10) | (6 << 20)); + + log::info!("E1000: TX initialized with {} descriptors", TX_RING_SIZE); + } + + /// Set up the MAC address filter + fn init_mac_filter(&self) { + let mac = &self.mac_addr; + + // Write to Receive Address Low (RAL) + let ral = (mac[0] as u32) + | ((mac[1] as u32) << 8) + | ((mac[2] as u32) << 16) + | ((mac[3] as u32) << 24); + self.write_reg(REG_RAL, ral); + + // Write to Receive Address High (RAH) with Address Valid bit + let rah = (mac[4] as u32) | ((mac[5] as u32) << 8) | RAH_AV; + self.write_reg(REG_RAH, rah); + + // Clear Multicast Table Array + for i in 0..128 { + self.write_reg(REG_MTA + (i * 4), 0); + } + } + + /// Enable link + fn enable_link(&self) { + let ctrl = self.read_reg(REG_CTRL); + // Set Link Up, Auto-Speed Detection Enable + self.write_reg(REG_CTRL, ctrl | CTRL_SLU | CTRL_ASDE); + } + + /// Check if link is up + pub fn link_up(&self) -> bool { + self.read_reg(REG_STATUS) & STATUS_LU != 0 + } + + /// Get link speed in Mbps + pub fn link_speed(&self) -> u32 { + let status = self.read_reg(REG_STATUS); + match (status >> 6) & 0x3 { + 0b00 => 10, + 0b01 => 100, + _ => 1000, + } + } + + /// Get MAC address + pub fn mac_address(&self) -> &[u8; 6] { + &self.mac_addr + } + + /// Transmit a packet + pub fn transmit(&mut self, data: &[u8]) -> Result<(), &'static str> { + if data.len() > ETH_FRAME_MAX { + return Err("Packet too large"); + } + + let idx = self.tx_cur; + + // Check if the descriptor is available + // The buffer can be reused once the descriptor's DD bit is set + if !self.tx_ring[idx].is_done() { + let cmd = unsafe { read_volatile(&self.tx_ring[idx].cmd) }; + if cmd & TXD_CMD_RS != 0 { + return Err("TX ring full"); + } + } + + // Copy data to the pre-allocated buffer at this index + // This buffer is reused - no allocation needed + let tx_buffer = &mut self.tx_buffers[idx]; + tx_buffer[..data.len()].copy_from_slice(data); + let buf_virt = tx_buffer.as_ptr() as usize; + let phys_addr = Self::virt_to_phys(buf_virt); + + // Set up the descriptor using volatile writes to ensure hardware sees the values + let desc = &mut self.tx_ring[idx]; + unsafe { + write_volatile(&mut desc.addr, phys_addr); + write_volatile(&mut desc.length, data.len() as u16); + write_volatile(&mut desc.cso, 0); + write_volatile(&mut desc.css, 0); + write_volatile(&mut desc.special, 0); + write_volatile(&mut desc.status, 0); + // Write cmd last - this signals the descriptor is ready + write_volatile(&mut desc.cmd, TXD_CMD_EOP | TXD_CMD_IFCS | TXD_CMD_RS); + } + + // Memory barrier to ensure all descriptor writes are visible before tail update + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + + // Advance tail pointer + self.tx_cur = (self.tx_cur + 1) % TX_RING_SIZE; + self.write_reg(REG_TDT, self.tx_cur as u32); + + // Wait for transmit to complete (polling for now) + for _ in 0..100000 { + if self.tx_ring[idx].is_done() { + // Buffer remains allocated in tx_buffers[idx] for reuse + return Ok(()); + } + core::hint::spin_loop(); + } + + // Check status after timeout + let tdh_after = self.read_reg(REG_TDH); + let tdt_after = self.read_reg(REG_TDT); + let status = unsafe { read_volatile(&self.tx_ring[idx].status) }; + log::warn!( + "E1000: TX timeout TDH={} TDT={} desc.status={:#x}", + tdh_after, tdt_after, status + ); + + Err("TX timeout") + } + + /// Check if a packet is available to receive + pub fn can_receive(&self) -> bool { + self.rx_ring[self.rx_cur].is_done() + } + + /// Receive a packet + /// Returns the number of bytes received and copies data to the provided buffer + pub fn receive(&mut self, buffer: &mut [u8]) -> Result { + let idx = self.rx_cur; + + if !self.rx_ring[idx].is_done() { + return Err("No packet available"); + } + + let desc = &self.rx_ring[idx]; + let len = desc.length as usize; + + if len > buffer.len() { + return Err("Buffer too small"); + } + + // Check for errors + if desc.errors != 0 { + // Reset the descriptor for reuse + self.rx_ring[idx].status = 0; + self.rx_ring[idx].errors = 0; + let old_tail = self.read_reg(REG_RDT); + self.write_reg(REG_RDT, (old_tail + 1) % RX_RING_SIZE as u32); + self.rx_cur = (self.rx_cur + 1) % RX_RING_SIZE; + return Err("Receive error"); + } + + // Copy data from the receive buffer + let rx_buf = &self.rx_buffers[idx]; + buffer[..len].copy_from_slice(&rx_buf[..len]); + + // Reset the descriptor for reuse + self.rx_ring[idx].status = 0; + self.rx_ring[idx].length = 0; + + // Advance tail pointer to give buffer back to hardware + let old_tail = self.read_reg(REG_RDT); + self.write_reg(REG_RDT, (old_tail + 1) % RX_RING_SIZE as u32); + + // Advance our current index + self.rx_cur = (self.rx_cur + 1) % RX_RING_SIZE; + + Ok(len) + } + + /// Handle interrupt + pub fn handle_interrupt(&mut self) { + let icr = self.read_reg(REG_ICR); + + if icr & ICR_RXT0 != 0 { + // Receive timer expired - packets available + log::debug!("E1000: RX interrupt"); + } + + if icr & ICR_TXDW != 0 { + // Transmit descriptor written back + log::debug!("E1000: TX interrupt"); + } + + if icr & ICR_LSC != 0 { + // Link status change + if self.link_up() { + log::info!("E1000: Link up at {} Mbps", self.link_speed()); + } else { + log::info!("E1000: Link down"); + } + } + } + + /// Enable interrupts + pub fn enable_interrupts(&self) { + // Enable RX timer, TX descriptor written back, and link status change + self.write_reg(REG_IMS, IMS_RXT0 | IMS_TXDW | IMS_LSC); + + // Set receive delay timer to 0 for immediate interrupts + self.write_reg(REG_RDTR, 0); + self.write_reg(REG_RADV, 0); + } + + /// Disable interrupts + #[allow(dead_code)] // Will be used when interrupt handler is wired up + pub fn disable_interrupts(&self) { + self.write_reg(REG_IMC, 0xFFFF_FFFF); + } +} + +/// Global E1000 driver instance +static E1000_DRIVER: Mutex> = Mutex::new(None); +static E1000_INITIALIZED: AtomicBool = AtomicBool::new(false); + +/// Initialize the E1000 driver +pub fn init() -> Result<(), &'static str> { + // Find the E1000 device on the PCI bus + let device = pci::find_device(INTEL_VENDOR_ID, E1000_DEVICE_ID) + .ok_or("E1000 device not found on PCI bus")?; + + log::info!( + "E1000: Found device at {:02x}:{:02x}.{} IRQ={}", + device.bus, + device.device, + device.function, + device.interrupt_line + ); + + // Get the MMIO BAR + let mmio_bar = device.get_mmio_bar().ok_or("E1000: No MMIO BAR found")?; + + log::info!( + "E1000: MMIO at {:#x} size {:#x}", + mmio_bar.address, + mmio_bar.size + ); + + // Map the MMIO region + let mmio_base = crate::memory::map_mmio(mmio_bar.address, mmio_bar.size as usize)?; + + log::info!("E1000: Mapped MMIO to {:#x}", mmio_base); + + // Enable bus mastering and memory space + device.enable_bus_master(); + device.enable_memory_space(); + + // Allocate descriptor rings + let rx_ring = Box::new([RxDesc::new(); RX_RING_SIZE]); + let tx_ring = Box::new([TxDesc::new(); TX_RING_SIZE]); + + // Allocate receive buffers + let mut rx_buffers = Vec::with_capacity(RX_RING_SIZE); + for _ in 0..RX_RING_SIZE { + rx_buffers.push(Box::new([0u8; RX_BUFFER_SIZE])); + } + + // Allocate transmit buffers (pre-allocated for reuse, prevents memory leaks) + let mut tx_buffers = Vec::with_capacity(TX_RING_SIZE); + for _ in 0..TX_RING_SIZE { + tx_buffers.push(Box::new([0u8; ETH_FRAME_MAX])); + } + + // Create driver instance + let mut driver = E1000 { + pci_device: device, + mmio_base, + rx_ring, + rx_buffers, + tx_ring, + tx_buffers, + rx_cur: 0, + tx_cur: 0, + mac_addr: [0; 6], + }; + + // Initialize the device + driver.reset(); + + // Read MAC address + driver.mac_addr = driver.read_mac_address(); + log::info!( + "E1000: MAC address {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + driver.mac_addr[0], + driver.mac_addr[1], + driver.mac_addr[2], + driver.mac_addr[3], + driver.mac_addr[4], + driver.mac_addr[5] + ); + + // Set up MAC address filter + driver.init_mac_filter(); + + // Set up receive buffer addresses in descriptors + for (i, buf) in driver.rx_buffers.iter().enumerate() { + driver.rx_ring[i].addr = E1000::virt_to_phys(buf.as_ptr() as usize); + } + + // Initialize RX and TX + driver.init_rx(); + driver.init_tx(); + + // Enable link + driver.enable_link(); + + // Check link status + if driver.link_up() { + log::info!("E1000: Link up at {} Mbps", driver.link_speed()); + } else { + log::info!("E1000: Link down (waiting for link...)"); + } + + // Enable interrupts on the device + driver.enable_interrupts(); + + // Store driver instance + *E1000_DRIVER.lock() = Some(driver); + E1000_INITIALIZED.store(true, Ordering::Release); + + log::info!("E1000 driver initialized"); + Ok(()) +} + +/// Check if the E1000 driver is initialized +#[allow(dead_code)] // Public API for network stack +pub fn is_initialized() -> bool { + E1000_INITIALIZED.load(Ordering::Acquire) +} + +/// Get the MAC address +#[allow(dead_code)] // Public API for network stack +pub fn mac_address() -> Option<[u8; 6]> { + E1000_DRIVER.lock().as_ref().map(|d| *d.mac_address()) +} + +/// Check if link is up +#[allow(dead_code)] // Public API for network stack +pub fn link_up() -> bool { + E1000_DRIVER + .lock() + .as_ref() + .map(|d| d.link_up()) + .unwrap_or(false) +} + +/// Transmit a packet +#[allow(dead_code)] // Public API for network stack +pub fn transmit(data: &[u8]) -> Result<(), &'static str> { + E1000_DRIVER + .lock() + .as_mut() + .ok_or("E1000 not initialized")? + .transmit(data) +} + +/// Receive a packet +#[allow(dead_code)] // Public API for network stack +pub fn receive(buffer: &mut [u8]) -> Result { + E1000_DRIVER + .lock() + .as_mut() + .ok_or("E1000 not initialized")? + .receive(buffer) +} + +/// Check if a packet is available to receive +#[allow(dead_code)] // Public API for network stack +pub fn can_receive() -> bool { + E1000_DRIVER + .lock() + .as_ref() + .map(|d| d.can_receive()) + .unwrap_or(false) +} + +/// Handle E1000 interrupt (called from IRQ 11 handler) +pub fn handle_interrupt() { + if let Some(driver) = E1000_DRIVER.lock().as_mut() { + driver.handle_interrupt(); + } + + // Process any received packets + // Note: This is done outside the driver lock to avoid holding it too long + crate::net::process_rx(); +} diff --git a/kernel/src/drivers/e1000/regs.rs b/kernel/src/drivers/e1000/regs.rs new file mode 100644 index 0000000..383f1d8 --- /dev/null +++ b/kernel/src/drivers/e1000/regs.rs @@ -0,0 +1,338 @@ +//! Intel E1000 Register Definitions +//! +//! Based on Intel PCI/PCI-X Family of Gigabit Ethernet Controllers +//! Software Developer's Manual + +// Allow unused constants - these are hardware register definitions that form +// the complete e1000 driver API. Not all are used yet but provide the full +// register map for future features (interrupts, advanced offloads, etc.) +#![allow(dead_code)] + +// ============================================================================= +// Register Offsets +// ============================================================================= + +/// Device Control Register +pub const REG_CTRL: u32 = 0x0000; +/// Device Status Register +pub const REG_STATUS: u32 = 0x0008; +/// EEPROM Read Register +pub const REG_EERD: u32 = 0x0014; +/// Flow Control Address Low +pub const REG_FCAL: u32 = 0x0028; +/// Flow Control Address High +pub const REG_FCAH: u32 = 0x002C; +/// Flow Control Type +pub const REG_FCT: u32 = 0x0030; +/// VLAN Ether Type +pub const REG_VET: u32 = 0x0038; +/// Interrupt Cause Read +pub const REG_ICR: u32 = 0x00C0; +/// Interrupt Throttling Register +pub const REG_ITR: u32 = 0x00C4; +/// Interrupt Cause Set +pub const REG_ICS: u32 = 0x00C8; +/// Interrupt Mask Set/Read +pub const REG_IMS: u32 = 0x00D0; +/// Interrupt Mask Clear +pub const REG_IMC: u32 = 0x00D8; + +// Receive Registers +/// Receive Control Register +pub const REG_RCTL: u32 = 0x0100; +/// Flow Control Receive Threshold Low +pub const REG_FCRTL: u32 = 0x2160; +/// Flow Control Receive Threshold High +pub const REG_FCRTH: u32 = 0x2168; +/// Receive Descriptor Base Address Low +pub const REG_RDBAL: u32 = 0x2800; +/// Receive Descriptor Base Address High +pub const REG_RDBAH: u32 = 0x2804; +/// Receive Descriptor Length +pub const REG_RDLEN: u32 = 0x2808; +/// Receive Descriptor Head +pub const REG_RDH: u32 = 0x2810; +/// Receive Descriptor Tail +pub const REG_RDT: u32 = 0x2818; +/// Receive Delay Timer +pub const REG_RDTR: u32 = 0x2820; +/// Receive Absolute Delay Timer +pub const REG_RADV: u32 = 0x282C; +/// Receive Small Packet Detect +pub const REG_RSRPD: u32 = 0x2C00; + +// Transmit Registers +/// Transmit Control Register +pub const REG_TCTL: u32 = 0x0400; +/// Transmit Inter-Packet Gap +pub const REG_TIPG: u32 = 0x0410; +/// Transmit Descriptor Base Address Low +pub const REG_TDBAL: u32 = 0x3800; +/// Transmit Descriptor Base Address High +pub const REG_TDBAH: u32 = 0x3804; +/// Transmit Descriptor Length +pub const REG_TDLEN: u32 = 0x3808; +/// Transmit Descriptor Head +pub const REG_TDH: u32 = 0x3810; +/// Transmit Descriptor Tail +pub const REG_TDT: u32 = 0x3818; +/// Transmit Interrupt Delay Value +pub const REG_TIDV: u32 = 0x3820; +/// Transmit Absolute Interrupt Delay Value +pub const REG_TADV: u32 = 0x382C; + +// Receive Address Registers +/// Receive Address Low +pub const REG_RAL: u32 = 0x5400; +/// Receive Address High +pub const REG_RAH: u32 = 0x5404; +/// Multicast Table Array (128 entries) +pub const REG_MTA: u32 = 0x5200; + +// ============================================================================= +// Device Control Register (CTRL) Bits +// ============================================================================= + +/// Full Duplex +pub const CTRL_FD: u32 = 1 << 0; +/// GIO Master Disable +pub const CTRL_GIO_MASTER_DISABLE: u32 = 1 << 2; +/// Link Reset +pub const CTRL_LRST: u32 = 1 << 3; +/// Set Link Up +pub const CTRL_SLU: u32 = 1 << 6; +/// Speed selection (bits 8-9) +pub const CTRL_SPEED_MASK: u32 = 0x3 << 8; +pub const CTRL_SPEED_10: u32 = 0 << 8; +pub const CTRL_SPEED_100: u32 = 1 << 8; +pub const CTRL_SPEED_1000: u32 = 2 << 8; +/// Force Speed +pub const CTRL_FRCSPD: u32 = 1 << 11; +/// Force Duplex +pub const CTRL_FRCDPLX: u32 = 1 << 12; +/// Software Defined Pin 0 Data +pub const CTRL_SDP0_DATA: u32 = 1 << 18; +/// Software Defined Pin 1 Data +pub const CTRL_SDP1_DATA: u32 = 1 << 19; +/// Auto-Speed Detection Enable +pub const CTRL_ASDE: u32 = 1 << 20; +/// Invert Loss-of-Signal +pub const CTRL_ILOS: u32 = 1 << 21; +/// Device Reset +pub const CTRL_RST: u32 = 1 << 26; +/// Receive Flow Control Enable +pub const CTRL_RFCE: u32 = 1 << 27; +/// Transmit Flow Control Enable +pub const CTRL_TFCE: u32 = 1 << 28; +/// VLAN Mode Enable +pub const CTRL_VME: u32 = 1 << 30; +/// PHY Reset +pub const CTRL_PHY_RST: u32 = 1 << 31; + +// ============================================================================= +// Device Status Register (STATUS) Bits +// ============================================================================= + +/// Full Duplex +pub const STATUS_FD: u32 = 1 << 0; +/// Link Up +pub const STATUS_LU: u32 = 1 << 1; +/// Function ID (bits 2-3) +pub const STATUS_FUNC_ID_MASK: u32 = 0x3 << 2; +/// TX Off +pub const STATUS_TXOFF: u32 = 1 << 4; +/// TBI mode +pub const STATUS_TBIMODE: u32 = 1 << 5; +/// Speed (bits 6-7) +pub const STATUS_SPEED_MASK: u32 = 0x3 << 6; +pub const STATUS_SPEED_10: u32 = 0 << 6; +pub const STATUS_SPEED_100: u32 = 1 << 6; +pub const STATUS_SPEED_1000: u32 = 2 << 6; +/// Auto-Speed Detection Value +pub const STATUS_ASDV_MASK: u32 = 0x3 << 8; +/// PHY Reset Asserted +pub const STATUS_PHYRA: u32 = 1 << 10; +/// GIO Master Enable Status +pub const STATUS_GIO_MASTER_ENABLE: u32 = 1 << 19; + +// ============================================================================= +// EEPROM Read Register (EERD) Bits +// ============================================================================= + +/// Start Read +pub const EERD_START: u32 = 1 << 0; +/// Read Done +pub const EERD_DONE: u32 = 1 << 4; +/// Address shift (bits 8-15) +pub const EERD_ADDR_SHIFT: u32 = 8; +/// Data shift (bits 16-31) +pub const EERD_DATA_SHIFT: u32 = 16; + +// ============================================================================= +// Interrupt Cause/Mask Bits +// ============================================================================= + +/// TX Descriptor Written Back +pub const ICR_TXDW: u32 = 1 << 0; +/// TX Queue Empty +pub const ICR_TXQE: u32 = 1 << 1; +/// Link Status Change +pub const ICR_LSC: u32 = 1 << 2; +/// RX Sequence Error +pub const ICR_RXSEQ: u32 = 1 << 3; +/// RX Descriptor Min Threshold Hit +pub const ICR_RXDMT0: u32 = 1 << 4; +/// RX Overrun +pub const ICR_RXO: u32 = 1 << 6; +/// RX Timer Interrupt +pub const ICR_RXT0: u32 = 1 << 7; +/// MDIO Access Complete +pub const ICR_MDAC: u32 = 1 << 9; +/// RX Config Queue +pub const ICR_RXCFG: u32 = 1 << 10; +/// PHY Interrupt +pub const ICR_PHYINT: u32 = 1 << 12; +/// General Purpose Interrupt 2 +pub const ICR_GPI_SDP2: u32 = 1 << 13; +/// General Purpose Interrupt 3 +pub const ICR_GPI_SDP3: u32 = 1 << 14; +/// TX Descriptor Low Threshold Hit +pub const ICR_TXD_LOW: u32 = 1 << 15; +/// Small Receive Packet Detected +pub const ICR_SRPD: u32 = 1 << 16; + +/// Interrupt Mask Set aliases +pub const IMS_TXDW: u32 = ICR_TXDW; +pub const IMS_TXQE: u32 = ICR_TXQE; +pub const IMS_LSC: u32 = ICR_LSC; +pub const IMS_RXSEQ: u32 = ICR_RXSEQ; +pub const IMS_RXDMT0: u32 = ICR_RXDMT0; +pub const IMS_RXO: u32 = ICR_RXO; +pub const IMS_RXT0: u32 = ICR_RXT0; + +// ============================================================================= +// Receive Control Register (RCTL) Bits +// ============================================================================= + +/// Receiver Enable +pub const RCTL_EN: u32 = 1 << 1; +/// Store Bad Packets +pub const RCTL_SBP: u32 = 1 << 2; +/// Unicast Promiscuous Enable +pub const RCTL_UPE: u32 = 1 << 3; +/// Multicast Promiscuous Enable +pub const RCTL_MPE: u32 = 1 << 4; +/// Long Packet Reception Enable +pub const RCTL_LPE: u32 = 1 << 5; +/// Loopback Mode (bits 6-7) +pub const RCTL_LBM_MASK: u32 = 0x3 << 6; +pub const RCTL_LBM_NONE: u32 = 0 << 6; +pub const RCTL_LBM_MAC: u32 = 1 << 6; +/// Receive Descriptor Min Threshold Size (bits 8-9) +pub const RCTL_RDMTS_MASK: u32 = 0x3 << 8; +pub const RCTL_RDMTS_HALF: u32 = 0 << 8; +pub const RCTL_RDMTS_QUARTER: u32 = 1 << 8; +pub const RCTL_RDMTS_EIGHTH: u32 = 2 << 8; +/// Descriptor Type (bits 10-11) +pub const RCTL_DTYP_MASK: u32 = 0x3 << 10; +/// Multicast Offset (bits 12-13) +pub const RCTL_MO_MASK: u32 = 0x3 << 12; +/// Broadcast Accept Mode +pub const RCTL_BAM: u32 = 1 << 15; +/// Receive Buffer Size (bits 16-17) +pub const RCTL_BSIZE_MASK: u32 = 0x3 << 16; +pub const RCTL_SZ_2048: u32 = 0 << 16; +pub const RCTL_SZ_1024: u32 = 1 << 16; +pub const RCTL_SZ_512: u32 = 2 << 16; +pub const RCTL_SZ_256: u32 = 3 << 16; +/// VLAN Filter Enable +pub const RCTL_VFE: u32 = 1 << 18; +/// Canonical Form Indicator Enable +pub const RCTL_CFIEN: u32 = 1 << 19; +/// Canonical Form Indicator value +pub const RCTL_CFI: u32 = 1 << 20; +/// Discard Pause Frames +pub const RCTL_DPF: u32 = 1 << 22; +/// Pass MAC Control Frames +pub const RCTL_PMCF: u32 = 1 << 23; +/// Buffer Size Extension +pub const RCTL_BSEX: u32 = 1 << 25; +/// Strip Ethernet CRC +pub const RCTL_SECRC: u32 = 1 << 26; + +// ============================================================================= +// Transmit Control Register (TCTL) Bits +// ============================================================================= + +/// Transmit Enable +pub const TCTL_EN: u32 = 1 << 1; +/// Pad Short Packets +pub const TCTL_PSP: u32 = 1 << 3; +/// Collision Threshold (bits 4-11) +pub const TCTL_CT_MASK: u32 = 0xFF << 4; +pub const TCTL_CT_SHIFT: u32 = 4; +/// Collision Distance (bits 12-21) +pub const TCTL_COLD_MASK: u32 = 0x3FF << 12; +pub const TCTL_COLD_SHIFT: u32 = 12; +/// Software XOFF Transmission +pub const TCTL_SWXOFF: u32 = 1 << 22; +/// Re-transmit on Late Collision +pub const TCTL_RTLC: u32 = 1 << 24; + +// ============================================================================= +// Receive Address High (RAH) Bits +// ============================================================================= + +/// Address Valid +pub const RAH_AV: u32 = 1 << 31; + +// ============================================================================= +// TX Descriptor Command Bits +// ============================================================================= + +/// End of Packet +pub const TXD_CMD_EOP: u8 = 1 << 0; +/// Insert FCS (CRC) +pub const TXD_CMD_IFCS: u8 = 1 << 1; +/// Insert Checksum +pub const TXD_CMD_IC: u8 = 1 << 2; +/// Report Status +pub const TXD_CMD_RS: u8 = 1 << 3; +/// Report Packet Sent +pub const TXD_CMD_RPS: u8 = 1 << 4; +/// Descriptor Extension +pub const TXD_CMD_DEXT: u8 = 1 << 5; +/// VLAN Packet Enable +pub const TXD_CMD_VLE: u8 = 1 << 6; +/// Interrupt Delay Enable +pub const TXD_CMD_IDE: u8 = 1 << 7; + +// ============================================================================= +// TX Descriptor Status Bits +// ============================================================================= + +/// Descriptor Done +pub const TXD_STAT_DD: u8 = 1 << 0; +/// Excess Collisions +pub const TXD_STAT_EC: u8 = 1 << 1; +/// Late Collision +pub const TXD_STAT_LC: u8 = 1 << 2; + +// ============================================================================= +// RX Descriptor Status Bits +// ============================================================================= + +/// Descriptor Done +pub const RXD_STAT_DD: u8 = 1 << 0; +/// End of Packet +pub const RXD_STAT_EOP: u8 = 1 << 1; +/// Ignore Checksum Indication +pub const RXD_STAT_IXSM: u8 = 1 << 2; +/// VLAN Packet +pub const RXD_STAT_VP: u8 = 1 << 3; +/// TCP Checksum Calculated +pub const RXD_STAT_TCPCS: u8 = 1 << 5; +/// IP Checksum Calculated +pub const RXD_STAT_IPCS: u8 = 1 << 6; +/// Passed In-exact Filter +pub const RXD_STAT_PIF: u8 = 1 << 7; diff --git a/kernel/src/drivers/mod.rs b/kernel/src/drivers/mod.rs index d64b1a4..d4343cd 100644 --- a/kernel/src/drivers/mod.rs +++ b/kernel/src/drivers/mod.rs @@ -1,8 +1,9 @@ //! Device drivers subsystem //! //! This module provides the driver infrastructure for Breenix, including -//! PCI enumeration and device-specific drivers like VirtIO. +//! PCI enumeration and device-specific drivers. +pub mod e1000; pub mod pci; pub mod virtio; @@ -37,6 +38,17 @@ pub fn init() -> usize { } } + // Initialize E1000 network driver if device was found + match e1000::init() { + Ok(()) => { + log::info!("E1000 network driver initialized successfully"); + // TODO: Enable E1000 IRQ when interrupt handler is wired up + } + Err(e) => { + log::warn!("E1000 network driver initialization failed: {}", e); + } + } + log::info!("Driver subsystem initialized"); device_count } diff --git a/kernel/src/drivers/pci.rs b/kernel/src/drivers/pci.rs index 8b5ac05..684c0a0 100644 --- a/kernel/src/drivers/pci.rs +++ b/kernel/src/drivers/pci.rs @@ -42,6 +42,15 @@ pub const VIRTIO_VENDOR_ID: u16 = 0x1AF4; pub const VIRTIO_BLOCK_DEVICE_ID_LEGACY: u16 = 0x1001; /// VirtIO block device ID (modern) pub const VIRTIO_BLOCK_DEVICE_ID_MODERN: u16 = 0x1042; +/// VirtIO network device ID (legacy) +pub const VIRTIO_NET_DEVICE_ID_LEGACY: u16 = 0x1000; +/// VirtIO network device ID (modern) +pub const VIRTIO_NET_DEVICE_ID_MODERN: u16 = 0x1041; + +/// Intel vendor ID (for reference - common in QEMU) +pub const INTEL_VENDOR_ID: u16 = 0x8086; +/// Red Hat / QEMU vendor ID +pub const QEMU_VENDOR_ID: u16 = 0x1B36; /// PCI device class codes #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -176,6 +185,18 @@ impl Device { || self.device_id == VIRTIO_BLOCK_DEVICE_ID_MODERN) } + /// Check if this is a VirtIO network device + pub fn is_virtio_net(&self) -> bool { + self.is_virtio() + && (self.device_id == VIRTIO_NET_DEVICE_ID_LEGACY + || self.device_id == VIRTIO_NET_DEVICE_ID_MODERN) + } + + /// Check if this is any network controller + pub fn is_network(&self) -> bool { + self.class == DeviceClass::Network + } + /// Get the first valid memory-mapped BAR #[allow(dead_code)] // Part of public API, will be used by VirtIO driver pub fn get_mmio_bar(&self) -> Option<&Bar> { @@ -465,6 +486,20 @@ static PCI_DEVICES: Mutex>> = Mutex::new(None); /// Track whether PCI enumeration has completed static PCI_INITIALIZED: AtomicBool = AtomicBool::new(false); +/// Get a human-readable vendor name for common vendors +fn vendor_name(vendor_id: u16) -> &'static str { + match vendor_id { + VIRTIO_VENDOR_ID => "VirtIO", + INTEL_VENDOR_ID => "Intel", + QEMU_VENDOR_ID => "QEMU/RedHat", + 0x1022 => "AMD", + 0x10DE => "NVIDIA", + 0x14E4 => "Broadcom", + 0x10EC => "Realtek", + _ => "Unknown", + } +} + /// Enumerate all PCI devices on the bus /// /// Returns the total number of devices found. @@ -473,6 +508,7 @@ pub fn enumerate() -> usize { let mut devices = Vec::new(); let mut virtio_block_count = 0; + let mut network_count = 0; for bus in 0..=MAX_BUS { for device in 0..MAX_DEVICE { @@ -480,34 +516,47 @@ pub fn enumerate() -> usize { if let Some(dev) = probe_device(bus, device, 0) { let is_multifunction = dev.multifunction; - // Log interesting devices + // Log ALL discovered devices for visibility + log::info!( + "PCI: {:02x}:{:02x}.{} [{:04x}:{:04x}] {} {:?}/0x{:02x} IRQ={}", + dev.bus, + dev.device, + dev.function, + dev.vendor_id, + dev.device_id, + vendor_name(dev.vendor_id), + dev.class, + dev.subclass, + dev.interrupt_line + ); + + // Log BAR info for all devices with valid BARs + for (i, bar) in dev.bars.iter().enumerate() { + if bar.is_valid() { + log::debug!( + "PCI: BAR{}: addr={:#x} size={:#x} {}", + i, + bar.address, + bar.size, + if bar.is_io { "I/O" } else { "MMIO" } + ); + } + } + + // Track specific device types if dev.is_virtio_block() { virtio_block_count += 1; + } + if dev.is_network() { + network_count += 1; log::info!( - "PCI: Found VirtIO block device at {:02x}:{:02x}.0 (IRQ {})", - bus, - device, - dev.interrupt_line + "PCI: -> Network controller detected!{}", + if dev.is_virtio_net() { " (VirtIO-net)" } else { "" } ); - // Log BAR info for VirtIO devices - for (i, bar) in dev.bars.iter().enumerate() { - if bar.is_valid() { - log::info!( - "PCI: BAR{}: addr={:#x} size={:#x} {}", - i, - bar.address, - bar.size, - if bar.is_io { "I/O" } else { "MMIO" } - ); - } + // Boot stage marker for E1000 detection + if dev.vendor_id == 0x8086 && dev.device_id == 0x100e { + log::info!("E1000 network device found"); } - } else if dev.is_virtio() { - log::info!( - "PCI: Found VirtIO device {:04x} at {:02x}:{:02x}.0", - dev.device_id, - bus, - device - ); } devices.push(dev); @@ -516,14 +565,32 @@ pub fn enumerate() -> usize { if is_multifunction { for function in 1..MAX_FUNCTION { if let Some(func_dev) = probe_device(bus, device, function) { + log::info!( + "PCI: {:02x}:{:02x}.{} [{:04x}:{:04x}] {} {:?}/0x{:02x} IRQ={}", + func_dev.bus, + func_dev.device, + func_dev.function, + func_dev.vendor_id, + func_dev.device_id, + vendor_name(func_dev.vendor_id), + func_dev.class, + func_dev.subclass, + func_dev.interrupt_line + ); + if func_dev.is_virtio_block() { virtio_block_count += 1; + } + if func_dev.is_network() { + network_count += 1; log::info!( - "PCI: Found VirtIO block device at {:02x}:{:02x}.{}", - bus, - device, - function + "PCI: -> Network controller detected!{}", + if func_dev.is_virtio_net() { " (VirtIO-net)" } else { "" } ); + // Boot stage marker for E1000 detection + if func_dev.vendor_id == 0x8086 && func_dev.device_id == 0x100e { + log::info!("E1000 network device found"); + } } devices.push(func_dev); } @@ -535,9 +602,10 @@ pub fn enumerate() -> usize { let device_count = devices.len(); log::info!( - "PCI: Enumeration complete. Found {} devices ({} VirtIO block)", + "PCI: Enumeration complete. Found {} devices ({} VirtIO block, {} network)", device_count, - virtio_block_count + virtio_block_count, + network_count ); // Store devices globally @@ -569,3 +637,23 @@ pub fn find_virtio_block_devices() -> Vec { None => Vec::new(), } } + +/// Find all network controller devices +#[allow(dead_code)] // Part of public API, will be used by network driver +pub fn find_network_devices() -> Vec { + let devices = PCI_DEVICES.lock(); + match devices.as_ref() { + Some(devs) => devs.iter().filter(|d| d.is_network()).cloned().collect(), + None => Vec::new(), + } +} + +/// Find all VirtIO network devices +#[allow(dead_code)] // Part of public API, will be used by VirtIO-net driver +pub fn find_virtio_net_devices() -> Vec { + let devices = PCI_DEVICES.lock(); + match devices.as_ref() { + Some(devs) => devs.iter().filter(|d| d.is_virtio_net()).cloned().collect(), + None => Vec::new(), + } +} diff --git a/kernel/src/interrupts.rs b/kernel/src/interrupts.rs index e0739bb..2461be9 100644 --- a/kernel/src/interrupts.rs +++ b/kernel/src/interrupts.rs @@ -26,8 +26,8 @@ pub enum InterruptIndex { Keyboard, // Skip COM2 (IRQ3) Serial = PIC_1_OFFSET + 4, // COM1 is IRQ4 - // VirtIO block device uses IRQ 11 (on PIC2) - VirtioBlock = PIC_2_OFFSET + 3, // IRQ 11 = 40 + 3 = 43 + // IRQ 11 is shared by VirtIO block and E1000 network devices (on PIC2) + Irq11 = PIC_2_OFFSET + 3, // IRQ 11 = 40 + 3 = 43 } /// System call interrupt vector (INT 0x80) @@ -126,7 +126,7 @@ pub fn init_idt() { } idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler); idt[InterruptIndex::Serial.as_u8()].set_handler_fn(serial_interrupt_handler); - idt[InterruptIndex::VirtioBlock.as_u8()].set_handler_fn(virtio_block_interrupt_handler); + idt[InterruptIndex::Irq11.as_u8()].set_handler_fn(irq11_handler); // System call handler (INT 0x80) // Use assembly handler for proper syscall dispatching @@ -173,7 +173,7 @@ pub fn init_idt() { if i != InterruptIndex::Timer.as_u8() && i != InterruptIndex::Keyboard.as_u8() && i != InterruptIndex::Serial.as_u8() - && i != InterruptIndex::VirtioBlock.as_u8() + && i != InterruptIndex::Irq11.as_u8() && i != SYSCALL_INTERRUPT_ID { idt[i].set_handler_fn(generic_handler); @@ -213,16 +213,16 @@ pub fn init_pic() { } } -/// Enable VirtIO block device interrupts (IRQ 11) +/// Enable IRQ 11 (shared by VirtIO block and E1000 network) /// -/// IMPORTANT: Only call this AFTER the VirtIO driver has been initialized. +/// IMPORTANT: Only call this AFTER devices using IRQ 11 have been initialized. /// Calling earlier will cause hangs due to interrupt handler accessing /// uninitialized driver state. -pub fn enable_virtio_irq() { +pub fn enable_irq11() { unsafe { use x86_64::instructions::port::Port; - // Unmask IRQ 11 (bit 3 on PIC2) for VirtIO block device + // Unmask IRQ 11 (bit 3 on PIC2) let mut port2: Port = Port::new(0xA1); // PIC2 data port let mask2 = port2.read() & !0b00001000; // Clear bit 3 (IRQ 11 = 8 + 3) port2.write(mask2); @@ -232,10 +232,15 @@ pub fn enable_virtio_irq() { let mask1_cascade = port1.read() & !0b00000100; // Clear bit 2 (cascade) port1.write(mask1_cascade); - log::debug!("VirtIO IRQ 11 enabled"); + log::debug!("IRQ 11 enabled (VirtIO + E1000)"); } } +/// Legacy alias for enable_irq11 +pub fn enable_virtio_irq() { + enable_irq11(); +} + extern "x86-interrupt" fn debug_handler(stack_frame: InterruptStackFrame) { // Enter exception context - use preempt_disable for exceptions (not IRQs) crate::per_cpu::preempt_disable(); @@ -460,27 +465,26 @@ extern "x86-interrupt" fn serial_interrupt_handler(_stack_frame: InterruptStackF } -/// VirtIO block device interrupt handler +/// Shared IRQ 11 handler for VirtIO block and E1000 network devices /// /// CRITICAL: This handler must be extremely fast. No logging, no allocations. /// Target: <1000 cycles total. -/// -/// NOTE: Handler callback mechanism removed due to boot hang issue with atomic statics. -/// For now, this is a minimal handler that just sends EOI. -/// The VirtIO driver can poll for completion or use a different signaling mechanism. -extern "x86-interrupt" fn virtio_block_interrupt_handler(_stack_frame: InterruptStackFrame) { +extern "x86-interrupt" fn irq11_handler(_stack_frame: InterruptStackFrame) { // Enter hardware IRQ context crate::per_cpu::irq_enter(); - // Call the device's interrupt handler if it exists + // Dispatch to VirtIO block if present if let Some(device) = crate::drivers::virtio::block::get_device() { device.handle_interrupt(); } + // Dispatch to E1000 network if initialized + crate::drivers::e1000::handle_interrupt(); + // Send EOI to both PICs (IRQ 11 is on PIC2) unsafe { PICS.lock() - .notify_end_of_interrupt(InterruptIndex::VirtioBlock.as_u8()); + .notify_end_of_interrupt(InterruptIndex::Irq11.as_u8()); } // Exit hardware IRQ context diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 46d1ba7..a8362e8 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -30,6 +30,7 @@ mod drivers; mod elf; mod framebuffer; mod gdt; +mod net; #[cfg(feature = "testing")] mod gdt_tests; mod test_checkpoints; @@ -173,6 +174,9 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! { let pci_device_count = drivers::init(); log::info!("PCI subsystem initialized: {} devices found", pci_device_count); + // Initialize network stack (after E1000 driver is ready) + net::init(); + // Update IST stacks with per-CPU emergency stacks gdt::update_ist_stacks(); log::info!("Updated IST stacks with per-CPU emergency and page fault stacks"); diff --git a/kernel/src/memory/heap.rs b/kernel/src/memory/heap.rs index acb717b..bc7c6aa 100644 --- a/kernel/src/memory/heap.rs +++ b/kernel/src/memory/heap.rs @@ -86,6 +86,12 @@ pub fn init(mapper: &OffsetPageTable<'static>) -> Result<(), &'static str> { for page in Page::range_inclusive(heap_start_page, heap_end_page) { let frame = crate::memory::frame_allocator::allocate_frame().ok_or("out of memory")?; + let frame_phys = frame.start_address().as_u64(); + + // Log the first few frame allocations for debugging + if frame_phys > 0xFFFF_FFFF { + log::error!("HEAP: Allocated frame {:#x} > 4GB - DMA will fail!", frame_phys); + } let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; diff --git a/kernel/src/memory/mod.rs b/kernel/src/memory/mod.rs index e0db867..9fb5164 100644 --- a/kernel/src/memory/mod.rs +++ b/kernel/src/memory/mod.rs @@ -12,11 +12,17 @@ pub mod vma; use bootloader_api::info::MemoryRegions; use conquer_once::spin::OnceCell; +use spin::Mutex; +use x86_64::structures::paging::{Mapper, Page, PageTableFlags, PhysFrame, Size4KiB}; use x86_64::{PhysAddr, VirtAddr}; /// Global physical memory offset for use throughout the kernel static PHYSICAL_MEMORY_OFFSET: OnceCell = OnceCell::uninit(); +/// Next available MMIO virtual address +#[allow(dead_code)] // Used by map_mmio for device driver MMIO mappings +static MMIO_NEXT_ADDR: Mutex = Mutex::new(layout::MMIO_BASE); + /// Initialize the memory subsystem pub fn init(physical_memory_offset: VirtAddr, memory_regions: &'static MemoryRegions) { log::info!("Initializing memory management..."); @@ -187,3 +193,181 @@ pub fn debug_memory_info() { log::info!("============================="); } + +/// Map a physical MMIO region into kernel virtual address space +/// +/// Allocates virtual address space from the MMIO region and creates page table +/// mappings for the given physical address range. +/// +/// Returns the virtual address where the MMIO region is mapped. +pub fn map_mmio(phys_addr: u64, size: usize) -> Result { + let phys_offset = physical_memory_offset(); + + // Align size up to page boundary + let size_aligned = (size + 0xFFF) & !0xFFF; + let num_pages = size_aligned / 4096; + + // Allocate virtual address space + let virt_addr = { + let mut next = MMIO_NEXT_ADDR.lock(); + let addr = *next; + *next += size_aligned as u64; + addr + }; + + log::info!( + "MMIO: Mapping {:#x} -> {:#x} ({} pages)", + phys_addr, + virt_addr, + num_pages + ); + + // Get mapper + let mut mapper = unsafe { paging::get_mapper_with_offset(phys_offset) }; + + // Map each page with uncacheable flags + for i in 0..num_pages { + let page_phys = phys_addr + (i * 4096) as u64; + let page_virt = virt_addr + (i * 4096) as u64; + + let page = Page::::containing_address(VirtAddr::new(page_virt)); + let frame = PhysFrame::::containing_address(PhysAddr::new(page_phys)); + + // Use write-through, no-cache flags for MMIO + let flags = PageTableFlags::PRESENT + | PageTableFlags::WRITABLE + | PageTableFlags::NO_CACHE + | PageTableFlags::WRITE_THROUGH; + + unsafe { + mapper + .map_to( + page, + frame, + flags, + &mut frame_allocator::GlobalFrameAllocator, + ) + .map_err(|_| "Failed to map MMIO page")? + .flush(); + } + } + + Ok(virt_addr as usize) +} + +/// Wrapper for PhysAddr operations that converts kernel virtual addresses +/// to physical addresses +pub struct PhysAddrWrapper; + +impl PhysAddrWrapper { + /// Convert a kernel virtual address to a physical address + /// + /// This function handles several types of kernel addresses: + /// 1. Direct physical memory mappings (starting at phys_offset) - subtract offset + /// 2. Heap and other mapped regions - walk page tables to find physical address + /// + /// NOTE: The direct physical memory map starts at phys_offset and extends for + /// the size of physical RAM (~500MB in QEMU). Addresses like the heap (0x4444_4444_0000) + /// are NOT in this region even though they may be numerically greater than phys_offset. + pub fn from_kernel_virt(virt: usize) -> u64 { + let phys_offset = physical_memory_offset(); + let heap_start = crate::memory::heap::HEAP_START; + let heap_end = heap_start + crate::memory::heap::HEAP_SIZE; + + // Check if this is a heap address - these are mapped, not direct + let is_heap = (virt as u64) >= heap_start && (virt as u64) < heap_end; + + // The direct physical memory map starts at phys_offset. + // We can detect if an address is truly in the direct map by checking: + // 1. It's >= phys_offset + // 2. Subtracting phys_offset gives a reasonable physical address (< 4GB typically) + // 3. It's NOT in a known non-direct-mapped region (heap, MMIO, stack, etc.) + if !is_heap && (virt as u64) >= phys_offset.as_u64() { + let candidate_phys = (virt as u64) - phys_offset.as_u64(); + // Physical RAM is typically < 4GB in our QEMU setup (512MB max) + // If the result is reasonable, it's likely a direct map address + if candidate_phys < 0x1_0000_0000 { + return candidate_phys; + } + } + + // For heap and other mapped regions, use the page table entry directly + // We can't use translate_addr because it adds the offset back when reading PTEs + use x86_64::registers::control::Cr3; + use x86_64::structures::paging::PageTable; + + let virt_addr = VirtAddr::new(virt as u64); + + // Get page table indices for this virtual address + let p4_idx = (virt_addr.as_u64() >> 39) & 0x1FF; + let p3_idx = (virt_addr.as_u64() >> 30) & 0x1FF; + let p2_idx = (virt_addr.as_u64() >> 21) & 0x1FF; + let p1_idx = (virt_addr.as_u64() >> 12) & 0x1FF; + let page_offset = virt_addr.as_u64() & 0xFFF; + + // Get the CR3 (PML4 physical address) + let (pml4_frame, _) = Cr3::read(); + let pml4_phys = pml4_frame.start_address().as_u64(); + + // Access PML4 through physical memory mapping + let pml4_virt = phys_offset.as_u64() + pml4_phys; + let pml4 = unsafe { &*(pml4_virt as *const PageTable) }; + + let p4_entry = &pml4[p4_idx as usize]; + if !p4_entry.flags().contains(x86_64::structures::paging::PageTableFlags::PRESENT) { + log::error!("PhysAddrWrapper: PML4[{}] not present for virt {:#x}", p4_idx, virt); + return virt as u64; + } + + // Get PDPT + let pdpt_phys = p4_entry.addr().as_u64(); + let pdpt_virt = phys_offset.as_u64() + pdpt_phys; + let pdpt = unsafe { &*(pdpt_virt as *const PageTable) }; + + let p3_entry = &pdpt[p3_idx as usize]; + if !p3_entry.flags().contains(x86_64::structures::paging::PageTableFlags::PRESENT) { + log::error!("PhysAddrWrapper: PDPT[{}] not present for virt {:#x}", p3_idx, virt); + return virt as u64; + } + + // Check for 1GB huge page + if p3_entry.flags().contains(x86_64::structures::paging::PageTableFlags::HUGE_PAGE) { + let phys = p3_entry.addr().as_u64() + (virt_addr.as_u64() & 0x3FFFFFFF); + return phys; + } + + // Get PD + let pd_phys = p3_entry.addr().as_u64(); + let pd_virt = phys_offset.as_u64() + pd_phys; + let pd = unsafe { &*(pd_virt as *const PageTable) }; + + let p2_entry = &pd[p2_idx as usize]; + if !p2_entry.flags().contains(x86_64::structures::paging::PageTableFlags::PRESENT) { + log::error!("PhysAddrWrapper: PD[{}] not present for virt {:#x}", p2_idx, virt); + return virt as u64; + } + + // Check for 2MB huge page + if p2_entry.flags().contains(x86_64::structures::paging::PageTableFlags::HUGE_PAGE) { + let phys = p2_entry.addr().as_u64() + (virt_addr.as_u64() & 0x1FFFFF); + return phys; + } + + // Get PT + let pt_phys = p2_entry.addr().as_u64(); + let pt_virt = phys_offset.as_u64() + pt_phys; + let pt = unsafe { &*(pt_virt as *const PageTable) }; + + let p1_entry = &pt[p1_idx as usize]; + if !p1_entry.flags().contains(x86_64::structures::paging::PageTableFlags::PRESENT) { + log::error!("PhysAddrWrapper: PT[{}] not present for virt {:#x}", p1_idx, virt); + return virt as u64; + } + + // Final physical address + let frame_phys = p1_entry.addr().as_u64(); + let phys = frame_phys + page_offset; + + phys + } +} diff --git a/kernel/src/net/arp.rs b/kernel/src/net/arp.rs new file mode 100644 index 0000000..204c10d --- /dev/null +++ b/kernel/src/net/arp.rs @@ -0,0 +1,277 @@ +//! ARP (Address Resolution Protocol) implementation +//! +//! Implements RFC 826 for IPv4-to-Ethernet address resolution. + +use alloc::vec::Vec; +use spin::Mutex; + +use super::ethernet::{self, EthernetFrame, BROADCAST_MAC, ETHERTYPE_ARP}; +use crate::drivers::e1000; + +/// ARP hardware type for Ethernet +pub const ARP_HTYPE_ETHERNET: u16 = 1; + +/// ARP protocol type for IPv4 +pub const ARP_PTYPE_IPV4: u16 = 0x0800; + +/// ARP operation: request +pub const ARP_OP_REQUEST: u16 = 1; + +/// ARP operation: reply +pub const ARP_OP_REPLY: u16 = 2; + +/// ARP packet header size for Ethernet/IPv4 +pub const ARP_PACKET_SIZE: usize = 28; + +/// Maximum ARP cache entries +const ARP_CACHE_SIZE: usize = 16; + +/// ARP cache entry +#[derive(Clone, Copy)] +struct ArpCacheEntry { + ip: [u8; 4], + mac: [u8; 6], + valid: bool, +} + +impl Default for ArpCacheEntry { + fn default() -> Self { + ArpCacheEntry { + ip: [0; 4], + mac: [0; 6], + valid: false, + } + } +} + +/// ARP cache +static ARP_CACHE: Mutex<[ArpCacheEntry; ARP_CACHE_SIZE]> = + Mutex::new([ArpCacheEntry { ip: [0; 4], mac: [0; 6], valid: false }; ARP_CACHE_SIZE]); + +/// Initialize ARP subsystem +pub fn init() { + // Cache is already initialized with default values + log::debug!("ARP: Cache initialized ({} entries)", ARP_CACHE_SIZE); +} + +/// Parsed ARP packet +#[derive(Debug)] +#[allow(dead_code)] // Protocol fields - all are part of the ARP specification +pub struct ArpPacket { + /// Hardware type (should be 1 for Ethernet) + pub htype: u16, + /// Protocol type (should be 0x0800 for IPv4) + pub ptype: u16, + /// Hardware address length (6 for Ethernet) + pub hlen: u8, + /// Protocol address length (4 for IPv4) + pub plen: u8, + /// Operation (1 = request, 2 = reply) + pub operation: u16, + /// Sender hardware address (MAC) + pub sender_mac: [u8; 6], + /// Sender protocol address (IP) + pub sender_ip: [u8; 4], + /// Target hardware address (MAC) + pub target_mac: [u8; 6], + /// Target protocol address (IP) + pub target_ip: [u8; 4], +} + +impl ArpPacket { + /// Parse an ARP packet from raw bytes + pub fn parse(data: &[u8]) -> Option { + if data.len() < ARP_PACKET_SIZE { + return None; + } + + let htype = u16::from_be_bytes([data[0], data[1]]); + let ptype = u16::from_be_bytes([data[2], data[3]]); + let hlen = data[4]; + let plen = data[5]; + let operation = u16::from_be_bytes([data[6], data[7]]); + + // Validate this is Ethernet/IPv4 ARP + if htype != ARP_HTYPE_ETHERNET || ptype != ARP_PTYPE_IPV4 || hlen != 6 || plen != 4 { + return None; + } + + let sender_mac = [data[8], data[9], data[10], data[11], data[12], data[13]]; + let sender_ip = [data[14], data[15], data[16], data[17]]; + let target_mac = [data[18], data[19], data[20], data[21], data[22], data[23]]; + let target_ip = [data[24], data[25], data[26], data[27]]; + + Some(ArpPacket { + htype, + ptype, + hlen, + plen, + operation, + sender_mac, + sender_ip, + target_mac, + target_ip, + }) + } + + /// Build an ARP packet + pub fn build( + operation: u16, + sender_mac: &[u8; 6], + sender_ip: &[u8; 4], + target_mac: &[u8; 6], + target_ip: &[u8; 4], + ) -> Vec { + let mut packet = Vec::with_capacity(ARP_PACKET_SIZE); + + // Hardware type (Ethernet = 1) + packet.extend_from_slice(&ARP_HTYPE_ETHERNET.to_be_bytes()); + // Protocol type (IPv4 = 0x0800) + packet.extend_from_slice(&ARP_PTYPE_IPV4.to_be_bytes()); + // Hardware address length (6) + packet.push(6); + // Protocol address length (4) + packet.push(4); + // Operation + packet.extend_from_slice(&operation.to_be_bytes()); + // Sender MAC + packet.extend_from_slice(sender_mac); + // Sender IP + packet.extend_from_slice(sender_ip); + // Target MAC + packet.extend_from_slice(target_mac); + // Target IP + packet.extend_from_slice(target_ip); + + packet + } +} + +/// Handle an incoming ARP packet +pub fn handle_arp(eth_frame: &EthernetFrame, arp: &ArpPacket) { + let config = super::config(); + let our_mac = match e1000::mac_address() { + Some(mac) => mac, + None => return, + }; + + // Always learn from ARP packets (update cache with sender info) + update_cache(&arp.sender_ip, &arp.sender_mac); + + // Check if this ARP is for us + if arp.target_ip != config.ip_addr { + return; + } + + match arp.operation { + ARP_OP_REQUEST => { + // Send ARP reply + log::debug!( + "ARP: Request from {}.{}.{}.{} for our IP", + arp.sender_ip[0], arp.sender_ip[1], arp.sender_ip[2], arp.sender_ip[3] + ); + + let reply = ArpPacket::build( + ARP_OP_REPLY, + &our_mac, + &config.ip_addr, + &arp.sender_mac, + &arp.sender_ip, + ); + + let frame = ethernet::EthernetFrame::build( + &our_mac, + ð_frame.src_mac, + ETHERTYPE_ARP, + &reply, + ); + + if let Err(e) = e1000::transmit(&frame) { + log::warn!("ARP: Failed to send reply: {}", e); + } else { + log::debug!("ARP: Sent reply"); + } + } + ARP_OP_REPLY => { + log::debug!( + "ARP: Reply from {}.{}.{}.{} -> {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + arp.sender_ip[0], arp.sender_ip[1], arp.sender_ip[2], arp.sender_ip[3], + arp.sender_mac[0], arp.sender_mac[1], arp.sender_mac[2], + arp.sender_mac[3], arp.sender_mac[4], arp.sender_mac[5] + ); + // Already updated cache above + } + _ => {} + } +} + +/// Update the ARP cache with a new entry +fn update_cache(ip: &[u8; 4], mac: &[u8; 6]) { + let mut cache = ARP_CACHE.lock(); + + // First, check if entry already exists + for entry in cache.iter_mut() { + if entry.valid && entry.ip == *ip { + entry.mac = *mac; + return; + } + } + + // Find an empty slot + for entry in cache.iter_mut() { + if !entry.valid { + entry.ip = *ip; + entry.mac = *mac; + entry.valid = true; + return; + } + } + + // Cache full - replace first entry (simple replacement policy) + cache[0].ip = *ip; + cache[0].mac = *mac; + cache[0].valid = true; +} + +/// Look up a MAC address in the ARP cache +pub fn lookup(ip: &[u8; 4]) -> Option<[u8; 6]> { + let cache = ARP_CACHE.lock(); + + for entry in cache.iter() { + if entry.valid && entry.ip == *ip { + return Some(entry.mac); + } + } + + None +} + +/// Send an ARP request for an IP address +pub fn request(target_ip: &[u8; 4]) -> Result<(), &'static str> { + let config = super::config(); + let our_mac = e1000::mac_address().ok_or("E1000 not initialized")?; + + let arp_packet = ArpPacket::build( + ARP_OP_REQUEST, + &our_mac, + &config.ip_addr, + &[0, 0, 0, 0, 0, 0], // Unknown target MAC + target_ip, + ); + + let frame = ethernet::EthernetFrame::build( + &our_mac, + &BROADCAST_MAC, + ETHERTYPE_ARP, + &arp_packet, + ); + + e1000::transmit(&frame)?; + + log::debug!( + "ARP: Sent request for {}.{}.{}.{}", + target_ip[0], target_ip[1], target_ip[2], target_ip[3] + ); + + Ok(()) +} diff --git a/kernel/src/net/ethernet.rs b/kernel/src/net/ethernet.rs new file mode 100644 index 0000000..82e4947 --- /dev/null +++ b/kernel/src/net/ethernet.rs @@ -0,0 +1,95 @@ +//! Ethernet frame parsing and construction +//! +//! Implements IEEE 802.3 Ethernet II frame format. + +use alloc::vec::Vec; + +/// Ethernet frame header size (without VLAN tag) +pub const ETHERNET_HEADER_SIZE: usize = 14; + +/// Minimum Ethernet frame size (excluding FCS) +pub const ETHERNET_MIN_SIZE: usize = 60; + +/// Maximum Ethernet payload size (MTU) +#[allow(dead_code)] // Standard constant for reference +pub const ETHERNET_MTU: usize = 1500; + +/// Broadcast MAC address +pub const BROADCAST_MAC: [u8; 6] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + +/// EtherType for IPv4 +pub const ETHERTYPE_IPV4: u16 = 0x0800; + +/// EtherType for ARP +pub const ETHERTYPE_ARP: u16 = 0x0806; + +/// EtherType for IPv6 +#[allow(dead_code)] +pub const ETHERTYPE_IPV6: u16 = 0x86DD; + +/// Parsed Ethernet frame +#[derive(Debug)] +#[allow(dead_code)] // Protocol fields - all are part of Ethernet frame +pub struct EthernetFrame<'a> { + /// Destination MAC address + pub dst_mac: [u8; 6], + /// Source MAC address + pub src_mac: [u8; 6], + /// EtherType field + pub ethertype: u16, + /// Frame payload + pub payload: &'a [u8], +} + +impl<'a> EthernetFrame<'a> { + /// Parse an Ethernet frame from raw bytes + pub fn parse(data: &'a [u8]) -> Option { + if data.len() < ETHERNET_HEADER_SIZE { + return None; + } + + let dst_mac = [data[0], data[1], data[2], data[3], data[4], data[5]]; + let src_mac = [data[6], data[7], data[8], data[9], data[10], data[11]]; + let ethertype = u16::from_be_bytes([data[12], data[13]]); + + Some(EthernetFrame { + dst_mac, + src_mac, + ethertype, + payload: &data[ETHERNET_HEADER_SIZE..], + }) + } + + /// Build an Ethernet frame + pub fn build(src_mac: &[u8; 6], dst_mac: &[u8; 6], ethertype: u16, payload: &[u8]) -> Vec { + let mut frame = Vec::with_capacity(ETHERNET_HEADER_SIZE + payload.len()); + + // Destination MAC + frame.extend_from_slice(dst_mac); + // Source MAC + frame.extend_from_slice(src_mac); + // EtherType + frame.extend_from_slice(ðertype.to_be_bytes()); + // Payload + frame.extend_from_slice(payload); + + // Pad to minimum frame size if needed + while frame.len() < ETHERNET_MIN_SIZE { + frame.push(0); + } + + frame + } +} + +/// Check if a MAC address is broadcast +#[allow(dead_code)] +pub fn is_broadcast(mac: &[u8; 6]) -> bool { + *mac == BROADCAST_MAC +} + +/// Check if a MAC address is multicast +#[allow(dead_code)] +pub fn is_multicast(mac: &[u8; 6]) -> bool { + mac[0] & 0x01 != 0 +} diff --git a/kernel/src/net/icmp.rs b/kernel/src/net/icmp.rs new file mode 100644 index 0000000..5d85516 --- /dev/null +++ b/kernel/src/net/icmp.rs @@ -0,0 +1,173 @@ +//! ICMP (Internet Control Message Protocol) implementation +//! +//! Implements ICMP echo (ping) request and reply (RFC 792). + +use alloc::vec::Vec; + +use super::ethernet::{EthernetFrame, ETHERTYPE_IPV4}; +use super::ipv4::{self, Ipv4Packet, PROTOCOL_ICMP}; +use crate::drivers::e1000; + +/// ICMP type: Echo Reply +pub const ICMP_ECHO_REPLY: u8 = 0; + +/// ICMP type: Destination Unreachable +#[allow(dead_code)] +pub const ICMP_DEST_UNREACHABLE: u8 = 3; + +/// ICMP type: Echo Request +pub const ICMP_ECHO_REQUEST: u8 = 8; + +/// ICMP header size +pub const ICMP_HEADER_SIZE: usize = 8; + +/// Parsed ICMP packet +#[derive(Debug)] +#[allow(dead_code)] // Protocol fields - all are part of ICMP specification +pub struct IcmpPacket<'a> { + /// ICMP type + pub icmp_type: u8, + /// ICMP code + pub code: u8, + /// Checksum + pub checksum: u16, + /// Identifier (for echo request/reply) + pub identifier: u16, + /// Sequence number (for echo request/reply) + pub sequence: u16, + /// Payload data + pub payload: &'a [u8], +} + +impl<'a> IcmpPacket<'a> { + /// Parse an ICMP packet from raw bytes + pub fn parse(data: &'a [u8]) -> Option { + if data.len() < ICMP_HEADER_SIZE { + return None; + } + + let icmp_type = data[0]; + let code = data[1]; + let checksum = u16::from_be_bytes([data[2], data[3]]); + let identifier = u16::from_be_bytes([data[4], data[5]]); + let sequence = u16::from_be_bytes([data[6], data[7]]); + let payload = &data[ICMP_HEADER_SIZE..]; + + Some(IcmpPacket { + icmp_type, + code, + checksum, + identifier, + sequence, + payload, + }) + } + + /// Build an ICMP echo request packet + pub fn echo_request(identifier: u16, sequence: u16, payload: &[u8]) -> Vec { + Self::build_echo(ICMP_ECHO_REQUEST, identifier, sequence, payload) + } + + /// Build an ICMP echo reply packet + pub fn echo_reply(identifier: u16, sequence: u16, payload: &[u8]) -> Vec { + Self::build_echo(ICMP_ECHO_REPLY, identifier, sequence, payload) + } + + /// Build an ICMP echo packet (request or reply) + fn build_echo(icmp_type: u8, identifier: u16, sequence: u16, payload: &[u8]) -> Vec { + let mut packet = Vec::with_capacity(ICMP_HEADER_SIZE + payload.len()); + + // Type + packet.push(icmp_type); + // Code (0 for echo) + packet.push(0); + // Checksum (placeholder) + packet.extend_from_slice(&[0, 0]); + // Identifier + packet.extend_from_slice(&identifier.to_be_bytes()); + // Sequence + packet.extend_from_slice(&sequence.to_be_bytes()); + // Payload + packet.extend_from_slice(payload); + + // Calculate and insert checksum + let checksum = ipv4::internet_checksum(&packet); + packet[2] = (checksum >> 8) as u8; + packet[3] = (checksum & 0xFF) as u8; + + packet + } +} + +/// Handle an incoming ICMP packet +pub fn handle_icmp(eth_frame: &EthernetFrame, ip: &Ipv4Packet, icmp: &IcmpPacket) { + match icmp.icmp_type { + ICMP_ECHO_REQUEST => { + log::info!( + "ICMP: Echo request from {}.{}.{}.{} seq={}", + ip.src_ip[0], ip.src_ip[1], ip.src_ip[2], ip.src_ip[3], + icmp.sequence + ); + + // Send echo reply + send_echo_reply(eth_frame, ip, icmp); + } + ICMP_ECHO_REPLY => { + log::info!( + "NET: ICMP echo reply received from {}.{}.{}.{} seq={}", + ip.src_ip[0], ip.src_ip[1], ip.src_ip[2], ip.src_ip[3], + icmp.sequence + ); + } + ICMP_DEST_UNREACHABLE => { + log::warn!( + "ICMP: Destination unreachable from {}.{}.{}.{} code={}", + ip.src_ip[0], ip.src_ip[1], ip.src_ip[2], ip.src_ip[3], + icmp.code + ); + } + _ => { + log::debug!("ICMP: Unknown type {} from {}.{}.{}.{}", + icmp.icmp_type, + ip.src_ip[0], ip.src_ip[1], ip.src_ip[2], ip.src_ip[3] + ); + } + } +} + +/// Send an ICMP echo reply +fn send_echo_reply(eth_frame: &EthernetFrame, ip: &Ipv4Packet, icmp: &IcmpPacket) { + let our_mac = match e1000::mac_address() { + Some(mac) => mac, + None => return, + }; + + let config = super::config(); + + // Build ICMP reply (same identifier, sequence, and payload as request) + let icmp_reply = IcmpPacket::echo_reply(icmp.identifier, icmp.sequence, icmp.payload); + + // Build IP packet + let ip_packet = Ipv4Packet::build( + config.ip_addr, + ip.src_ip, + PROTOCOL_ICMP, + &icmp_reply, + ); + + // Build Ethernet frame (reply to sender) + let frame = super::ethernet::EthernetFrame::build( + &our_mac, + ð_frame.src_mac, + ETHERTYPE_IPV4, + &ip_packet, + ); + + if let Err(e) = e1000::transmit(&frame) { + log::warn!("ICMP: Failed to send echo reply: {}", e); + } else { + log::debug!("ICMP: Sent echo reply to {}.{}.{}.{}", + ip.src_ip[0], ip.src_ip[1], ip.src_ip[2], ip.src_ip[3] + ); + } +} diff --git a/kernel/src/net/ipv4.rs b/kernel/src/net/ipv4.rs new file mode 100644 index 0000000..75ebc01 --- /dev/null +++ b/kernel/src/net/ipv4.rs @@ -0,0 +1,214 @@ +//! IPv4 packet parsing and construction +//! +//! Implements basic IPv4 packet handling (RFC 791). + +use alloc::vec::Vec; +use core::sync::atomic::{AtomicU16, Ordering}; + +use super::ethernet::EthernetFrame; +use super::icmp; + +/// IPv4 header minimum size (no options) +pub const IPV4_HEADER_MIN_SIZE: usize = 20; + +/// IPv4 protocol number for ICMP +pub const PROTOCOL_ICMP: u8 = 1; + +/// IPv4 protocol number for TCP +#[allow(dead_code)] +pub const PROTOCOL_TCP: u8 = 6; + +/// IPv4 protocol number for UDP +#[allow(dead_code)] +pub const PROTOCOL_UDP: u8 = 17; + +/// Default TTL for outgoing packets +pub const DEFAULT_TTL: u8 = 64; + +/// Parsed IPv4 packet +#[derive(Debug)] +#[allow(dead_code)] // Protocol fields - all are part of IPv4 header specification +pub struct Ipv4Packet<'a> { + /// Version (should be 4) + pub version: u8, + /// Header length in 32-bit words + pub ihl: u8, + /// Type of service / DSCP + pub tos: u8, + /// Total length + pub total_length: u16, + /// Identification + pub identification: u16, + /// Flags and fragment offset + pub flags_fragment: u16, + /// Time to live + pub ttl: u8, + /// Protocol + pub protocol: u8, + /// Header checksum + pub checksum: u16, + /// Source IP address + pub src_ip: [u8; 4], + /// Destination IP address + pub dst_ip: [u8; 4], + /// Payload (after header) + pub payload: &'a [u8], +} + +impl<'a> Ipv4Packet<'a> { + /// Parse an IPv4 packet from raw bytes + pub fn parse(data: &'a [u8]) -> Option { + if data.len() < IPV4_HEADER_MIN_SIZE { + return None; + } + + let version = (data[0] >> 4) & 0x0F; + let ihl = data[0] & 0x0F; + + // Validate version + if version != 4 { + return None; + } + + // Validate header length (minimum 5 = 20 bytes) + if ihl < 5 { + return None; + } + + let header_len = (ihl as usize) * 4; + if data.len() < header_len { + return None; + } + + let tos = data[1]; + let total_length = u16::from_be_bytes([data[2], data[3]]); + let identification = u16::from_be_bytes([data[4], data[5]]); + let flags_fragment = u16::from_be_bytes([data[6], data[7]]); + let ttl = data[8]; + let protocol = data[9]; + let checksum = u16::from_be_bytes([data[10], data[11]]); + let src_ip = [data[12], data[13], data[14], data[15]]; + let dst_ip = [data[16], data[17], data[18], data[19]]; + + // Validate total length + if (total_length as usize) > data.len() { + return None; + } + + let payload = &data[header_len..(total_length as usize)]; + + Some(Ipv4Packet { + version, + ihl, + tos, + total_length, + identification, + flags_fragment, + ttl, + protocol, + checksum, + src_ip, + dst_ip, + payload, + }) + } + + /// Build an IPv4 packet + pub fn build(src_ip: [u8; 4], dst_ip: [u8; 4], protocol: u8, payload: &[u8]) -> Vec { + let total_length = (IPV4_HEADER_MIN_SIZE + payload.len()) as u16; + + let mut packet = Vec::with_capacity(total_length as usize); + + // Version (4) + IHL (5 = 20 bytes) + packet.push(0x45); + // TOS + packet.push(0); + // Total length + packet.extend_from_slice(&total_length.to_be_bytes()); + // Identification (use a simple counter) + static PACKET_ID: AtomicU16 = AtomicU16::new(0); + let id = PACKET_ID.fetch_add(1, Ordering::Relaxed); + packet.extend_from_slice(&id.to_be_bytes()); + // Flags (Don't Fragment) + Fragment offset (0) + packet.extend_from_slice(&0x4000u16.to_be_bytes()); + // TTL + packet.push(DEFAULT_TTL); + // Protocol + packet.push(protocol); + // Checksum (placeholder - will calculate after) + packet.extend_from_slice(&[0, 0]); + // Source IP + packet.extend_from_slice(&src_ip); + // Destination IP + packet.extend_from_slice(&dst_ip); + + // Calculate and insert checksum + let checksum = internet_checksum(&packet[..IPV4_HEADER_MIN_SIZE]); + packet[10] = (checksum >> 8) as u8; + packet[11] = (checksum & 0xFF) as u8; + + // Payload + packet.extend_from_slice(payload); + + packet + } +} + +/// Handle an incoming IPv4 packet +pub fn handle_ipv4(eth_frame: &EthernetFrame, ip: &Ipv4Packet) { + let config = super::config(); + + // Check if this packet is for us + if ip.dst_ip != config.ip_addr { + // Not for us, ignore (we don't do routing) + return; + } + + // Verify checksum + // Note: In a production system, we'd verify the checksum here + // For now, we trust the NIC's checksum offload + + match ip.protocol { + PROTOCOL_ICMP => { + if let Some(icmp_packet) = icmp::IcmpPacket::parse(ip.payload) { + icmp::handle_icmp(eth_frame, ip, &icmp_packet); + } + } + PROTOCOL_TCP => { + // TCP not implemented yet + log::debug!("IPv4: Received TCP packet (not implemented)"); + } + PROTOCOL_UDP => { + // UDP not implemented yet + log::debug!("IPv4: Received UDP packet (not implemented)"); + } + _ => { + log::debug!("IPv4: Unknown protocol {}", ip.protocol); + } + } +} + +/// Calculate the Internet checksum (RFC 1071) +pub fn internet_checksum(data: &[u8]) -> u16 { + let mut sum: u32 = 0; + + // Sum 16-bit words + let mut i = 0; + while i + 1 < data.len() { + sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32; + i += 2; + } + + // Add odd byte if present + if i < data.len() { + sum += (data[i] as u32) << 8; + } + + // Fold 32-bit sum to 16 bits + while sum >> 16 != 0 { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + // One's complement + !(sum as u16) +} diff --git a/kernel/src/net/mod.rs b/kernel/src/net/mod.rs new file mode 100644 index 0000000..e559017 --- /dev/null +++ b/kernel/src/net/mod.rs @@ -0,0 +1,225 @@ +//! Network stack for Breenix +//! +//! Implements a minimal network stack with: +//! - Ethernet frame parsing and construction +//! - ARP for IPv4 address resolution +//! - IPv4 packet handling +//! - ICMP echo (ping) request/reply + +pub mod arp; +pub mod ethernet; +pub mod icmp; +pub mod ipv4; + +use spin::Mutex; + +use crate::drivers::e1000; + +/// Network interface configuration +#[derive(Clone, Copy, Debug)] +pub struct NetConfig { + /// Our IPv4 address + pub ip_addr: [u8; 4], + /// Subnet mask + pub subnet_mask: [u8; 4], + /// Default gateway + pub gateway: [u8; 4], +} + +/// Default network configuration for QEMU user-mode networking (SLIRP) +/// QEMU's default user-mode network uses 10.0.2.0/24 with gateway at 10.0.2.2 +#[allow(dead_code)] // Used conditionally without vmnet feature +pub const SLIRP_CONFIG: NetConfig = NetConfig { + ip_addr: [10, 0, 2, 15], // Guest IP + subnet_mask: [255, 255, 255, 0], + gateway: [10, 0, 2, 2], // QEMU gateway +}; + +/// Network configuration for macOS vmnet/bridge networking +/// socket_vmnet daemon uses 192.168.105.x (configured via --vmnet-gateway in plist) +/// The daemon runs DHCP but we use static IP to avoid waiting for DHCP +#[allow(dead_code)] // Used conditionally with vmnet feature +pub const VMNET_CONFIG: NetConfig = NetConfig { + ip_addr: [192, 168, 105, 100], // Static guest IP (avoiding DHCP conflicts) + subnet_mask: [255, 255, 255, 0], + gateway: [192, 168, 105, 1], // vmnet gateway (socket_vmnet default) +}; + +/// Select network config based on compile-time feature or default to SLIRP +/// Use VMNET_CONFIG when BREENIX_NET_MODE=vmnet is set at build time +#[cfg(feature = "vmnet")] +pub const DEFAULT_CONFIG: NetConfig = VMNET_CONFIG; + +#[cfg(not(feature = "vmnet"))] +pub const DEFAULT_CONFIG: NetConfig = SLIRP_CONFIG; + +static NET_CONFIG: Mutex = Mutex::new(DEFAULT_CONFIG); + +/// Initialize the network stack +pub fn init() { + log::info!("NET: Initializing network stack..."); + + if let Some(mac) = e1000::mac_address() { + log::info!( + "NET: MAC address: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + } + + let config = NET_CONFIG.lock(); + log::info!( + "NET: IP address: {}.{}.{}.{}", + config.ip_addr[0], config.ip_addr[1], config.ip_addr[2], config.ip_addr[3] + ); + log::info!( + "NET: Gateway: {}.{}.{}.{}", + config.gateway[0], config.gateway[1], config.gateway[2], config.gateway[3] + ); + + // Initialize ARP cache + arp::init(); + + log::info!("Network stack initialized"); + + // Send ARP request for gateway to test network connectivity + let gateway = config.gateway; + drop(config); // Release lock before calling arp::request + log::info!("NET: Sending ARP request for gateway {}.{}.{}.{}", + gateway[0], gateway[1], gateway[2], gateway[3]); + if let Err(e) = arp::request(&gateway) { + log::warn!("NET: Failed to send ARP request: {}", e); + return; + } + log::info!("ARP request sent successfully"); + + // Wait for ARP reply (poll RX a few times to get the gateway MAC) + // The reply comes via interrupt, so we just need to give it time to arrive + for _ in 0..50 { + process_rx(); + // Delay to let packets arrive and interrupts fire + for _ in 0..500000 { + core::hint::spin_loop(); + } + // Check if we got the ARP reply yet + if let Some(gateway_mac) = arp::lookup(&gateway) { + log::info!("NET: ARP resolved gateway MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + gateway_mac[0], gateway_mac[1], gateway_mac[2], + gateway_mac[3], gateway_mac[4], gateway_mac[5]); + break; + } + } + + // Check if ARP resolved the gateway + if arp::lookup(&gateway).is_none() { + log::warn!("NET: Gateway ARP not resolved, skipping ping test"); + return; + } + + // Send ICMP echo request (ping) to gateway + log::info!("NET: Sending ICMP echo request to gateway {}.{}.{}.{}", + gateway[0], gateway[1], gateway[2], gateway[3]); + if let Err(e) = ping(gateway) { + log::warn!("NET: Failed to send ping: {}", e); + return; + } + + // Poll for the ping reply (just process RX to handle incoming packets) + for _ in 0..20 { + process_rx(); + // Delay to let packets arrive and interrupts fire + for _ in 0..500000 { + core::hint::spin_loop(); + } + } + + log::info!("NET: Network initialization complete"); +} + +/// Get the current network configuration +pub fn config() -> NetConfig { + *NET_CONFIG.lock() +} + +/// Process incoming packets (called from interrupt handler or polling loop) +pub fn process_rx() { + let mut buffer = [0u8; 2048]; + + while e1000::can_receive() { + match e1000::receive(&mut buffer) { + Ok(len) => { + process_packet(&buffer[..len]); + } + Err(_) => break, + } + } +} + +/// Process a received Ethernet frame +fn process_packet(data: &[u8]) { + if let Some(frame) = ethernet::EthernetFrame::parse(data) { + match frame.ethertype { + ethernet::ETHERTYPE_ARP => { + if let Some(arp_packet) = arp::ArpPacket::parse(frame.payload) { + arp::handle_arp(&frame, &arp_packet); + } + } + ethernet::ETHERTYPE_IPV4 => { + if let Some(ip_packet) = ipv4::Ipv4Packet::parse(frame.payload) { + ipv4::handle_ipv4(&frame, &ip_packet); + } + } + _ => { + // Unknown ethertype, ignore + } + } + } +} + +/// Send an Ethernet frame +pub fn send_ethernet(dst_mac: &[u8; 6], ethertype: u16, payload: &[u8]) -> Result<(), &'static str> { + let src_mac = e1000::mac_address().ok_or("E1000 not initialized")?; + + let frame = ethernet::EthernetFrame::build(&src_mac, dst_mac, ethertype, payload); + e1000::transmit(&frame) +} + +/// Send an IPv4 packet +pub fn send_ipv4(dst_ip: [u8; 4], protocol: u8, payload: &[u8]) -> Result<(), &'static str> { + let config = config(); + + // Look up destination MAC in ARP cache + let dst_mac = if is_same_subnet(&dst_ip, &config.ip_addr, &config.subnet_mask) { + // Same subnet - ARP for destination directly + arp::lookup(&dst_ip).ok_or("ARP lookup failed - destination not in cache")? + } else { + // Different subnet - send to gateway + arp::lookup(&config.gateway).ok_or("ARP lookup failed - gateway not in cache")? + }; + + // Build IP packet + let ip_packet = ipv4::Ipv4Packet::build( + config.ip_addr, + dst_ip, + protocol, + payload, + ); + + send_ethernet(&dst_mac, ethernet::ETHERTYPE_IPV4, &ip_packet) +} + +/// Check if two IPs are on the same subnet +fn is_same_subnet(ip1: &[u8; 4], ip2: &[u8; 4], mask: &[u8; 4]) -> bool { + for i in 0..4 { + if (ip1[i] & mask[i]) != (ip2[i] & mask[i]) { + return false; + } + } + true +} + +/// Send an ICMP echo request (ping) +#[allow(dead_code)] // Public API +pub fn ping(dst_ip: [u8; 4]) -> Result<(), &'static str> { + let icmp_packet = icmp::IcmpPacket::echo_request(1, 1, b"breenix ping"); + send_ipv4(dst_ip, ipv4::PROTOCOL_ICMP, &icmp_packet) +} diff --git a/src/bin/qemu-uefi.rs b/src/bin/qemu-uefi.rs index 16ae9f2..e66305a 100644 --- a/src/bin/qemu-uefi.rs +++ b/src/bin/qemu-uefi.rs @@ -225,6 +225,69 @@ fn main() { } // Hint firmware to route stdout to serial (fw_cfg toggle; ignored if unsupported) qemu.args(["-fw_cfg", "name=opt/org.tianocore/StdoutToSerial,string=1"]); + + // Network configuration: BREENIX_NET_MODE controls the backend + // - "slirp" (default): User-mode NAT networking, no host interface needed + // - "vmnet": macOS vmnet-shared (requires sudo), creates real bridge interface + // - "socket_vmnet": Uses socket_vmnet daemon for host visibility WITHOUT sudo + // - "none": No networking + let net_mode = env::var("BREENIX_NET_MODE").unwrap_or_else(|_| "slirp".to_string()); + + // For socket_vmnet mode, we need to track that we'll wrap QEMU with the client + let use_socket_vmnet = net_mode == "socket_vmnet"; + + match net_mode.as_str() { + "vmnet" => { + // macOS vmnet-shared: Creates a real bridge interface on the host + // Requires: sudo or entitlements, QEMU built with vmnet support + // The guest gets DHCP from the host (192.168.64.x range typically) + // Host can see traffic with: sudo tcpdump -i bridge100 -nn + qemu.args([ + "-netdev", "vmnet-shared,id=net0", + "-device", "e1000,netdev=net0,mac=52:54:00:12:34:56", + ]); + eprintln!("[qemu-uefi] Network: vmnet-shared (host bridge interface)"); + eprintln!("[qemu-uefi] To observe traffic: sudo tcpdump -i bridge100 -nn icmp"); + eprintln!("[qemu-uefi] Guest will get IP via DHCP (192.168.64.x range)"); + } + "socket_vmnet" => { + // socket_vmnet: Privileged helper daemon provides vmnet access via Unix socket + // Install: brew install socket_vmnet && sudo brew services start socket_vmnet + // QEMU runs unprivileged; daemon handles vmnet interface creation + // File descriptor 3 is passed by socket_vmnet_client wrapper + // Build kernel with --features vmnet for 192.168.64.x static IP config + qemu.args([ + "-netdev", "socket,id=net0,fd=3", + "-device", "e1000,netdev=net0,mac=52:54:00:12:34:56", + ]); + eprintln!("[qemu-uefi] Network: socket_vmnet (host bridge via daemon)"); + eprintln!("[qemu-uefi] To observe traffic: sudo tcpdump -i bridge102 -nn 'arp or icmp'"); + eprintln!("[qemu-uefi] Guest IP: 192.168.105.100 (static, build with --features vmnet)"); + } + "none" => { + eprintln!("[qemu-uefi] Network: disabled"); + } + _ => { + // Default: SLIRP user-mode networking + // Traffic is internal to QEMU, not visible on host interfaces + qemu.args([ + "-netdev", "user,id=net0", + "-device", "e1000,netdev=net0,mac=52:54:00:12:34:56", + ]); + eprintln!("[qemu-uefi] Network: SLIRP user-mode (10.0.2.x internal)"); + } + } + + // Optional packet capture: BREENIX_PCAP_FILE=/path/to/capture.pcap + // Works with slirp and vmnet modes + if net_mode != "none" { + if let Ok(pcap_path) = env::var("BREENIX_PCAP_FILE") { + qemu.args([ + "-object", &format!("filter-dump,id=dump0,netdev=net0,file={}", pcap_path), + ]); + eprintln!("[qemu-uefi] Packet capture: {}", pcap_path); + } + } // Forward any additional command-line arguments to QEMU (runner may supply -serial ...) let extra_args: Vec = env::args().skip(1).collect(); if !extra_args.is_empty() { @@ -245,6 +308,65 @@ fn main() { eprintln!("[qemu-uefi] ════════════════════════════════════════════════════════"); } eprintln!("[qemu-uefi] Launching QEMU..."); - let exit_status = qemu.status().unwrap(); + + // For socket_vmnet mode, wrap QEMU with socket_vmnet_client + let exit_status = if use_socket_vmnet { + // socket_vmnet_client passes a vmnet file descriptor as fd=3 to the wrapped command + // Usage: socket_vmnet_client [args...] + let socket_vmnet_client = PathBuf::from("/opt/homebrew/opt/socket_vmnet/bin/socket_vmnet_client"); + let socket_path = PathBuf::from("/opt/homebrew/var/run/socket_vmnet"); + + if !socket_vmnet_client.exists() { + eprintln!(); + eprintln!("╔══════════════════════════════════════════════════════════════╗"); + eprintln!("║ ❌ ERROR: socket_vmnet_client NOT FOUND ║"); + eprintln!("╠══════════════════════════════════════════════════════════════╣"); + eprintln!("║ socket_vmnet is required for host-visible networking. ║"); + eprintln!("║ ║"); + eprintln!("║ To install: ║"); + eprintln!("║ brew install socket_vmnet ║"); + eprintln!("║ sudo brew services start socket_vmnet ║"); + eprintln!("║ ║"); + eprintln!("║ Or use SLIRP mode (no host visibility): ║"); + eprintln!("║ BREENIX_NET_MODE=slirp cargo run -p xtask -- boot-stages ║"); + eprintln!("╚══════════════════════════════════════════════════════════════╝"); + eprintln!(); + process::exit(1); + } + + if !socket_path.exists() { + eprintln!(); + eprintln!("╔══════════════════════════════════════════════════════════════╗"); + eprintln!("║ ❌ ERROR: socket_vmnet DAEMON NOT RUNNING ║"); + eprintln!("╠══════════════════════════════════════════════════════════════╣"); + eprintln!("║ The socket_vmnet daemon must be started first. ║"); + eprintln!("║ ║"); + eprintln!("║ To start: ║"); + eprintln!("║ sudo brew services start socket_vmnet ║"); + eprintln!("║ ║"); + eprintln!("║ To verify: ║"); + eprintln!("║ ls -la /opt/homebrew/var/run/socket_vmnet ║"); + eprintln!("╚══════════════════════════════════════════════════════════════╝"); + eprintln!(); + process::exit(1); + } + + // Build wrapper command: socket_vmnet_client qemu-system-x86_64 [qemu_args...] + let mut wrapper = Command::new(&socket_vmnet_client); + wrapper.arg(&socket_path); + wrapper.arg("qemu-system-x86_64"); + + // Get all the args we've built for QEMU and pass them through + // We need to extract the args from the qemu Command - use get_args() + for arg in qemu.get_args() { + wrapper.arg(arg); + } + + eprintln!("[qemu-uefi] Wrapping with socket_vmnet_client"); + wrapper.status().unwrap() + } else { + qemu.status().unwrap() + }; + process::exit(exit_status.code().unwrap_or(-1)); } \ No newline at end of file diff --git a/userspace/tests/syscall_diagnostic_test.rs b/userspace/tests/syscall_diagnostic_test.rs index d84dda4..f86b9bf 100644 --- a/userspace/tests/syscall_diagnostic_test.rs +++ b/userspace/tests/syscall_diagnostic_test.rs @@ -32,13 +32,18 @@ struct Timespec { } // Syscall wrappers +// Note: int 0x80 can clobber rcx and r11, and may access memory. +// We must declare these clobbers so the compiler doesn't keep local +// variables in those registers across syscalls. unsafe fn syscall0(n: u64) -> u64 { let ret: u64; core::arch::asm!( "int 0x80", in("rax") n, lateout("rax") ret, - options(nostack, preserves_flags) + lateout("rcx") _, + lateout("r11") _, + options(nostack) ); ret } @@ -50,7 +55,9 @@ unsafe fn syscall1(n: u64, arg1: u64) -> u64 { in("rax") n, in("rdi") arg1, lateout("rax") ret, - options(nostack, preserves_flags) + lateout("rcx") _, + lateout("r11") _, + options(nostack) ); ret } @@ -63,7 +70,9 @@ unsafe fn syscall2(n: u64, arg1: u64, arg2: u64) -> u64 { in("rdi") arg1, in("rsi") arg2, lateout("rax") ret, - options(nostack, preserves_flags) + lateout("rcx") _, + lateout("r11") _, + options(nostack) ); ret } @@ -77,7 +86,9 @@ unsafe fn syscall3(n: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 { in("rsi") arg2, in("rdx") arg3, lateout("rax") ret, - options(nostack, preserves_flags) + lateout("rcx") _, + lateout("r11") _, + options(nostack) ); ret } @@ -266,6 +277,7 @@ fn test_41d_register_preservation() -> bool { out("rax") _, lateout("rcx") _, lateout("r11") _, + options(nostack, preserves_flags) ); } @@ -368,6 +380,11 @@ pub extern "C" fn _start() -> ! { write_str("\n=== SUMMARY: "); write_num(passed); write_str("/5 tests passed ===\n"); + write_str("DEBUG: passed="); + write_num(passed); + write_str(", failed="); + write_num(failed); + write_str("\n"); if failed == 0 { write_str("\n✓ All diagnostic tests passed\n"); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index ab53dd0..8568c5a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -128,9 +128,15 @@ fn get_boot_stages() -> Vec { failure_meaning: "PCI enumeration failed or found no devices", check_hint: "drivers::pci::enumerate() - check I/O port access (0xCF8/0xCFC)", }, + BootStage { + name: "E1000 network device found", + marker: "E1000 network device found", + failure_meaning: "No E1000 network device detected on PCI bus - network I/O will fail", + check_hint: "Check QEMU e1000 configuration, verify vendor ID 0x8086 and device ID 0x100E", + }, BootStage { name: "VirtIO block device found", - marker: "PCI: Found VirtIO block device", + marker: "[1af4:1001] VirtIO MassStorage", failure_meaning: "No VirtIO block device detected - disk I/O will fail", check_hint: "Check QEMU virtio-blk-pci configuration, verify vendor ID 0x1AF4 and device ID 0x1001/0x1042", }, @@ -146,6 +152,36 @@ fn get_boot_stages() -> Vec { failure_meaning: "VirtIO disk I/O failed - cannot read from block device", check_hint: "drivers/virtio/block.rs:read_sector() - check descriptor chain setup and polling", }, + BootStage { + name: "E1000 driver initialized", + marker: "E1000 driver initialized", + failure_meaning: "E1000 device initialization failed - MMIO mapping, MAC setup, or link configuration issue", + check_hint: "drivers/e1000/mod.rs:init() - check MMIO BAR mapping, MAC address reading, and link status", + }, + BootStage { + name: "Network stack initialized", + marker: "Network stack initialized", + failure_meaning: "Network stack initialization failed - ARP cache or configuration issue", + check_hint: "net/mod.rs:init() - check network configuration and ARP cache initialization", + }, + BootStage { + name: "ARP request sent successfully", + marker: "ARP request sent successfully", + failure_meaning: "Failed to send ARP request for gateway - E1000 transmit path broken", + check_hint: "net/arp.rs:request() and drivers/e1000/mod.rs:transmit() - check TX descriptor setup and transmission", + }, + BootStage { + name: "ARP reply received and gateway MAC resolved", + marker: "NET: ARP resolved gateway MAC:", + failure_meaning: "ARP request was sent but no reply received - network RX path broken or gateway not responding", + check_hint: "net/arp.rs:handle_arp() - check E1000 RX descriptor processing, interrupt handling, and ARP reply parsing", + }, + BootStage { + name: "ICMP echo reply received from gateway", + marker: "NET: ICMP echo reply received from", + failure_meaning: "Ping was sent but no reply received - ICMP handling broken or gateway not responding to ping", + check_hint: "net/icmp.rs:handle_icmp() - check ICMP echo reply processing and IPv4 packet handling", + }, BootStage { name: "IST stacks updated", marker: "Updated IST stacks with per-CPU emergency",