diff --git a/kernel/comps/pci/src/arch/loongarch/mod.rs b/kernel/comps/pci/src/arch/loongarch/mod.rs index 39117ce83..43391a05a 100644 --- a/kernel/comps/pci/src/arch/loongarch/mod.rs +++ b/kernel/comps/pci/src/arch/loongarch/mod.rs @@ -17,19 +17,28 @@ use crate::PciDeviceLocation; static PCI_ECAM_CFG_SPACE: Once = Once::new(); pub(crate) fn write32(location: &PciDeviceLocation, offset: u32, value: u32) -> Result<(), Error> { + if offset > PCI_ECAM_MAX_OFFSET { + return Err(Error::InvalidArgs); + } PCI_ECAM_CFG_SPACE.get().ok_or(Error::IoError)?.write_once( - (encode_as_address_offset(location) | (offset & 0xfc)) as usize, + (encode_as_address_offset(location) | offset) as usize, &value, ) } pub(crate) fn read32(location: &PciDeviceLocation, offset: u32) -> Result { + if offset > PCI_ECAM_MAX_OFFSET { + return Err(Error::InvalidArgs); + } PCI_ECAM_CFG_SPACE .get() .ok_or(Error::IoError)? - .read_once((encode_as_address_offset(location) | (offset & 0xfc)) as usize) + .read_once((encode_as_address_offset(location) | offset) as usize) } +/// The maximum offset in the 12-bit configuration space when using [`encode_as_address_offset`]. +const PCI_ECAM_MAX_OFFSET: u32 = 0xffc; + /// Encodes the bus, device, and function into an address offset in the PCI MMIO region. fn encode_as_address_offset(location: &PciDeviceLocation) -> u32 { // We only support ECAM here for LoongArch platforms. Offsets are from diff --git a/kernel/comps/pci/src/arch/riscv/mod.rs b/kernel/comps/pci/src/arch/riscv/mod.rs index ba3af277e..c0b1f88d8 100644 --- a/kernel/comps/pci/src/arch/riscv/mod.rs +++ b/kernel/comps/pci/src/arch/riscv/mod.rs @@ -13,19 +13,28 @@ use crate::PciDeviceLocation; static PCI_ECAM_CFG_SPACE: Once = Once::new(); pub(crate) fn write32(location: &PciDeviceLocation, offset: u32, value: u32) -> Result<(), Error> { + if offset > PCI_ECAM_MAX_OFFSET { + return Err(Error::InvalidArgs); + } PCI_ECAM_CFG_SPACE.get().ok_or(Error::IoError)?.write_once( - (encode_as_address_offset(location) | (offset & 0xfc)) as usize, + (encode_as_address_offset(location) | offset) as usize, &value, ) } pub(crate) fn read32(location: &PciDeviceLocation, offset: u32) -> Result { + if offset > PCI_ECAM_MAX_OFFSET { + return Err(Error::InvalidArgs); + } PCI_ECAM_CFG_SPACE .get() .ok_or(Error::IoError)? - .read_once((encode_as_address_offset(location) | (offset & 0xfc)) as usize) + .read_once((encode_as_address_offset(location) | offset) as usize) } +/// The maximum offset in the 12-bit configuration space when using [`encode_as_address_offset`]. +const PCI_ECAM_MAX_OFFSET: u32 = 0xffc; + /// Encodes the bus, device, and function into an address offset in the PCI MMIO region. fn encode_as_address_offset(location: &PciDeviceLocation) -> u32 { // We only support ECAM here for RISC-V platforms. Offsets are from diff --git a/kernel/comps/pci/src/arch/x86/mod.rs b/kernel/comps/pci/src/arch/x86/mod.rs index eefb4197d..44f8ce12c 100644 --- a/kernel/comps/pci/src/arch/x86/mod.rs +++ b/kernel/comps/pci/src/arch/x86/mod.rs @@ -6,8 +6,12 @@ use core::ops::RangeInclusive; use ostd::{ Error, - arch::device::io_port::{ReadWriteAccess, WriteOnlyAccess}, - io::IoPort, + arch::{ + device::io_port::{ReadWriteAccess, WriteOnlyAccess}, + kernel::ACPI_INFO, + }, + io::{IoMem, IoPort}, + mm::VmIoOnce, sync::SpinLock, }; use spin::Once; @@ -21,23 +25,66 @@ struct AddressAndDataPort { static PCI_PIO_CFG_SPACE: Once> = Once::new(); -const BIT32_ALIGN_MASK: u32 = 0xFFFC; +static PCI_ECAM_CFG_SPACE: Once = Once::new(); pub(crate) fn write32(location: &PciDeviceLocation, offset: u32, value: u32) -> Result<(), Error> { - let pio = PCI_PIO_CFG_SPACE.get().ok_or(Error::IoError)?.lock(); - pio.address_port - .write(encode_as_port(location) | (offset & BIT32_ALIGN_MASK)); - pio.data_port.write(value.to_le()); - Ok(()) + if let Some(ecam) = PCI_ECAM_CFG_SPACE.get() { + if offset > PCI_ECAM_MAX_OFFSET { + return Err(Error::InvalidArgs); + } + ecam.write_once( + (encode_as_address_offset(location) | offset) as usize, + &value, + )?; + return Ok(()); + } + + if let Some(pio_ports) = PCI_PIO_CFG_SPACE.get() { + if offset > PCI_PIO_MAX_OFFSET { + return Err(Error::InvalidArgs); + } + let pio = pio_ports.lock(); + pio.address_port.write(encode_as_port(location) | offset); + pio.data_port.write(value.to_le()); + return Ok(()); + } + + Err(Error::IoError) } pub(crate) fn read32(location: &PciDeviceLocation, offset: u32) -> Result { - let pio = PCI_PIO_CFG_SPACE.get().ok_or(Error::IoError)?.lock(); - pio.address_port - .write(encode_as_port(location) | (offset & BIT32_ALIGN_MASK)); - Ok(pio.data_port.read().to_le()) + if let Some(ecam) = PCI_ECAM_CFG_SPACE.get() { + if offset > PCI_ECAM_MAX_OFFSET { + return Err(Error::InvalidArgs); + } + return ecam.read_once((encode_as_address_offset(location) | offset) as usize); + } + + if let Some(pio_ports) = PCI_PIO_CFG_SPACE.get() { + if offset > PCI_PIO_MAX_OFFSET { + return Err(Error::InvalidArgs); + } + let pio = pio_ports.lock(); + pio.address_port.write(encode_as_port(location) | offset); + return Ok(pio.data_port.read().to_le()); + } + + Err(Error::IoError) } +/// The maximum offset in the 12-bit configuration space when using [`encode_as_address_offset`]. +const PCI_ECAM_MAX_OFFSET: u32 = 0xffc; + +/// Encodes the bus, device, and function into an address offset in the PCI MMIO region. +fn encode_as_address_offset(location: &PciDeviceLocation) -> u32 { + ((location.bus as u32) << 20) + | ((location.device as u32) << 15) + | ((location.function as u32) << 12) +} + +/// The maximum offset in the 8-bit configuration space when using [`encode_as_port`]. +const PCI_PIO_MAX_OFFSET: u32 = 0xfc; + /// Encodes the bus, device, and function into a port address for use with the PCI I/O port. fn encode_as_port(location: &PciDeviceLocation) -> u32 { // 1 << 31: Configuration enable @@ -51,6 +98,19 @@ fn encode_as_port(location: &PciDeviceLocation) -> u32 { /// /// Returns a range for the PCI bus number, or [`None`] if there is no PCI bus. pub(crate) fn init() -> Option> { + if let Some(ecam) = ACPI_INFO.get().unwrap().pci_ecam_region.as_ref() { + let bus_start = ecam.bus_start; + let bus_end = ecam.bus_end; + + let addr_start = ecam.base_address as usize; + // Note that the base address always corresponds to the bus number 0, regardless of the + // actual value of `bus_start`. + let addr_end = addr_start + (bus_end as usize + 1) * (1 << 20); + PCI_ECAM_CFG_SPACE.call_once(|| IoMem::acquire(addr_start..addr_end).unwrap()); + + return Some(bus_start..=bus_end); + } + // We use `acquire_overlapping` to acquire the port at 0xCF8 because 0xCF9 may be used as a // reset control register in the PIIX4. Although the two ports overlap in their I/O range, they // serve completely different purposes. See diff --git a/ostd/src/arch/x86/kernel/acpi/mod.rs b/ostd/src/arch/x86/kernel/acpi/mod.rs index e880540bc..99aa44795 100644 --- a/ostd/src/arch/x86/kernel/acpi/mod.rs +++ b/ostd/src/arch/x86/kernel/acpi/mod.rs @@ -9,6 +9,7 @@ use acpi::{ AcpiHandler, AcpiTables, address::AddressSpace, fadt::{Fadt, IaPcBootArchFlags}, + mcfg::Mcfg, rsdp::Rsdp, }; use log::warn; @@ -85,6 +86,19 @@ pub struct AcpiInfo { pub boot_flags: Option, /// An I/O port to reset the machine by writing the specified value. pub reset_port_and_val: Option<(u16, u8)>, + /// A memory region that is stolen for PCI configuration space. + pub pci_ecam_region: Option, +} + +/// A memory region that is stolen for PCI configuration space. +#[derive(Debug)] +pub struct PciEcamRegion { + /// The base address of the memory region. + pub base_address: u64, + /// The start of the bus number. + pub bus_start: u8, + /// The end of the bus number. + pub bus_end: u8, } /// The [`AcpiInfo`] singleton. @@ -95,11 +109,15 @@ pub(in crate::arch) fn init() { century_register: None, boot_flags: None, reset_port_and_val: None, + pci_ecam_region: None, }; - if let Some(acpi_tables) = get_acpi_tables() - && let Ok(fadt) = acpi_tables.find_table::() - { + let Some(acpi_tables) = get_acpi_tables() else { + ACPI_INFO.call_once(|| acpi_info); + return; + }; + + if let Ok(fadt) = acpi_tables.find_table::() { // A zero means that the century register does not exist. acpi_info.century_register = NonZeroU8::new(fadt.century); acpi_info.boot_flags = Some(fadt.iapc_boot_arch); @@ -111,6 +129,18 @@ pub(in crate::arch) fn init() { } }; + if let Ok(mcfg) = acpi_tables.find_table::() + // TODO: Support multiple PCIe segment groups instead of assuming only one + // PCIe segment group is in use. + && let Some(mcfg_entry) = mcfg.entries().first() + { + acpi_info.pci_ecam_region = Some(PciEcamRegion { + base_address: mcfg_entry.base_address, + bus_start: mcfg_entry.bus_number_start, + bus_end: mcfg_entry.bus_number_end, + }); + } + log::info!("[ACPI]: Collected information {:?}", acpi_info); ACPI_INFO.call_once(|| acpi_info);