Use PCI ECAM in x86 if possible

This commit is contained in:
Ruihan Li 2026-01-18 15:52:23 +08:00 committed by Tate, Hongliang Tian
parent a09de99c1d
commit a82d185154
4 changed files with 127 additions and 19 deletions

View File

@ -17,19 +17,28 @@ use crate::PciDeviceLocation;
static PCI_ECAM_CFG_SPACE: Once<IoMem> = 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<u32, Error> {
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

View File

@ -13,19 +13,28 @@ use crate::PciDeviceLocation;
static PCI_ECAM_CFG_SPACE: Once<IoMem> = 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<u32, Error> {
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

View File

@ -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<SpinLock<AddressAndDataPort>> = Once::new();
const BIT32_ALIGN_MASK: u32 = 0xFFFC;
static PCI_ECAM_CFG_SPACE: Once<IoMem> = 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<u32, 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));
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<RangeInclusive<u8>> {
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

View File

@ -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<IaPcBootArchFlags>,
/// 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<PciEcamRegion>,
}
/// 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::<Fadt>()
{
let Some(acpi_tables) = get_acpi_tables() else {
ACPI_INFO.call_once(|| acpi_info);
return;
};
if let Ok(fadt) = acpi_tables.find_table::<Fadt>() {
// 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::<Mcfg>()
// 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);