Skip to content

Commit 2fc1b45

Browse files
authored
Merge pull request #1819 from seijikun/mr-pci-enumerate
uefi: Implement PciRootBridgeIo bus device enumeration logic
2 parents 98b3588 + a82e24e commit 2fc1b45

File tree

6 files changed

+499
-38
lines changed

6 files changed

+499
-38
lines changed

uefi-test-runner/src/proto/pci/root_bridge.rs

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
use uefi::Handle;
44
use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, image_handle};
55
use uefi::proto::ProtocolPointer;
6-
use uefi::proto::pci::PciIoAddress;
76
use uefi::proto::pci::root_bridge::PciRootBridgeIo;
87

98
const RED_HAT_PCI_VENDOR_ID: u16 = 0x1AF4;
@@ -22,42 +21,40 @@ pub fn test() {
2221
for pci_handle in pci_handles {
2322
let mut pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);
2423

25-
for bus in 0..=255 {
26-
for dev in 0..32 {
27-
for fun in 0..8 {
28-
let addr = PciIoAddress::new(bus, dev, fun);
29-
let Ok(reg0) = pci_proto.pci().read_one::<u32>(addr.with_register(0)) else {
30-
continue;
31-
};
32-
if reg0 == 0xFFFFFFFF {
33-
continue; // not a valid device
34-
}
35-
let reg1 = pci_proto
36-
.pci()
37-
.read_one::<u32>(addr.with_register(2 * REG_SIZE))
38-
.unwrap();
39-
40-
let vendor_id = (reg0 & 0xFFFF) as u16;
41-
let device_id = (reg0 >> 16) as u16;
42-
if vendor_id == RED_HAT_PCI_VENDOR_ID {
43-
red_hat_dev_cnt += 1;
44-
}
24+
let devices = pci_proto.enumerate().unwrap();
25+
for fqaddr in devices {
26+
let addr = fqaddr.addr();
27+
let Ok(reg0) = pci_proto.pci().read_one::<u32>(addr.with_register(0)) else {
28+
continue;
29+
};
30+
if reg0 == 0xFFFFFFFF {
31+
continue; // not a valid device
32+
}
33+
let reg1 = pci_proto
34+
.pci()
35+
.read_one::<u32>(addr.with_register(2 * REG_SIZE))
36+
.unwrap();
4537

46-
let class_code = (reg1 >> 24) as u8;
47-
let subclass_code = ((reg1 >> 16) & 0xFF) as u8;
48-
if class_code == MASS_STORAGE_CTRL_CLASS_CODE {
49-
mass_storage_ctrl_cnt += 1;
38+
let vendor_id = (reg0 & 0xFFFF) as u16;
39+
let device_id = (reg0 >> 16) as u16;
40+
if vendor_id == RED_HAT_PCI_VENDOR_ID {
41+
red_hat_dev_cnt += 1;
42+
}
5043

51-
if subclass_code == SATA_CTRL_SUBCLASS_CODE {
52-
sata_ctrl_cnt += 1;
53-
}
54-
}
44+
let class_code = (reg1 >> 24) as u8;
45+
let subclass_code = ((reg1 >> 16) & 0xFF) as u8;
46+
if class_code == MASS_STORAGE_CTRL_CLASS_CODE {
47+
mass_storage_ctrl_cnt += 1;
5548

56-
log::info!(
57-
"PCI Device: [{bus}, {dev}, {fun}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}"
58-
);
49+
if subclass_code == SATA_CTRL_SUBCLASS_CODE {
50+
sata_ctrl_cnt += 1;
5951
}
6052
}
53+
54+
let (bus, dev, fun) = (addr.bus, addr.dev, addr.fun);
55+
log::info!(
56+
"PCI Device: [{bus}, {dev}, {fun}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}"
57+
);
6158
}
6259
}
6360

uefi/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
## Added
44
- Added `proto::ata::AtaRequestBuilder::read_pio()`.
55
- Added `proto::shell::Shell::{var(), set_var(), vars()}`
6+
- Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`.
7+
- Added `proto::pci::root_bridge::PciRootBridgeIo::enumerate()`.
68

79
## Changed
8-
10+
- Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg).
911

1012
# uefi - v0.36.1 (2025-11-05)
1113

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! Pci root bus resource configuration descriptor parsing.
4+
5+
/// Represents the type of resource described by a QWORD Address Space Descriptor.
6+
/// This corresponds to the `resource_type` field at offset 0x03 in the descriptor.
7+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8+
#[repr(u8)]
9+
pub enum ResourceRangeType {
10+
/// Memory Range (value = 0)
11+
/// Indicates that the descriptor describes a memory-mapped address range.
12+
/// Commonly used for MMIO regions decoded by the PCI root bridge.
13+
Memory = 0,
14+
15+
/// I/O Range (value = 1)
16+
/// Indicates that the descriptor describes a legacy I/O port range.
17+
/// Used for devices that communicate via port-mapped I/O.
18+
Io = 1,
19+
20+
/// Bus Number Range (value = 2)
21+
/// Indicates that the descriptor describes a range of PCI bus numbers.
22+
/// Used to define the bus hierarchy behind a PCI root bridge.
23+
Bus = 2,
24+
25+
/// Unknown or vendor-specific resource type.
26+
/// Captures any unrecognized value for forward compatibility.
27+
Unknown(u8),
28+
}
29+
impl From<u8> for ResourceRangeType {
30+
fn from(value: u8) -> Self {
31+
match value {
32+
0 => Self::Memory,
33+
1 => Self::Io,
34+
2 => Self::Bus,
35+
other => Self::Unknown(other),
36+
}
37+
}
38+
}
39+
40+
/// Represents a parsed QWORD Address Space Descriptor from UEFI.
41+
/// This structure describes a decoded resource range for a PCI root bridge.
42+
#[derive(Clone, Debug)]
43+
pub struct QwordAddressSpaceDescriptor {
44+
/// Type of resource: Memory, I/O, Bus, or Unknown.
45+
pub resource_range_type: ResourceRangeType,
46+
/// General flags that describe decode behavior (e.g., positive decode).
47+
pub general_flags: u8,
48+
/// Type-specific flags (e.g., cacheability for memory).
49+
pub type_specific_flags: u8,
50+
/// Granularity of the address space (typically 32 or 64).
51+
/// Indicates whether the range is 32-bit or 64-bit.
52+
pub granularity: u64,
53+
/// Minimum address of the range (inclusive).
54+
pub address_min: u64,
55+
/// Maximum address of the range (inclusive).
56+
pub address_max: u64,
57+
/// Translation offset to convert host address to PCI address.
58+
/// Usually zero unless the bridge remaps addresses.
59+
pub translation_offset: u64,
60+
/// Length of the address range (in bytes or bus numbers).
61+
pub address_length: u64,
62+
}
63+
64+
/// Parses a list of QWORD Address Space Descriptors from a raw memory region.
65+
/// Stops when it encounters an End Tag descriptor (type 0x79).
66+
#[cfg(feature = "alloc")]
67+
pub(crate) fn parse(
68+
base: *const core::ffi::c_void,
69+
) -> alloc::vec::Vec<QwordAddressSpaceDescriptor> {
70+
use alloc::slice;
71+
use alloc::vec::Vec;
72+
const PCI_RESTBL_QWORDADDRSPEC_TAG: u8 = 0x8a;
73+
const PCI_RESTBL_END_TAG: u8 = 0x79;
74+
75+
let base: *const u8 = base.cast();
76+
77+
// Phase 1: determine total length
78+
let mut offset = 0;
79+
loop {
80+
let tag = unsafe { core::ptr::read(base.add(offset)) };
81+
offset += match tag {
82+
PCI_RESTBL_QWORDADDRSPEC_TAG => 3 + 0x2B,
83+
PCI_RESTBL_END_TAG => break,
84+
_ => panic!("{tag}"), // Unknown tag - bailing
85+
};
86+
}
87+
88+
// Phase 2: parse descriptors from resource table
89+
let mut bfr: &[u8] = unsafe { slice::from_raw_parts(base, offset) };
90+
let mut descriptors = Vec::new();
91+
while !bfr.is_empty() {
92+
match bfr[0] {
93+
PCI_RESTBL_QWORDADDRSPEC_TAG => {
94+
let descriptor = QwordAddressSpaceDescriptor {
95+
resource_range_type: ResourceRangeType::from(bfr[0x03]),
96+
general_flags: bfr[0x04],
97+
type_specific_flags: bfr[0x05],
98+
granularity: u64::from_le_bytes(bfr[0x06..0x06 + 8].try_into().unwrap()),
99+
address_min: u64::from_le_bytes(bfr[0x0E..0x0E + 8].try_into().unwrap()),
100+
address_max: u64::from_le_bytes(bfr[0x16..0x16 + 8].try_into().unwrap()),
101+
translation_offset: u64::from_le_bytes(bfr[0x1E..0x1E + 8].try_into().unwrap()),
102+
address_length: u64::from_le_bytes(bfr[0x26..0x26 + 8].try_into().unwrap()),
103+
};
104+
descriptors.push(descriptor);
105+
106+
bfr = &bfr[3 + 0x2B..];
107+
}
108+
_ => break,
109+
}
110+
}
111+
112+
descriptors
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use crate::proto::pci::configuration::ResourceRangeType;
118+
119+
#[test]
120+
fn parse() {
121+
// example acpi pci qword configuration table export from a qemu vm
122+
const BFR: &[u8] = &[
123+
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,
124+
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,
125+
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,
126+
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,
127+
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,
128+
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,
129+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 121, 0,
130+
];
131+
let configuration = super::parse(BFR.as_ptr().cast());
132+
assert_eq!(configuration.len(), 4);
133+
let (mut cnt_mem, mut cnt_io, mut cnt_bus) = (0, 0, 0);
134+
for entry in &configuration {
135+
match entry.resource_range_type {
136+
ResourceRangeType::Memory => cnt_mem += 1,
137+
ResourceRangeType::Io => cnt_io += 1,
138+
ResourceRangeType::Bus => cnt_bus += 1,
139+
_ => unreachable!(),
140+
}
141+
}
142+
assert_eq!(cnt_mem, 2);
143+
assert_eq!(cnt_io, 1);
144+
assert_eq!(cnt_bus, 1);
145+
}
146+
}

uefi/src/proto/pci/enumeration.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! PCI Bus device function and bridge enumeration.
4+
5+
use core::mem;
6+
7+
use alloc::collections::btree_set::BTreeSet;
8+
9+
use super::root_bridge::PciRootBridgeIo;
10+
use super::{FullPciIoAddress, PciIoAddress};
11+
12+
#[allow(unused)]
13+
#[derive(Clone, Copy, Debug)]
14+
struct PciRegister0 {
15+
vendor_id: u16,
16+
device_id: u16,
17+
}
18+
19+
#[allow(unused)]
20+
#[derive(Clone, Copy, Debug)]
21+
struct PciRegister2 {
22+
revision_id: u8,
23+
prog_if: u8,
24+
subclass: u8,
25+
class: u8,
26+
}
27+
28+
#[allow(unused)]
29+
#[derive(Clone, Copy, Debug)]
30+
struct PciRegister3 {
31+
cache_line_size: u8,
32+
latency_timer: u8,
33+
header_type: u8,
34+
bist: u8,
35+
}
36+
37+
#[allow(unused)]
38+
#[derive(Clone, Copy, Debug)]
39+
struct PciHeader1Register6 {
40+
secondary_latency_timer: u8,
41+
subordinate_bus: u8,
42+
secondary_bus: u8,
43+
primary_bus: u8,
44+
}
45+
46+
/// Read the 4byte pci register with the given `addr` and cast it into the given structured representation.
47+
fn read_device_register_u32<T: Sized + Copy>(
48+
proto: &mut PciRootBridgeIo,
49+
addr: PciIoAddress,
50+
) -> uefi::Result<T> {
51+
unsafe {
52+
let raw = proto.pci().read_one::<u32>(addr)?;
53+
let reg: T = mem::transmute_copy(&raw);
54+
Ok(reg)
55+
}
56+
}
57+
58+
// ##########################################################################################
59+
// # Query Helpers (read from a device's configuration registers)
60+
61+
fn get_vendor_id(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<u16> {
62+
read_device_register_u32::<PciRegister0>(proto, addr.with_register(0)).map(|v| v.vendor_id)
63+
}
64+
65+
fn get_classes(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<(u8, u8)> {
66+
let reg = read_device_register_u32::<PciRegister2>(proto, addr.with_register(2 * 4))?;
67+
Ok((reg.class, reg.subclass))
68+
}
69+
70+
fn get_header_type(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<u8> {
71+
read_device_register_u32::<PciRegister3>(proto, addr.with_register(3 * 4))
72+
.map(|v| v.header_type)
73+
}
74+
75+
fn get_secondary_bus_range(
76+
proto: &mut PciRootBridgeIo,
77+
addr: PciIoAddress,
78+
) -> uefi::Result<(u8, u8)> {
79+
let reg = read_device_register_u32::<PciHeader1Register6>(proto, addr.with_register(6 * 4))?;
80+
Ok((reg.secondary_bus, reg.subordinate_bus))
81+
}
82+
83+
// ##########################################################################################
84+
// # Recursive visitor implementation
85+
86+
fn visit_function(
87+
proto: &mut PciRootBridgeIo,
88+
addr: PciIoAddress,
89+
queue: &mut BTreeSet<FullPciIoAddress>,
90+
) -> uefi::Result<()> {
91+
if get_vendor_id(proto, addr)? == 0xFFFF {
92+
return Ok(()); // function doesn't exist - bail instantly
93+
}
94+
queue.insert(FullPciIoAddress::new(proto.segment_nr(), addr));
95+
let (base_class, sub_class) = get_classes(proto, addr)?;
96+
if base_class == 0x6 && sub_class == 0x4 && get_header_type(proto, addr)? == 0x01 {
97+
// This is a PCI-to-PCI bridge controller. The current `addr` is the address with which it's
98+
// mounted in the PCI tree we are currently traversing. Now we query its header, where
99+
// the bridge tells us a range of addresses [secondary;subordinate], with which the other
100+
// side of the bridge is mounted into the PCI tree.
101+
let (secondary_bus_nr, subordinate_bus_nr) = get_secondary_bus_range(proto, addr)?;
102+
if secondary_bus_nr == 0 || subordinate_bus_nr < secondary_bus_nr {
103+
// If the secondary bus number is the root number, or if the range is invalid - this hardware
104+
// is so horribly broken that we refrain from touching it. It might explode - or worse!
105+
return Ok(());
106+
}
107+
for bus in secondary_bus_nr..=subordinate_bus_nr {
108+
// Recurse into the bus namespaces on the other side of the bridge
109+
visit_bus(proto, PciIoAddress::new(bus, 0, 0), queue)?;
110+
}
111+
}
112+
Ok(())
113+
}
114+
115+
fn visit_device(
116+
proto: &mut PciRootBridgeIo,
117+
addr: PciIoAddress,
118+
queue: &mut BTreeSet<FullPciIoAddress>,
119+
) -> uefi::Result<()> {
120+
if get_vendor_id(proto, addr)? == 0xFFFF {
121+
return Ok(()); // device doesn't exist
122+
}
123+
visit_function(proto, addr.with_function(0), queue)?;
124+
if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 {
125+
// This is a multi-function device - also try the remaining functions [1;7]
126+
// These remaining functions can be sparsely populated - as long as function 0 exists.
127+
for fun in 1..=7 {
128+
visit_function(proto, addr.with_function(fun), queue)?;
129+
}
130+
}
131+
132+
Ok(())
133+
}
134+
135+
pub(crate) fn visit_bus(
136+
proto: &mut PciRootBridgeIo,
137+
addr: PciIoAddress,
138+
queue: &mut BTreeSet<FullPciIoAddress>,
139+
) -> uefi::Result<()> {
140+
// Given a valid bus entry point - simply try all possible devices addresses
141+
for dev in 0..32 {
142+
visit_device(proto, addr.with_device(dev), queue)?;
143+
}
144+
Ok(())
145+
}

0 commit comments

Comments
 (0)