diff --git a/Cargo.toml b/Cargo.toml index aa494d77..b7d0abfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ yboot-proto = { git = "https://git.alnyan.me/yggdrasil/yboot-proto.git" } aml = { git = "https://github.com/alnyan/acpi.git", version = "0.16.4" } acpi_lib = { git = "https://github.com/alnyan/acpi.git", version = "4.1.1", package = "acpi" } acpi-system = { git = "https://github.com/alnyan/acpi-system.git", version = "0.1.0" } +# TODO currently only supported here +xhci_lib = { git = "https://github.com/rust-osdev/xhci.git", package = "xhci" } [features] default = [] diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 0b2c7453..bbf042a2 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -2,7 +2,7 @@ use core::{ptr::NonNull, sync::atomic::Ordering}; use abi::error::Error; -use acpi_lib::{AcpiHandler, AcpiTables, HpetInfo, InterruptModel, PhysicalMapping}; +use acpi_lib::{mcfg::Mcfg, AcpiHandler, AcpiTables, HpetInfo, InterruptModel, PhysicalMapping}; use alloc::boxed::Box; use cpu::Cpu; use device_api::{ @@ -24,6 +24,7 @@ use crate::{ debug::{self, LogLevel}, device::{ self, + bus::pci::PciBusManager, display::{console, fb_console::FramebufferConsole, linear_fb::LinearFramebuffer}, tty::combined::CombinedTerminal, }, @@ -392,6 +393,14 @@ impl X86_64 { self.timer.init(Hpet::from_acpi(&hpet).unwrap()); acpi::init_acpi(acpi).unwrap(); + + // Enumerate PCIe root devices + // TODO can there be multiple MCFGs? + if let Ok(mcfg) = acpi.find_table::() { + for entry in mcfg.entries() { + PciBusManager::add_segment_from_mcfg(entry).unwrap(); + } + } } unsafe fn init_framebuffer(&'static self) { @@ -469,6 +478,9 @@ impl X86_64 { device::register_device(self.timer.get()); device::register_device(ps2); + // Initialize devices from PCI bus + PciBusManager::setup_bus_devices().unwrap(); + infoln!("Device list:"); for device in device::manager_lock().devices() { infoln!("* {}", device.display_name()); diff --git a/src/arch/x86_64/table/fixed.rs b/src/arch/x86_64/table/fixed.rs index 0bb7f2a8..ed6cbc7a 100644 --- a/src/arch/x86_64/table/fixed.rs +++ b/src/arch/x86_64/table/fixed.rs @@ -72,6 +72,28 @@ impl FixedTables { Ok(virt) } else { // 4KiB mappings + // Check if a mapping to that address already exists + if self.device_l3i >= count { + for i in 0..self.device_l3i { + let mut matches = true; + + for j in 0..count { + let page = phys + j * 0x1000; + let existing = self.device_l3[i].as_page().unwrap(); + + if page != existing { + matches = false; + break; + } + } + + if matches { + let virt = DEVICE_VIRT_OFFSET + (i << 12); + return Ok(virt); + } + } + } + if self.device_l3i + count > 512 { return Err(Error::OutOfMemory); } diff --git a/src/device/bus/mod.rs b/src/device/bus/mod.rs index 42007f9c..e6810506 100644 --- a/src/device/bus/mod.rs +++ b/src/device/bus/mod.rs @@ -2,3 +2,5 @@ #[cfg(feature = "device-tree")] pub mod simple_bus; + +pub mod pci; diff --git a/src/device/bus/pci/mod.rs b/src/device/bus/pci/mod.rs new file mode 100644 index 00000000..3754b413 --- /dev/null +++ b/src/device/bus/pci/mod.rs @@ -0,0 +1,238 @@ +use core::fmt; + +use acpi_lib::mcfg::McfgEntry; +use alloc::{rc::Rc, vec::Vec}; +use device_api::Device; +use yggdrasil_abi::error::Error; + +mod space; + +pub use space::{ + ecam::PciEcam, PciConfigSpace, PciConfigurationSpace, PciLegacyConfigurationSpace, +}; + +use crate::sync::IrqSafeSpinlock; + +pub const PCI_COMMAND_IO: u16 = 1 << 0; +pub const PCI_COMMAND_MEMORY: u16 = 1 << 1; +pub const PCI_COMMAND_BUS_MASTER: u16 = 1 << 2; +pub const PCI_COMMAND_INTERRUPT_DISABLE: u16 = 1 << 10; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct PciAddress { + pub segment: u8, + pub bus: u8, + pub device: u8, + pub function: u8, +} + +// TODO other attributes +#[derive(Debug)] +pub enum PciBaseAddress { + Memory(usize), + Io(u16), +} + +#[derive(Debug)] +pub struct PciDeviceInfo { + pub address: PciAddress, + pub config_space: PciConfigSpace, +} + +pub trait FromPciBus: Sized { + fn from_pci_bus(info: &PciDeviceInfo) -> Result; +} + +pub struct PciBusDevice { + info: PciDeviceInfo, + driver: Option>, +} + +pub struct PciBusSegment { + segment_number: u8, + bus_number_start: u8, + bus_number_end: u8, + ecam_phys_base: Option, + + devices: Vec, +} + +pub struct PciBusManager { + segments: Vec, +} + +impl PciBusSegment { + fn probe_config_space(&self, address: PciAddress) -> Result, Error> { + match self.ecam_phys_base { + Some(ecam_phys_base) => Ok(unsafe { + PciEcam::probe_raw_parts(ecam_phys_base, self.bus_number_start, address)? + } + .map(PciConfigSpace::Ecam)), + None => todo!(), + } + } + + fn enumerate_function( + &mut self, + parent: &mut PciBusManager, + address: PciAddress, + ) -> Result<(), Error> { + let Some(config) = self.probe_config_space(address)? else { + return Ok(()); + }; + + let header_type = config.header_type(); + + // Enumerate multi-function devices + if address.function == 0 && header_type & 0x80 != 0 { + for function in 1..8 { + self.enumerate_function(parent, address.with_function(function))?; + } + } + + // PCI-to-PCI bridge + // if config.class_code() == 0x06 && config.subclass() == 0x04 { + // let secondary_bus = config.secondary_bus(); + // // TODO + // } + + let info = PciDeviceInfo { + address, + config_space: config, + }; + self.devices.push(PciBusDevice { info, driver: None }); + + Ok(()) + } + + fn enumerate_bus(&mut self, parent: &mut PciBusManager, bus: u8) -> Result<(), Error> { + let address = PciAddress::for_bus(self.segment_number, bus); + + for i in 0..32 { + let device_address = address.with_device(i); + + self.enumerate_function(parent, device_address)?; + } + + Ok(()) + } + + pub fn enumerate(&mut self, parent: &mut PciBusManager) -> Result<(), Error> { + for bus in self.bus_number_start..self.bus_number_end { + self.enumerate_bus(parent, bus)?; + } + Ok(()) + } +} + +impl PciBusManager { + pub const fn new() -> Self { + Self { + segments: Vec::new(), + } + } + + pub fn setup_bus_devices() -> Result<(), Error> { + Self::walk_bus_devices(|device| { + setup_bus_device(device)?; + Ok(true) + }) + } + + pub fn walk_bus_devices Result>( + mut f: F, + ) -> Result<(), Error> { + let mut this = PCI_MANAGER.lock(); + + for segment in this.segments.iter_mut() { + for device in segment.devices.iter_mut() { + if !f(device)? { + return Ok(()); + } + } + } + + Ok(()) + } + + pub fn add_segment_from_mcfg(entry: &McfgEntry) -> Result<(), Error> { + let mut bus_segment = PciBusSegment { + segment_number: entry.pci_segment_group as u8, + bus_number_start: entry.bus_number_start, + bus_number_end: entry.bus_number_end, + ecam_phys_base: Some(entry.base_address as usize), + + devices: Vec::new(), + }; + + let mut this = PCI_MANAGER.lock(); + bus_segment.enumerate(&mut *this)?; + this.segments.push(bus_segment); + + Ok(()) + } +} + +impl fmt::Display for PciAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}:{}", self.bus, self.device, self.function) + } +} + +impl PciAddress { + pub const fn for_bus(segment: u8, bus: u8) -> Self { + Self { + segment, + bus, + device: 0, + function: 0, + } + } + + pub const fn with_device(self, device: u8) -> Self { + Self { + device, + function: 0, + ..self + } + } + + pub const fn with_function(self, function: u8) -> Self { + Self { function, ..self } + } +} + +impl PciConfigurationSpace for PciConfigSpace { + fn read_u32(&self, offset: usize) -> u32 { + match self { + Self::Ecam(ecam) => ecam.read_u32(offset), + _ => todo!(), + } + } +} + +fn setup_bus_device(device: &mut PciBusDevice) -> Result<(), Error> { + if device.driver.is_some() { + return Ok(()); + } + + let config = &device.info.config_space; + + debugln!( + "{}: {:04x}:{:04x}", + device.info.address, + config.vendor_id(), + config.device_id() + ); + + // Match by class + match (config.class_code(), config.subclass(), config.prog_if()) { + _ => { + debugln!(" -> No driver"); + } + } + + Ok(()) +} + +static PCI_MANAGER: IrqSafeSpinlock = IrqSafeSpinlock::new(PciBusManager::new()); diff --git a/src/device/bus/pci/space/ecam.rs b/src/device/bus/pci/space/ecam.rs new file mode 100644 index 00000000..078e2704 --- /dev/null +++ b/src/device/bus/pci/space/ecam.rs @@ -0,0 +1,79 @@ +use yggdrasil_abi::error::Error; + +use crate::{ + device::bus::pci::{PciConfigSpace, PciDeviceInfo}, + mem::{device::DeviceMemory, ConvertAddress}, +}; + +use super::{PciAddress, PciConfigurationSpace}; + +#[derive(Debug)] +#[repr(transparent)] +pub struct PciEcam { + mapping: DeviceMemory, +} + +// Only used for probing +#[derive(Debug)] +#[repr(transparent)] +struct PciRawEcam { + virt_addr: usize, +} + +impl PciConfigurationSpace for PciRawEcam { + fn read_u32(&self, offset: usize) -> u32 { + assert_eq!(offset & 3, 0); + unsafe { ((self.virt_addr + offset) as *const u32).read_volatile() } + } +} + +impl PciConfigurationSpace for PciEcam { + fn read_u32(&self, offset: usize) -> u32 { + assert_eq!(offset & 3, 0); + unsafe { ((self.mapping.base() + offset) as *const u32).read_volatile() } + } +} + +impl PciEcam { + pub unsafe fn map(phys_addr: usize) -> Result { + // TODO check align + let mapping = DeviceMemory::map("pcie-ecam", phys_addr, 0x1000)?; + + Ok(PciEcam { mapping }) + } + + pub unsafe fn from_raw_parts( + segment_phys_addr: usize, + bus_offset: u8, + address: PciAddress, + ) -> Result { + todo!() + } + + pub unsafe fn probe_raw_parts( + segment_phys_addr: usize, + bus_offset: u8, + address: PciAddress, + ) -> Result, Error> { + let phys_addr = segment_phys_addr + + ((address.bus - bus_offset) as usize * 256 + + address.device as usize * 8 + + address.function as usize) + * 0x1000; + + if phys_addr + 0xFFF < 0x100000000 { + // Probe without allocating a mapping + let raw = PciRawEcam { + virt_addr: phys_addr.virtualize(), + }; + + if !raw.is_valid() { + return Ok(None); + } + + Self::map(phys_addr).map(Some) + } else { + todo!() + } + } +} diff --git a/src/device/bus/pci/space/mod.rs b/src/device/bus/pci/space/mod.rs new file mode 100644 index 00000000..33d2ea27 --- /dev/null +++ b/src/device/bus/pci/space/mod.rs @@ -0,0 +1,131 @@ +use super::{PciAddress, PciBaseAddress, PciEcam}; + +pub(super) mod ecam; + +macro_rules! pci_config_field_getter { + (u32, $name:ident, $offset:expr) => { + fn $name(&self) -> u32 { + self.read_u32($offset) + } + }; + + (u16, $name:ident, $offset:expr) => { + fn $name(&self) -> u16 { + self.read_u16($offset) + } + }; + + (u8, $name:ident, $offset:expr) => { + fn $name(&self) -> u8 { + self.read_u8($offset) + } + }; +} + +macro_rules! pci_config_field { + ($offset:expr => $ty:ident, $getter:ident $(, $setter:ident)?) => { + pci_config_field_getter!($ty, $getter, $offset); + + $( + fn $setter(&self, value: $ty) { + todo!() + } + )? + }; +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct PciLegacyConfigurationSpace { + address: PciAddress, +} + +#[derive(Debug)] +pub enum PciConfigSpace { + Legacy(PciAddress), + Ecam(PciEcam), +} + +pub trait PciConfigurationSpace { + fn read_u32(&self, offset: usize) -> u32; + + fn read_u16(&self, offset: usize) -> u16 { + assert_eq!(offset & 1, 0); + let value = self.read_u32(offset & !3); + (value >> ((offset & 3) * 8)) as u16 + } + + fn read_u8(&self, offset: usize) -> u8 { + let value = self.read_u32(offset & !3); + (value >> ((offset & 3) * 8)) as u8 + } + + fn is_valid(&self) -> bool { + self.vendor_id() != 0xFFFF && self.device_id() != 0xFFFF + } + + pci_config_field!(0x00 => u16, vendor_id); + pci_config_field!(0x02 => u16, device_id); + pci_config_field!(0x04 => u16, command, set_command); + pci_config_field!(0x06 => u16, status); + + pci_config_field!(0x08 => u8, rev_id); + pci_config_field!(0x09 => u8, prog_if); + pci_config_field!(0x0A => u8, subclass); + pci_config_field!(0x0B => u8, class_code); + + // ... + pci_config_field!(0x0E => u8, header_type); + // Header Type 1 only + pci_config_field!(0x19 => u8, secondary_bus); + + // Header Type 0 only + fn bar(&self, index: usize) -> Option { + assert!(index < 6); + + if index % 2 == 0 { + let w0 = self.read_u32(0x10 + index * 4); + + match w0 & 0 { + 0 => match (w0 >> 1) & 3 { + 0 => { + // 32-bit memory BAR + Some(PciBaseAddress::Memory((w0 as usize) & !0xF)) + } + 2 => { + // 64-bit memory BAR + let w1 = self.read_u32(0x10 + (index + 1) * 4); + Some(PciBaseAddress::Memory( + ((w1 as usize) << 32) | ((w0 as usize) & !0xF), + )) + } + _ => unimplemented!(), + }, + 1 => todo!(), + _ => unreachable!(), + } + } else { + let prev_w0 = self.read_u32(0x10 + (index - 1) * 4); + if prev_w0 & 0x7 == 0x4 { + // Previous BAR is 64-bit memory and this one is its continuation + return None; + } + + let w0 = self.read_u32(0x10 + index * 4); + + match w0 & 0 { + 0 => match (w0 >> 1) & 3 { + 0 => { + // 32-bit memory BAR + Some(PciBaseAddress::Memory((w0 as usize) & !0xF)) + } + // TODO can 64-bit BARs not be on a 64-bit boundary? + 2 => todo!(), + _ => unimplemented!(), + }, + 1 => todo!(), + _ => unreachable!(), + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 63732170..c6a299f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ //! osdev-x kernel crate #![feature( + decl_macro, naked_functions, asm_const, panic_info_message, diff --git a/src/mem/device.rs b/src/mem/device.rs index f9b9f36b..ca7a2491 100644 --- a/src/mem/device.rs +++ b/src/mem/device.rs @@ -6,7 +6,7 @@ use abi::error::Error; use crate::arch::{Architecture, ARCHITECTURE}; /// Generic MMIO access mapping -#[derive(Clone)] +#[derive(Clone, Debug)] #[allow(unused)] pub struct DeviceMemory { name: &'static str,