From 6c96da08ba8a9f2a934b9c0c15888dc52e2e8242 Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 20:41:34 +0100 Subject: [PATCH 1/4] 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 4583c2ff1604de43f0be4ae205baf0c21172e48c Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 20:42:04 +0100 Subject: [PATCH 2/4] 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 41ce27338..aa02b699e 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -4,7 +4,7 @@ - Added `proto::ata::AtaRequestBuilder::read_pio()`. ## 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 f9b6b11af0bd0fd0dedebc45d75ad1e3b3f1832c Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 20:57:44 +0100 Subject: [PATCH 3/4] uefi: Add PciRootBridgeIo::configuration() to query acpi table info --- uefi/CHANGELOG.md | 1 + uefi/src/proto/pci/configuration.rs | 114 ++++++++++++++++++++++++++++ uefi/src/proto/pci/mod.rs | 27 ++++--- uefi/src/proto/pci/root_bridge.rs | 26 ++++++- 4 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 uefi/src/proto/pci/configuration.rs diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index aa02b699e..545cb8309 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -2,6 +2,7 @@ ## Added - Added `proto::ata::AtaRequestBuilder::read_pio()`. +- 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..2b6e79887 --- /dev/null +++ b/uefi/src/proto/pci/configuration.rs @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Pci root bus resource configuration descriptor parsing. + +use core::ffi::c_void; +use core::ptr; + +use alloc::slice; +use alloc::vec::Vec; + +/// 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). +pub(crate) fn parse(base: *const c_void) -> 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 +} diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index cf4239511..537582978 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -6,6 +6,8 @@ use core::cmp::Ordering; use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; +#[cfg(feature = "alloc")] +pub mod configuration; pub mod root_bridge; /// IO Address for PCI/register IO operations @@ -188,33 +190,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..57a21abea 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,22 @@ 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> { + 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 3920b722cb93267b88ad0bd0b397d1f507b271dc Mon Sep 17 00:00:00 2001 From: Markus Ebner Date: Wed, 5 Nov 2025 21:36:18 +0100 Subject: [PATCH 4/4] 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 | 42 ++++++++- 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 uefi/src/proto/pci/enumeration.rs diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 545cb8309..02c5b5b73 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -3,6 +3,7 @@ ## Added - Added `proto::ata::AtaRequestBuilder::read_pio()`. - 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 537582978..6cce98488 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -8,6 +8,8 @@ use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; #[cfg(feature = "alloc")] pub mod configuration; +#[cfg(feature = "alloc")] +mod enumeration; pub mod root_bridge; /// IO Address for PCI/register IO operations @@ -123,6 +125,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 57a21abea..8d82d9abc 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,12 +71,48 @@ 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; + let mut resources: *const c_void = ptr::null(); unsafe { ((self.0.configuration)(&self.0, &mut resources)) .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.