add pci support

This commit is contained in:
Masato Imai
2025-06-28 12:09:47 +00:00
parent e4b0ea7a91
commit 34a342b68c
4 changed files with 429 additions and 2 deletions

View File

@ -11,11 +11,12 @@ extern crate alloc;
pub mod allocator; pub mod allocator;
pub mod gdt; pub mod gdt;
pub mod interrupts; pub mod interrupts;
pub mod pci;
// Re-export commonly used interrupt functionality // Re-export commonly used interrupt functionality
pub use interrupts::{ pub use interrupts::{
subscribe, unsubscribe, subscribe_with_context, unsubscribe_with_context, subscribe, subscribe_with_context, unsubscribe, unsubscribe_with_context,
InterruptContext, SubscriberCallback, AdvancedSubscriberCallback AdvancedSubscriberCallback, InterruptContext, SubscriberCallback,
}; };
pub mod memory; pub mod memory;
pub mod serial; pub mod serial;

View File

@ -56,6 +56,16 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
panic!("VMX not supported"); 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); let mut vcpu = VCpu::new(phys_mem_offset.as_u64(), &mut frame_allocator);
vcpu.activate(&mut frame_allocator, &mapper); vcpu.activate(&mut frame_allocator, &mapper);

256
src/pci/device.rs Normal file
View File

@ -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<Capability>,
}
#[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<u8>,
}
// 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<Self> {
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
)
}
}

160
src/pci/mod.rs Normal file
View File

@ -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<u32> = Port::new(CONFIG_ADDRESS);
let mut data_port: Port<u32> = 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<u32> = Port::new(CONFIG_ADDRESS);
let mut data_port: Port<u32> = 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<device::PciDevice> {
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<device::PciDevice> {
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<u8>,
) -> alloc::vec::Vec<device::PciDevice> {
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()
}