From e38013b1bed188914db8915059a17b03515882cf Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 20:41:34 +0100 Subject: [PATCH 1/5] pci: More convenience builder methods on PciIoAddress --- uefi/src/proto/pci/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index 8e3e9e09c..72d5882fe 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -37,6 +37,30 @@ impl PciIoAddress { } } + /// Construct a new address with the bus address set to the given value + #[must_use] + pub const fn with_bus(&self, bus: u8) -> Self { + let mut addr = *self; + addr.bus = bus; + addr + } + + /// Construct a new address with the device address set to the given value + #[must_use] + pub const fn with_device(&self, dev: u8) -> Self { + let mut addr = *self; + addr.dev = dev; + addr + } + + /// Construct a new address with the function address set to the given value + #[must_use] + pub const fn with_function(&self, fun: u8) -> Self { + let mut addr = *self; + addr.fun = fun; + addr + } + /// Configure the **byte**-offset of the register to access. #[must_use] pub const fn with_register(&self, reg: u8) -> Self { From 8c135d0bc1ab0cb1b212f99ae6e16c0b740a023c Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 20:42:04 +0100 Subject: [PATCH 2/5] uefi: Improve cmp impl of PciIoAddress to something logical you'd expect This sorting now actually makes sense from a logical point of view. This is now also the ordering you get by using typical tools like lspci. --- uefi/CHANGELOG.md | 2 +- uefi/src/proto/pci/mod.rs | 52 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 3406e8196..c5c70f262 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -5,7 +5,7 @@ - Added `proto::shell::Shell::{var(), set_var(), vars()}` ## Changed - +- Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg). # uefi - v0.36.1 (2025-11-05) diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index 72d5882fe..cf4239511 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -109,8 +109,15 @@ impl PartialOrd for PciIoAddress { } impl Ord for PciIoAddress { - fn cmp(&self, other: &Self) -> Ordering { - u64::from(*self).cmp(&u64::from(*other)) + fn cmp(&self, o: &Self) -> Ordering { + // extract fields because taking references to unaligned fields in packed structs is a nono + let (bus, dev, fun, reg, ext_reg) = (self.bus, self.dev, self.fun, self.reg, self.ext_reg); + let (o_bus, o_dev, o_fun, o_reg, o_ext_reg) = (o.bus, o.dev, o.fun, o.reg, o.ext_reg); + bus.cmp(&o_bus) + .then(dev.cmp(&o_dev)) + .then(fun.cmp(&o_fun)) + .then(reg.cmp(&o_reg)) + .then(ext_reg.cmp(&o_ext_reg)) } } @@ -152,6 +159,8 @@ fn encode_io_mode_and_unit(mode: PciIoMode) -> PciRootBridgeIoProt #[cfg(test)] mod tests { + use core::cmp::Ordering; + use super::PciIoAddress; #[test] @@ -170,4 +179,43 @@ mod tests { assert_eq!(rawaddr, 0x99_bb_dd_ff_7755_3311); assert_eq!(srcaddr, dstaddr); } + + #[test] + fn test_pci_order() { + let addr0_0_0 = PciIoAddress::new(0, 0, 0); + let addr0_0_1 = PciIoAddress::new(0, 0, 1); + let addr0_1_0 = PciIoAddress::new(0, 1, 0); + let addr1_0_0 = PciIoAddress::new(1, 0, 0); + + assert_eq!(addr0_0_0.cmp(&addr0_0_0), Ordering::Equal); + assert_eq!(addr0_0_0.cmp(addr0_0_1), Ordering::Less); + assert_eq!(addr0_0_0.cmp(&addr0_1_0), Ordering::Less); + assert_eq!(addr0_0_0.cmp(&addr1_0_0), Ordering::Less); + + assert_eq!(addr0_0_1.cmp(addr0_0_0), Ordering::Greater); + assert_eq!(addr0_0_1.cmp(addr0_0_1), Ordering::Equal); + assert_eq!(addr0_0_1.cmp(&addr0_1_0), Ordering::Less); + assert_eq!(addr0_0_1.cmp(&addr1_0_0), Ordering::Less); + + assert_eq!(addr0_1_0.cmp(addr0_0_0), Ordering::Greater); + assert_eq!(addr0_1_0.cmp(addr0_0_1), Ordering::Greater); + assert_eq!(addr0_1_0.cmp(&addr0_1_0), Ordering::Equal); + assert_eq!(addr0_1_0.cmp(&addr1_0_0), Ordering::Less); + + assert_eq!(addr1_0_0.cmp(addr0_0_0), Ordering::Greater); + assert_eq!(addr1_0_0.cmp(addr0_0_1), Ordering::Greater); + assert_eq!(addr1_0_0.cmp(&addr0_1_0), Ordering::Greater); + assert_eq!(addr1_0_0.cmp(&addr1_0_0), Ordering::Equal); + + assert_eq!(addr0_0_0.cmp(addr0_0_0.with_register(1)), Ordering::Less); + assert_eq!(addr0_0_0.with_register(1).cmp(addr0_0_0), Ordering::Greater); + assert_eq!( + addr0_0_0.cmp(addr0_0_0.with_extended_register(1)), + Ordering::Less + ); + assert_eq!( + addr0_0_0.with_extended_register(1).cmp(addr0_0_0), + Ordering::Greater + ); + } } From 60902c42b42dbf45aae12dedbd9c4894ef87e514 Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 20:57:44 +0100 Subject: [PATCH 3/5] uefi: Add PciRootBridgeIo::configuration() to query acpi table info --- uefi/CHANGELOG.md | 1 + uefi/src/proto/pci/configuration.rs | 149 ++++++++++++++++++++++++++++ uefi/src/proto/pci/mod.rs | 26 +++-- uefi/src/proto/pci/root_bridge.rs | 28 +++++- 4 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 uefi/src/proto/pci/configuration.rs diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index c5c70f262..800bf117c 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -3,6 +3,7 @@ ## Added - Added `proto::ata::AtaRequestBuilder::read_pio()`. - Added `proto::shell::Shell::{var(), set_var(), vars()}` +- Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`. ## Changed - Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg). diff --git a/uefi/src/proto/pci/configuration.rs b/uefi/src/proto/pci/configuration.rs new file mode 100644 index 000000000..a66a762db --- /dev/null +++ b/uefi/src/proto/pci/configuration.rs @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Pci root bus resource configuration descriptor parsing. + +use core::ptr; + +/// Represents the type of resource described by a QWORD Address Space Descriptor. +/// This corresponds to the `resource_type` field at offset 0x03 in the descriptor. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum ResourceRangeType { + /// Memory Range (value = 0) + /// Indicates that the descriptor describes a memory-mapped address range. + /// Commonly used for MMIO regions decoded by the PCI root bridge. + Memory = 0, + + /// I/O Range (value = 1) + /// Indicates that the descriptor describes a legacy I/O port range. + /// Used for devices that communicate via port-mapped I/O. + Io = 1, + + /// Bus Number Range (value = 2) + /// Indicates that the descriptor describes a range of PCI bus numbers. + /// Used to define the bus hierarchy behind a PCI root bridge. + Bus = 2, + + /// Unknown or vendor-specific resource type. + /// Captures any unrecognized value for forward compatibility. + Unknown(u8), +} +impl From for ResourceRangeType { + fn from(value: u8) -> Self { + match value { + 0 => Self::Memory, + 1 => Self::Io, + 2 => Self::Bus, + other => Self::Unknown(other), + } + } +} + +/// Represents a parsed QWORD Address Space Descriptor from UEFI. +/// This structure describes a decoded resource range for a PCI root bridge. +#[derive(Clone, Debug)] +pub struct QwordAddressSpaceDescriptor { + /// Type of resource: Memory, I/O, Bus, or Unknown. + pub resource_range_type: ResourceRangeType, + /// General flags that describe decode behavior (e.g., positive decode). + pub general_flags: u8, + /// Type-specific flags (e.g., cacheability for memory). + pub type_specific_flags: u8, + /// Granularity of the address space (typically 32 or 64). + /// Indicates whether the range is 32-bit or 64-bit. + pub granularity: u64, + /// Minimum address of the range (inclusive). + pub address_min: u64, + /// Maximum address of the range (inclusive). + pub address_max: u64, + /// Translation offset to convert host address to PCI address. + /// Usually zero unless the bridge remaps addresses. + pub translation_offset: u64, + /// Length of the address range (in bytes or bus numbers). + pub address_length: u64, +} + +const PCI_RESTBL_QWORDADDRSPEC_TAG: u8 = 0x8a; +const PCI_RESTBL_END_TAG: u8 = 0x79; + +/// Parses a list of QWORD Address Space Descriptors from a raw memory region. +/// Stops when it encounters an End Tag descriptor (type 0x79). +#[cfg(feature = "alloc")] +pub(crate) fn parse( + base: *const core::ffi::c_void, +) -> alloc::vec::Vec { + use alloc::slice; + use alloc::vec::Vec; + + let base: *const u8 = base.cast(); + + // Phase 1: determine total length + let mut offset = 0; + loop { + let tag = unsafe { ptr::read(base.add(offset)) }; + offset += match tag { + PCI_RESTBL_QWORDADDRSPEC_TAG => 3 + 0x2B, + PCI_RESTBL_END_TAG => break, + _ => return Vec::new(), // Unknown tag - bailing + }; + } + + // Phase 2: parse descriptors from resource table + let mut bfr: &[u8] = unsafe { slice::from_raw_parts(base, offset) }; + let mut descriptors = Vec::new(); + while !bfr.is_empty() { + match bfr[0] { + PCI_RESTBL_QWORDADDRSPEC_TAG => { + let descriptor = QwordAddressSpaceDescriptor { + resource_range_type: ResourceRangeType::from(bfr[0x03]), + general_flags: bfr[0x04], + type_specific_flags: bfr[0x05], + granularity: u64::from_le_bytes(bfr[0x06..0x06 + 8].try_into().unwrap()), + address_min: u64::from_le_bytes(bfr[0x0E..0x0E + 8].try_into().unwrap()), + address_max: u64::from_le_bytes(bfr[0x16..0x16 + 8].try_into().unwrap()), + translation_offset: u64::from_le_bytes(bfr[0x1E..0x1E + 8].try_into().unwrap()), + address_length: u64::from_le_bytes(bfr[0x26..0x26 + 8].try_into().unwrap()), + }; + descriptors.push(descriptor); + + bfr = &bfr[3 + 0x2B..]; + } + _ => break, + } + } + + descriptors +} + +#[cfg(test)] +mod tests { + use crate::proto::pci::configuration::ResourceRangeType; + + #[test] + fn parse() { + // example acpi pci qword configuration table export from a qemu vm + const BFR: [u8] = [ + 138, 43, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 255, 111, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 138, 43, 0, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 255, 255, 15, 129, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 138, 43, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 192, 0, 0, 0, 255, 255, 15, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 0, 0, 0, 138, 43, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + ]; + let configuration = super::parse(BFR.as_ptr()); + assert_eq!(configuration.len(), 4); + let (mut cnt_mem, mut cnt_io, mut cnt_bus) = (0, 0, 0); + for entry in &configuration { + match entry.resource_range_type { + ResourceRangeType::Memory => cnt_mem += 1, + ResourceRangeType::Io => cnt_io += 1, + ResourceRangeType::Bus => cnt_bus += 1, + _ => unreachable!(), + } + } + assert_eq!(cnt_mem, 2); + assert_eq!(cnt_io, 1); + assert_eq!(cnt_bus, 1); + } +} diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index cf4239511..685908d3a 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -6,6 +6,7 @@ use core::cmp::Ordering; use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; +pub mod configuration; pub mod root_bridge; /// IO Address for PCI/register IO operations @@ -188,33 +189,36 @@ mod tests { let addr1_0_0 = PciIoAddress::new(1, 0, 0); assert_eq!(addr0_0_0.cmp(&addr0_0_0), Ordering::Equal); - assert_eq!(addr0_0_0.cmp(addr0_0_1), Ordering::Less); + assert_eq!(addr0_0_0.cmp(&addr0_0_1), Ordering::Less); assert_eq!(addr0_0_0.cmp(&addr0_1_0), Ordering::Less); assert_eq!(addr0_0_0.cmp(&addr1_0_0), Ordering::Less); - assert_eq!(addr0_0_1.cmp(addr0_0_0), Ordering::Greater); - assert_eq!(addr0_0_1.cmp(addr0_0_1), Ordering::Equal); + assert_eq!(addr0_0_1.cmp(&addr0_0_0), Ordering::Greater); + assert_eq!(addr0_0_1.cmp(&addr0_0_1), Ordering::Equal); assert_eq!(addr0_0_1.cmp(&addr0_1_0), Ordering::Less); assert_eq!(addr0_0_1.cmp(&addr1_0_0), Ordering::Less); - assert_eq!(addr0_1_0.cmp(addr0_0_0), Ordering::Greater); - assert_eq!(addr0_1_0.cmp(addr0_0_1), Ordering::Greater); + assert_eq!(addr0_1_0.cmp(&addr0_0_0), Ordering::Greater); + assert_eq!(addr0_1_0.cmp(&addr0_0_1), Ordering::Greater); assert_eq!(addr0_1_0.cmp(&addr0_1_0), Ordering::Equal); assert_eq!(addr0_1_0.cmp(&addr1_0_0), Ordering::Less); - assert_eq!(addr1_0_0.cmp(addr0_0_0), Ordering::Greater); - assert_eq!(addr1_0_0.cmp(addr0_0_1), Ordering::Greater); + assert_eq!(addr1_0_0.cmp(&addr0_0_0), Ordering::Greater); + assert_eq!(addr1_0_0.cmp(&addr0_0_1), Ordering::Greater); assert_eq!(addr1_0_0.cmp(&addr0_1_0), Ordering::Greater); assert_eq!(addr1_0_0.cmp(&addr1_0_0), Ordering::Equal); - assert_eq!(addr0_0_0.cmp(addr0_0_0.with_register(1)), Ordering::Less); - assert_eq!(addr0_0_0.with_register(1).cmp(addr0_0_0), Ordering::Greater); + assert_eq!(addr0_0_0.cmp(&addr0_0_0.with_register(1)), Ordering::Less); assert_eq!( - addr0_0_0.cmp(addr0_0_0.with_extended_register(1)), + addr0_0_0.with_register(1).cmp(&addr0_0_0), + Ordering::Greater + ); + assert_eq!( + addr0_0_0.cmp(&addr0_0_0.with_extended_register(1)), Ordering::Less ); assert_eq!( - addr0_0_0.with_extended_register(1).cmp(addr0_0_0), + addr0_0_0.with_extended_register(1).cmp(&addr0_0_0), Ordering::Greater ); } diff --git a/uefi/src/proto/pci/root_bridge.rs b/uefi/src/proto/pci/root_bridge.rs index 57135279a..2bd0318ec 100644 --- a/uefi/src/proto/pci/root_bridge.rs +++ b/uefi/src/proto/pci/root_bridge.rs @@ -2,10 +2,15 @@ //! PCI Root Bridge protocol. -use core::ptr; - use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit}; use crate::StatusExt; +#[cfg(feature = "alloc")] +use crate::proto::pci::configuration::{self, QwordAddressSpaceDescriptor}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(feature = "alloc")] +use core::ffi::c_void; +use core::ptr; use uefi_macros::unsafe_protocol; use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol}; @@ -52,7 +57,24 @@ impl PciRootBridgeIo { // TODO: map & unmap & copy memory // TODO: buffer management // TODO: get/set attributes - // TODO: configuration / resource settings + + /// Retrieves the current resource settings of this PCI root bridge in the form of a set of ACPI resource descriptors. + /// + /// The returned list of descriptors contains information about bus, memory and io ranges that were set up + /// by the firmware. + /// + /// # Errors + /// - [`Status::UNSUPPORTED`] The current configuration of this PCI root bridge could not be retrieved. + #[cfg(feature = "alloc")] + pub fn configuration(&self) -> crate::Result> { + // The storage for the resource descriptors is allocated by this function. The caller must treat + // the return buffer as read-only data, and the buffer must not be freed by the caller. + let mut resources: *const c_void = ptr::null(); + unsafe { + ((self.0.configuration)(&self.0, &mut resources)) + .to_result_with_val(|| configuration::parse(resources)) + } + } } /// Struct for performing PCI I/O operations on a root bridge. From 60994c87f5ba217df3a391c7469220ae6e2c0799 Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 21:36:18 +0100 Subject: [PATCH 4/5] uefi: Add PciRootBridgeIo::enumeration() to recursively enumerate bus devices --- uefi/CHANGELOG.md | 1 + uefi/src/proto/pci/enumeration.rs | 145 ++++++++++++++++++++++++++++++ uefi/src/proto/pci/mod.rs | 34 +++++++ uefi/src/proto/pci/root_bridge.rs | 41 ++++++++- 4 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 uefi/src/proto/pci/enumeration.rs diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 800bf117c..e2ec9d485 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -4,6 +4,7 @@ - Added `proto::ata::AtaRequestBuilder::read_pio()`. - Added `proto::shell::Shell::{var(), set_var(), vars()}` - Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`. +- Added `proto::pci::root_bridge::PciRootBridgeIo::enumerate()`. ## Changed - Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg). diff --git a/uefi/src/proto/pci/enumeration.rs b/uefi/src/proto/pci/enumeration.rs new file mode 100644 index 000000000..49395cabb --- /dev/null +++ b/uefi/src/proto/pci/enumeration.rs @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! PCI Bus device function and bridge enumeration. + +use core::mem; + +use alloc::collections::btree_set::BTreeSet; + +use super::root_bridge::PciRootBridgeIo; +use super::{FullPciIoAddress, PciIoAddress}; + +#[allow(unused)] +#[derive(Clone, Copy, Debug)] +struct PciRegister0 { + vendor_id: u16, + device_id: u16, +} + +#[allow(unused)] +#[derive(Clone, Copy, Debug)] +struct PciRegister2 { + revision_id: u8, + prog_if: u8, + subclass: u8, + class: u8, +} + +#[allow(unused)] +#[derive(Clone, Copy, Debug)] +struct PciRegister3 { + cache_line_size: u8, + latency_timer: u8, + header_type: u8, + bist: u8, +} + +#[allow(unused)] +#[derive(Clone, Copy, Debug)] +struct PciHeader1Register6 { + secondary_latency_timer: u8, + subordinate_bus: u8, + secondary_bus: u8, + primary_bus: u8, +} + +/// Read the 4byte pci register with the given `addr` and cast it into the given structured representation. +fn read_device_register_u32( + proto: &mut PciRootBridgeIo, + addr: PciIoAddress, +) -> uefi::Result { + unsafe { + let raw = proto.pci().read_one::(addr)?; + let reg: T = mem::transmute_copy(&raw); + Ok(reg) + } +} + +// ########################################################################################## +// # Query Helpers (read from a device's configuration registers) + +fn get_vendor_id(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result { + read_device_register_u32::(proto, addr.with_register(0)).map(|v| v.vendor_id) +} + +fn get_classes(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<(u8, u8)> { + let reg = read_device_register_u32::(proto, addr.with_register(2 * 4))?; + Ok((reg.class, reg.subclass)) +} + +fn get_header_type(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result { + read_device_register_u32::(proto, addr.with_register(3 * 4)) + .map(|v| v.header_type) +} + +fn get_secondary_bus_range( + proto: &mut PciRootBridgeIo, + addr: PciIoAddress, +) -> uefi::Result<(u8, u8)> { + let reg = read_device_register_u32::(proto, addr.with_register(6 * 4))?; + Ok((reg.secondary_bus, reg.subordinate_bus)) +} + +// ########################################################################################## +// # Recursive visitor implementation + +fn visit_function( + proto: &mut PciRootBridgeIo, + addr: PciIoAddress, + queue: &mut BTreeSet, +) -> uefi::Result<()> { + if get_vendor_id(proto, addr)? == 0xFFFF { + return Ok(()); // function doesn't exist - bail instantly + } + queue.insert(FullPciIoAddress::new(proto.segment_nr(), addr)); + let (base_class, sub_class) = get_classes(proto, addr)?; + if base_class == 0x6 && sub_class == 0x4 && get_header_type(proto, addr)? == 0x01 { + // This is a PCI-to-PCI bridge controller. The current `addr` is the address with which it's + // mounted in the PCI tree we are currently traversing. Now we query its header, where + // the bridge tells us a range of addresses [secondary;subordinate], with which the other + // side of the bridge is mounted into the PCI tree. + let (secondary_bus_nr, subordinate_bus_nr) = get_secondary_bus_range(proto, addr)?; + if secondary_bus_nr == 0 || subordinate_bus_nr < secondary_bus_nr { + // If the secondary bus number is the root number, or if the range is invalid - this hardware + // is so horribly broken that we refrain from touching it. It might explode - or worse! + return Ok(()); + } + for bus in secondary_bus_nr..=subordinate_bus_nr { + // Recurse into the bus namespaces on the other side of the bridge + visit_bus(proto, PciIoAddress::new(bus, 0, 0), queue)?; + } + } + Ok(()) +} + +fn visit_device( + proto: &mut PciRootBridgeIo, + addr: PciIoAddress, + queue: &mut BTreeSet, +) -> uefi::Result<()> { + if get_vendor_id(proto, addr)? == 0xFFFF { + return Ok(()); // device doesn't exist + } + visit_function(proto, addr.with_function(0), queue)?; + if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 { + // This is a multi-function device - also try the remaining functions [1;7] + // These remaining functions can be sparsely populated - as long as function 0 exists. + for fun in 1..=7 { + visit_function(proto, addr.with_function(fun), queue)?; + } + } + + Ok(()) +} + +pub(crate) fn visit_bus( + proto: &mut PciRootBridgeIo, + addr: PciIoAddress, + queue: &mut BTreeSet, +) -> uefi::Result<()> { + // Given a valid bus entry point - simply try all possible devices addresses + for dev in 0..32 { + visit_device(proto, addr.with_device(dev), queue)?; + } + Ok(()) +} diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index 685908d3a..fb667f98d 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -7,6 +7,8 @@ use core::cmp::Ordering; use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; pub mod configuration; +#[cfg(feature = "alloc")] +mod enumeration; pub mod root_bridge; /// IO Address for PCI/register IO operations @@ -122,6 +124,38 @@ impl Ord for PciIoAddress { } } +// -------------------------------------------------------------------------------------------- + +/// Fully qualified pci address. This address is valid across root bridges. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct FullPciIoAddress { + /// PCI segment number + segment: u32, + /// Subsequent PCI address + addr: PciIoAddress, +} +impl FullPciIoAddress { + /// Construct a new fully qualified pci address. + #[must_use] + pub const fn new(segment: u32, addr: PciIoAddress) -> Self { + Self { segment, addr } + } + + /// Get the segment number this address belongs to. + #[must_use] + pub const fn segment(&self) -> u32 { + self.segment + } + + /// Get the internal RootBridge-specific portion of the address. + #[must_use] + pub const fn addr(&self) -> PciIoAddress { + self.addr + } +} + +// ############################################################################################ + /// Trait implemented by all data types that can natively be read from a PCI device. /// Note: Not all of them have to actually be supported by the hardware at hand. pub trait PciIoUnit: Sized + Default {} diff --git a/uefi/src/proto/pci/root_bridge.rs b/uefi/src/proto/pci/root_bridge.rs index 2bd0318ec..39e9e926e 100644 --- a/uefi/src/proto/pci/root_bridge.rs +++ b/uefi/src/proto/pci/root_bridge.rs @@ -5,7 +5,9 @@ use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit}; use crate::StatusExt; #[cfg(feature = "alloc")] -use crate::proto::pci::configuration::{self, QwordAddressSpaceDescriptor}; +use crate::proto::pci::configuration::QwordAddressSpaceDescriptor; +#[cfg(feature = "alloc")] +use alloc::collections::btree_set::BTreeSet; #[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "alloc")] @@ -14,6 +16,8 @@ use core::ptr; use uefi_macros::unsafe_protocol; use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol}; +#[cfg(doc)] +use super::FullPciIoAddress; #[cfg(doc)] use crate::Status; @@ -67,6 +71,7 @@ impl PciRootBridgeIo { /// - [`Status::UNSUPPORTED`] The current configuration of this PCI root bridge could not be retrieved. #[cfg(feature = "alloc")] pub fn configuration(&self) -> crate::Result> { + use crate::proto::pci::configuration; // The storage for the resource descriptors is allocated by this function. The caller must treat // the return buffer as read-only data, and the buffer must not be freed by the caller. let mut resources: *const c_void = ptr::null(); @@ -75,6 +80,40 @@ impl PciRootBridgeIo { .to_result_with_val(|| configuration::parse(resources)) } } + + // ################################################### + // # Convenience functionality + + /// Recursively enumerate all devices, device functions and pci-to-pci bridges on this root bridge. + /// + /// The returned addresses might overlap with the addresses returned by another [`PciRootBridgeIo`] instance. + /// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the returned [`FullPciIoAddress`]es. + /// **WARNING:** Only use the returned addresses with the respective [`PciRootBridgeIo`] instance that returned them. + /// + /// # Returns + /// An ordered list of addresses containing all present devices below this RootBridge. + /// + /// # Errors + /// This can basically fail with all the IO errors found in [`PciIoAccessPci`] methods. + #[cfg(feature = "alloc")] + pub fn enumerate(&mut self) -> crate::Result> { + use crate::proto::pci::configuration::ResourceRangeType; + use crate::proto::pci::enumeration; + + let mut devices = BTreeSet::new(); + for descriptor in self.configuration()? { + // In the descriptors we can query for the current root bridge, Bus entries contain ranges of valid + // bus addresses. These are starting points for the recursive scanning process performed in + // enumeration::enum_bus + if descriptor.resource_range_type == ResourceRangeType::Bus { + for bus in (descriptor.address_min as u8)..=(descriptor.address_max as u8) { + let addr = PciIoAddress::new(bus, 0, 0); + enumeration::visit_bus(self, addr, &mut devices)?; + } + } + } + Ok(devices) + } } /// Struct for performing PCI I/O operations on a root bridge. From 81798255b5f88b904f24ddf17c06551cccc26bfc Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Sun, 9 Nov 2025 20:05:49 +0100 Subject: [PATCH 5/5] uefi: Use new pci device enumeration in integration test --- uefi-test-runner/src/proto/pci/root_bridge.rs | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/uefi-test-runner/src/proto/pci/root_bridge.rs b/uefi-test-runner/src/proto/pci/root_bridge.rs index 6e09d3d89..06eeb9337 100644 --- a/uefi-test-runner/src/proto/pci/root_bridge.rs +++ b/uefi-test-runner/src/proto/pci/root_bridge.rs @@ -3,7 +3,6 @@ use uefi::Handle; use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, image_handle}; use uefi::proto::ProtocolPointer; -use uefi::proto::pci::PciIoAddress; use uefi::proto::pci::root_bridge::PciRootBridgeIo; const RED_HAT_PCI_VENDOR_ID: u16 = 0x1AF4; @@ -22,42 +21,40 @@ pub fn test() { for pci_handle in pci_handles { let mut pci_proto = get_open_protocol::(pci_handle); - for bus in 0..=255 { - for dev in 0..32 { - for fun in 0..8 { - let addr = PciIoAddress::new(bus, dev, fun); - let Ok(reg0) = pci_proto.pci().read_one::(addr.with_register(0)) else { - continue; - }; - if reg0 == 0xFFFFFFFF { - continue; // not a valid device - } - let reg1 = pci_proto - .pci() - .read_one::(addr.with_register(2 * REG_SIZE)) - .unwrap(); - - let vendor_id = (reg0 & 0xFFFF) as u16; - let device_id = (reg0 >> 16) as u16; - if vendor_id == RED_HAT_PCI_VENDOR_ID { - red_hat_dev_cnt += 1; - } + let devices = pci_proto.enumerate().unwrap(); + for fqaddr in devices { + let addr = fqaddr.addr(); + let Ok(reg0) = pci_proto.pci().read_one::(addr.with_register(0)) else { + continue; + }; + if reg0 == 0xFFFFFFFF { + continue; // not a valid device + } + let reg1 = pci_proto + .pci() + .read_one::(addr.with_register(2 * REG_SIZE)) + .unwrap(); - let class_code = (reg1 >> 24) as u8; - let subclass_code = ((reg1 >> 16) & 0xFF) as u8; - if class_code == MASS_STORAGE_CTRL_CLASS_CODE { - mass_storage_ctrl_cnt += 1; + let vendor_id = (reg0 & 0xFFFF) as u16; + let device_id = (reg0 >> 16) as u16; + if vendor_id == RED_HAT_PCI_VENDOR_ID { + red_hat_dev_cnt += 1; + } - if subclass_code == SATA_CTRL_SUBCLASS_CODE { - sata_ctrl_cnt += 1; - } - } + let class_code = (reg1 >> 24) as u8; + let subclass_code = ((reg1 >> 16) & 0xFF) as u8; + if class_code == MASS_STORAGE_CTRL_CLASS_CODE { + mass_storage_ctrl_cnt += 1; - log::info!( - "PCI Device: [{bus}, {dev}, {fun}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}" - ); + if subclass_code == SATA_CTRL_SUBCLASS_CODE { + sata_ctrl_cnt += 1; } } + + let (bus, dev, fun) = (addr.bus, addr.dev, addr.fun); + log::info!( + "PCI Device: [{bus}, {dev}, {fun}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}" + ); } }