diff --git a/src/lib.rs b/src/lib.rs index c3064a6..d45ea8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,11 +11,12 @@ extern crate alloc; pub mod allocator; pub mod gdt; pub mod interrupts; +pub mod pci; // Re-export commonly used interrupt functionality pub use interrupts::{ - subscribe, unsubscribe, subscribe_with_context, unsubscribe_with_context, - InterruptContext, SubscriberCallback, AdvancedSubscriberCallback + subscribe, subscribe_with_context, unsubscribe, unsubscribe_with_context, + AdvancedSubscriberCallback, InterruptContext, SubscriberCallback, }; pub mod memory; pub mod serial; diff --git a/src/main.rs b/src/main.rs index f30fc6b..ba09518 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,16 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { panic!("VMX not supported"); } + let devices = nel_os::pci::scan_devices(); + info!("PCI devices found: {}", devices.len()); + + for device in &devices { + info!( + " Device: {:x}:{:x} ({})", + device.vendor_id, device.device_id, device.class + ); + } + let mut vcpu = VCpu::new(phys_mem_offset.as_u64(), &mut frame_allocator); vcpu.activate(&mut frame_allocator, &mapper); diff --git a/src/pci/device.rs b/src/pci/device.rs new file mode 100644 index 0000000..fba08f0 --- /dev/null +++ b/src/pci/device.rs @@ -0,0 +1,256 @@ +use super::{ + read_config_u16, read_config_u32, read_config_u8, write_config_u32, ClassCode, HeaderType, + PciAddress, +}; +use alloc::vec::Vec; +use core::fmt; + +#[derive(Debug, Clone)] +pub struct PciDevice { + pub address: PciAddress, + pub vendor_id: u16, + pub device_id: u16, + pub command: u16, + pub status: u16, + pub class: ClassCode, + pub header_type: HeaderType, + pub bars: [Bar; 6], + pub interrupt_line: u8, + pub interrupt_pin: u8, + pub capabilities: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub enum Bar { + None, + Memory32 { + address: u32, + size: u32, + prefetchable: bool, + }, + Memory64 { + address: u64, + size: u64, + prefetchable: bool, + }, + Io { + address: u32, + size: u32, + }, +} + +#[derive(Debug, Clone)] +pub struct Capability { + pub id: u8, + pub offset: u8, + pub data: Vec, +} + +// PCI Command Register bits +pub const CMD_IO_SPACE: u16 = 0x0001; +pub const CMD_MEMORY_SPACE: u16 = 0x0002; +pub const CMD_BUS_MASTER: u16 = 0x0004; +pub const CMD_INTERRUPT_DISABLE: u16 = 0x0400; + +// PCI Status Register bits +pub const STATUS_INTERRUPT: u16 = 0x0008; +pub const STATUS_CAPABILITIES_LIST: u16 = 0x0010; + +// PCI Capability IDs +pub const CAP_MSI: u8 = 0x05; +pub const CAP_MSIX: u8 = 0x11; +pub const CAP_PCIE: u8 = 0x10; + +impl PciDevice { + pub fn new(address: PciAddress) -> Option { + let vendor_id = read_config_u16(address, 0x00); + if vendor_id == 0xFFFF { + return None; + } + + let device_id = read_config_u16(address, 0x02); + let command = read_config_u16(address, 0x04); + let status = read_config_u16(address, 0x06); + + let class_code = read_config_u8(address, 0x0B); + let sub_class = read_config_u8(address, 0x0A); + let interface = read_config_u8(address, 0x09); + + let header_type_raw = read_config_u8(address, 0x0E) & 0x7F; + let header_type = match header_type_raw { + 0x00 => HeaderType::Standard, + 0x01 => HeaderType::PciToPciBridge, + 0x02 => HeaderType::CardBusBridge, + _ => return None, + }; + + let mut device = Self { + address, + vendor_id, + device_id, + command, + status, + class: ClassCode { + base: class_code, + sub: sub_class, + interface, + }, + header_type, + bars: [Bar::None; 6], + interrupt_line: read_config_u8(address, 0x3C), + interrupt_pin: read_config_u8(address, 0x3D), + capabilities: Vec::new(), + }; + + if header_type == HeaderType::Standard { + device.read_bars(); + } + + if status & STATUS_CAPABILITIES_LIST != 0 { + device.read_capabilities(); + } + + Some(device) + } + + fn read_bars(&mut self) { + for i in 0..6 { + let bar_offset = 0x10 + (i * 4) as u8; + let bar_value = read_config_u32(self.address, bar_offset); + + if bar_value == 0 { + self.bars[i] = Bar::None; + continue; + } + + write_config_u32(self.address, bar_offset, 0xFFFFFFFF); + let size_mask = read_config_u32(self.address, bar_offset); + write_config_u32(self.address, bar_offset, bar_value); + + if bar_value & 0x01 != 0 { + let address = bar_value & 0xFFFFFFFC; + let size = (!size_mask & 0xFFFFFFFC).wrapping_add(1); + self.bars[i] = Bar::Io { address, size }; + } else { + let prefetchable = (bar_value & 0x08) != 0; + let bar_type = (bar_value >> 1) & 0x03; + + match bar_type { + 0 => { + // 32-bit memory + let address = bar_value & 0xFFFFFFF0; + let size = (!size_mask & 0xFFFFFFF0).wrapping_add(1); + self.bars[i] = Bar::Memory32 { + address, + size, + prefetchable, + }; + } + 2 => { + // 64-bit memory + if i < 5 { + let bar_high = read_config_u32(self.address, bar_offset + 4); + let address = + ((bar_high as u64) << 32) | (bar_value & 0xFFFFFFF0) as u64; + + write_config_u32(self.address, bar_offset + 4, 0xFFFFFFFF); + let size_mask_high = read_config_u32(self.address, bar_offset + 4); + write_config_u32(self.address, bar_offset + 4, bar_high); + + let size_mask_full = + ((size_mask_high as u64) << 32) | (size_mask & 0xFFFFFFF0) as u64; + let size = (!size_mask_full).wrapping_add(1); + + self.bars[i] = Bar::Memory64 { + address, + size, + prefetchable, + }; + self.bars[i + 1] = Bar::None; + } + } + _ => {} + } + } + } + } + + fn read_capabilities(&mut self) { + let mut cap_ptr = read_config_u8(self.address, 0x34) & 0xFC; + + while cap_ptr != 0 { + let cap_header = read_config_u32(self.address, cap_ptr); + let cap_id = (cap_header & 0xFF) as u8; + let next_ptr = ((cap_header >> 8) & 0xFF) as u8; + + // Read capability data based on ID + let data_size = match cap_id { + CAP_MSI => 24, // MSI capability + CAP_MSIX => 12, // MSI-X capability + CAP_PCIE => 60, // PCIe capability + _ => 4, // Unknown, read minimal + }; + + let mut data = Vec::new(); + for offset in (0..data_size).step_by(4) { + let value = read_config_u32(self.address, cap_ptr + offset); + data.extend_from_slice(&value.to_le_bytes()); + } + + self.capabilities.push(Capability { + id: cap_id, + offset: cap_ptr, + data, + }); + + cap_ptr = next_ptr & 0xFC; + } + } + + pub fn enable_bus_master(&mut self) { + self.command |= CMD_BUS_MASTER; + write_config_u32(self.address, 0x04, self.command as u32); + } + + pub fn enable_memory_space(&mut self) { + self.command |= CMD_MEMORY_SPACE; + write_config_u32(self.address, 0x04, self.command as u32); + } + + pub fn enable_io_space(&mut self) { + self.command |= CMD_IO_SPACE; + write_config_u32(self.address, 0x04, self.command as u32); + } + + pub fn disable_interrupts(&mut self) { + self.command |= CMD_INTERRUPT_DISABLE; + write_config_u32(self.address, 0x04, self.command as u32); + } + + pub fn find_capability(&self, cap_id: u8) -> Option<&Capability> { + self.capabilities.iter().find(|cap| cap.id == cap_id) + } + + pub fn has_msi(&self) -> bool { + self.find_capability(CAP_MSI).is_some() + } + + pub fn has_msix(&self) -> bool { + self.find_capability(CAP_MSIX).is_some() + } +} + +impl fmt::Display for PciDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{:02x}:{:02x}.{} [{:04x}:{:04x}] {}", + self.address.bus, + self.address.device, + self.address.function, + self.vendor_id, + self.device_id, + self.class + ) + } +} diff --git a/src/pci/mod.rs b/src/pci/mod.rs new file mode 100644 index 0000000..13cd976 --- /dev/null +++ b/src/pci/mod.rs @@ -0,0 +1,160 @@ +pub mod device; + +use core::fmt; +use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; + +const CONFIG_ADDRESS: u16 = 0xCF8; +const CONFIG_DATA: u16 = 0xCFC; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PciAddress { + pub bus: u8, + pub device: u8, + pub function: u8, +} + +impl PciAddress { + pub fn new(bus: u8, device: u8, function: u8) -> Self { + Self { + bus, + device, + function, + } + } + + fn to_address(&self, register: u8) -> u32 { + let bus = self.bus as u32; + let device = self.device as u32; + let function = self.function as u32; + let register = (register & 0xFC) as u32; + + 0x80000000 | (bus << 16) | (device << 11) | (function << 8) | register + } +} + +pub fn read_config_u32(addr: PciAddress, register: u8) -> u32 { + unsafe { + let mut address_port: Port = Port::new(CONFIG_ADDRESS); + let mut data_port: Port = Port::new(CONFIG_DATA); + + address_port.write(addr.to_address(register)); + data_port.read() + } +} + +pub fn write_config_u32(addr: PciAddress, register: u8, value: u32) { + unsafe { + let mut address_port: Port = Port::new(CONFIG_ADDRESS); + let mut data_port: Port = Port::new(CONFIG_DATA); + + address_port.write(addr.to_address(register)); + data_port.write(value); + } +} + +pub fn read_config_u16(addr: PciAddress, register: u8) -> u16 { + let value = read_config_u32(addr, register & 0xFC); + let offset = (register & 0x02) * 8; + (value >> offset) as u16 +} + +pub fn read_config_u8(addr: PciAddress, register: u8) -> u8 { + let value = read_config_u32(addr, register & 0xFC); + let offset = (register & 0x03) * 8; + (value >> offset) as u8 +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum HeaderType { + Standard = 0x00, + PciToPciBridge = 0x01, + CardBusBridge = 0x02, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ClassCode { + pub base: u8, + pub sub: u8, + pub interface: u8, +} + +impl fmt::Display for ClassCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let description = match (self.base, self.sub) { + (0x01, 0x00) => "SCSI Controller", + (0x01, 0x06) => "SATA Controller", + (0x02, 0x00) => "Ethernet Controller", + (0x03, 0x00) => "VGA Controller", + (0x06, 0x00) => "Host Bridge", + (0x06, 0x01) => "ISA Bridge", + (0x06, 0x04) => "PCI-to-PCI Bridge", + (0x0C, 0x03) => "USB Controller", + _ => "Unknown Device", + }; + write!( + f, + "{} ({:02x}:{:02x}:{:02x})", + description, self.base, self.sub, self.interface + ) + } +} + +pub fn scan_devices() -> alloc::vec::Vec { + use alloc::vec::Vec; + let mut devices = Vec::new(); + + for bus in 0..=255 { + for device in 0..32 { + let addr = PciAddress::new(bus, device, 0); + let vendor_id = read_config_u16(addr, 0x00); + + if vendor_id == 0xFFFF { + continue; + } + + let header_type = read_config_u8(addr, 0x0E); + let multifunction = (header_type & 0x80) != 0; + + if let Some(dev) = device::PciDevice::new(addr) { + devices.push(dev); + } + + if multifunction { + for function in 1..8 { + let addr = PciAddress::new(bus, device, function); + let vendor_id = read_config_u16(addr, 0x00); + + if vendor_id != 0xFFFF { + if let Some(dev) = device::PciDevice::new(addr) { + devices.push(dev); + } + } + } + } + } + } + + devices +} + +pub fn find_device(vendor_id: u16, device_id: u16) -> Option { + let devices = scan_devices(); + devices + .into_iter() + .find(|dev| dev.vendor_id == vendor_id && dev.device_id == device_id) +} + +pub fn find_devices_by_class( + base_class: u8, + sub_class: Option, +) -> alloc::vec::Vec { + let devices = scan_devices(); + devices + .into_iter() + .filter(|dev| { + dev.class.base == base_class + && (sub_class.is_none() || dev.class.sub == sub_class.unwrap()) + }) + .collect() +}