Skip to content

Commit a82b06c

Browse files
committed
uefi: Refactor PciRootBridgeIo::enumerate() with tree-topology information
- Refactored return type from standard BTreeSet to custom PciTree struct - Removed special FullPciIoAddress type, since segment number is PciRoot dependent - During enumeration, skip branches we have already seen - During enumeration, collect tree topology information (which child bus linked from where) - Add complicated pci structure in integration test vm - Print child busses for every device entry in integration test
1 parent 7a7d4ab commit a82b06c

File tree

6 files changed

+157
-68
lines changed

6 files changed

+157
-68
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ pub fn test() {
2121
for pci_handle in pci_handles {
2222
let mut pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);
2323

24-
let devices = pci_proto.enumerate().unwrap();
25-
for fqaddr in devices {
26-
let addr = fqaddr.addr();
24+
let pci_tree = pci_proto.enumerate().unwrap();
25+
for addr in pci_tree.iter().cloned() {
2726
let Ok(reg0) = pci_proto.pci().read_one::<u32>(addr.with_register(0)) else {
2827
continue;
2928
};
@@ -53,8 +52,11 @@ pub fn test() {
5352

5453
let (bus, dev, fun) = (addr.bus, addr.dev, addr.fun);
5554
log::info!(
56-
"PCI Device: [{bus}, {dev}, {fun}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}"
55+
"PCI Device: [{bus:02x}, {dev:02x}, {fun:02x}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}"
5756
);
57+
for child_bus in pci_tree.iter_subsequent_busses_for(addr) {
58+
log::info!(" |- Bus: {child_bus:02x}");
59+
}
5860
}
5961
}
6062

uefi-test-runner/src/proto/scsi/pass_thru.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ fn test_allocating_api() {
1616
// by default respectively. We manually configure an additional SCSI controller.
1717
// Thus, we should see two controllers with support for EXT_SCSI_PASS_THRU on this platform
1818
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
19-
assert_eq!(scsi_ctrl_handles.len(), 2);
19+
assert_eq!(scsi_ctrl_handles.len(), 4);
2020
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
21-
assert_eq!(scsi_ctrl_handles.len(), 1);
21+
assert_eq!(scsi_ctrl_handles.len(), 3);
2222

2323
let mut found_drive = false;
2424
for handle in scsi_ctrl_handles {

uefi/src/proto/pci/enumeration.rs

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
55
use core::mem;
66

7-
use alloc::collections::btree_set::BTreeSet;
7+
use alloc::collections::btree_map::BTreeMap;
8+
use alloc::collections::btree_set::{self, BTreeSet};
89

10+
use super::PciIoAddress;
911
use super::root_bridge::PciRootBridgeIo;
10-
use super::{FullPciIoAddress, PciIoAddress};
1112

1213
#[allow(unused)]
1314
#[derive(Clone, Copy, Debug)]
@@ -55,6 +56,89 @@ fn read_device_register_u32<T: Sized + Copy>(
5556
}
5657
}
5758

59+
// ##########################################################################################
60+
61+
/// Struct representing the tree structure of PCI devices.
62+
///
63+
/// This allows iterating over all valid PCI device addresses in a tree, as well as querying
64+
/// the tree topology.
65+
#[derive(Debug)]
66+
pub struct PciTree {
67+
segment: u32,
68+
devices: BTreeSet<PciIoAddress>,
69+
bus_anchors: BTreeMap<u8 /* bus */, PciIoAddress>,
70+
}
71+
impl PciTree {
72+
pub(crate) const fn new(segment: u32) -> Self {
73+
Self {
74+
segment,
75+
devices: BTreeSet::new(),
76+
bus_anchors: BTreeMap::new(),
77+
}
78+
}
79+
80+
pub(crate) fn push_bus(&self, bus: u8) -> bool {
81+
!self.bus_anchors.contains_key(&bus)
82+
}
83+
84+
pub(crate) fn push_device(&mut self, addr: PciIoAddress) {
85+
self.devices.insert(addr);
86+
}
87+
88+
pub(crate) fn push_bridge(&mut self, addr: PciIoAddress, child_bus: u8) -> bool {
89+
match self.bus_anchors.contains_key(&child_bus) {
90+
true => false,
91+
false => {
92+
self.bus_anchors.insert(child_bus, addr);
93+
true
94+
}
95+
}
96+
}
97+
98+
/// Iterate over all valid PCI device addresses in this tree structure.
99+
pub fn iter(&self) -> btree_set::Iter<'_, PciIoAddress> {
100+
self.devices.iter()
101+
}
102+
103+
/// Get the segment number of this PCI tree.
104+
#[must_use]
105+
pub const fn segment_nr(&self) -> u32 {
106+
self.segment
107+
}
108+
109+
/// Query the address of the parent PCI bridge this `addr`'s bus is subordinate to.
110+
#[must_use]
111+
pub fn parent_for(&self, addr: PciIoAddress) -> Option<PciIoAddress> {
112+
self.bus_anchors.get(&addr.bus).cloned()
113+
}
114+
115+
/// Iterate over all subsequent busses below the given `addr`.
116+
/// This yields 0 results if `addr` doesn't point to a PCI bridge.
117+
pub fn iter_subsequent_busses_for(&self, addr: PciIoAddress) -> impl Iterator<Item = u8> {
118+
self.bus_anchors
119+
.iter()
120+
.filter(move |&(_, parent)| *parent == addr)
121+
.map(|(bus, _)| bus)
122+
.cloned()
123+
}
124+
}
125+
impl IntoIterator for PciTree {
126+
type Item = PciIoAddress;
127+
type IntoIter = btree_set::IntoIter<PciIoAddress>;
128+
129+
fn into_iter(self) -> Self::IntoIter {
130+
self.devices.into_iter()
131+
}
132+
}
133+
impl<'a> IntoIterator for &'a PciTree {
134+
type Item = &'a PciIoAddress;
135+
type IntoIter = btree_set::Iter<'a, PciIoAddress>;
136+
137+
fn into_iter(self) -> Self::IntoIter {
138+
self.devices.iter()
139+
}
140+
}
141+
58142
// ##########################################################################################
59143
// # Query Helpers (read from a device's configuration registers)
60144

@@ -86,12 +170,12 @@ fn get_secondary_bus_range(
86170
fn visit_function(
87171
proto: &mut PciRootBridgeIo,
88172
addr: PciIoAddress,
89-
queue: &mut BTreeSet<FullPciIoAddress>,
173+
tree: &mut PciTree,
90174
) -> uefi::Result<()> {
91175
if get_vendor_id(proto, addr)? == 0xFFFF {
92176
return Ok(()); // function doesn't exist - bail instantly
93177
}
94-
queue.insert(FullPciIoAddress::new(proto.segment_nr(), addr));
178+
tree.push_device(addr);
95179
let (base_class, sub_class) = get_classes(proto, addr)?;
96180
let header_type = get_header_type(proto, addr)? & 0b01111111;
97181
if base_class == 0x6 && sub_class == 0x4 && header_type == 0x01 {
@@ -106,8 +190,11 @@ fn visit_function(
106190
return Ok(());
107191
}
108192
for bus in secondary_bus_nr..=subordinate_bus_nr {
109-
// Recurse into the bus namespaces on the other side of the bridge
110-
visit_bus(proto, PciIoAddress::new(bus, 0, 0), queue)?;
193+
// Recurse into the bus namespaces on the other side of the bridge, if we haven't visited
194+
// the subordinate bus through a more direct path already
195+
if tree.push_bridge(addr, bus) {
196+
visit_bus(proto, PciIoAddress::new(bus, 0, 0), tree)?;
197+
}
111198
}
112199
}
113200
Ok(())
@@ -116,17 +203,17 @@ fn visit_function(
116203
fn visit_device(
117204
proto: &mut PciRootBridgeIo,
118205
addr: PciIoAddress,
119-
queue: &mut BTreeSet<FullPciIoAddress>,
206+
tree: &mut PciTree,
120207
) -> uefi::Result<()> {
121208
if get_vendor_id(proto, addr)? == 0xFFFF {
122209
return Ok(()); // device doesn't exist
123210
}
124-
visit_function(proto, addr.with_function(0), queue)?;
211+
visit_function(proto, addr.with_function(0), tree)?;
125212
if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 {
126213
// This is a multi-function device - also try the remaining functions [1;7]
127214
// These remaining functions can be sparsely populated - as long as function 0 exists.
128215
for fun in 1..=7 {
129-
visit_function(proto, addr.with_function(fun), queue)?;
216+
visit_function(proto, addr.with_function(fun), tree)?;
130217
}
131218
}
132219

@@ -136,11 +223,11 @@ fn visit_device(
136223
pub(crate) fn visit_bus(
137224
proto: &mut PciRootBridgeIo,
138225
addr: PciIoAddress,
139-
queue: &mut BTreeSet<FullPciIoAddress>,
226+
tree: &mut PciTree,
140227
) -> uefi::Result<()> {
141228
// Given a valid bus entry point - simply try all possible devices addresses
142229
for dev in 0..32 {
143-
visit_device(proto, addr.with_device(dev), queue)?;
230+
visit_device(proto, addr.with_device(dev), tree)?;
144231
}
145232
Ok(())
146233
}

uefi/src/proto/pci/mod.rs

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth;
88

99
pub mod configuration;
1010
#[cfg(feature = "alloc")]
11-
mod enumeration;
11+
pub mod enumeration;
1212
pub mod root_bridge;
1313

1414
/// IO Address for PCI/register IO operations
@@ -124,36 +124,6 @@ impl Ord for PciIoAddress {
124124
}
125125
}
126126

127-
// --------------------------------------------------------------------------------------------
128-
129-
/// Fully qualified pci address. This address is valid across root bridges.
130-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
131-
pub struct FullPciIoAddress {
132-
/// PCI segment number
133-
segment: u32,
134-
/// Subsequent PCI address
135-
addr: PciIoAddress,
136-
}
137-
impl FullPciIoAddress {
138-
/// Construct a new fully qualified pci address.
139-
#[must_use]
140-
pub const fn new(segment: u32, addr: PciIoAddress) -> Self {
141-
Self { segment, addr }
142-
}
143-
144-
/// Get the segment number this address belongs to.
145-
#[must_use]
146-
pub const fn segment(&self) -> u32 {
147-
self.segment
148-
}
149-
150-
/// Get the internal RootBridge-specific portion of the address.
151-
#[must_use]
152-
pub const fn addr(&self) -> PciIoAddress {
153-
self.addr
154-
}
155-
}
156-
157127
// ############################################################################################
158128

159129
/// Trait implemented by all data types that can natively be read from a PCI device.

uefi/src/proto/pci/root_bridge.rs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@ use crate::StatusExt;
77
#[cfg(feature = "alloc")]
88
use crate::proto::pci::configuration::QwordAddressSpaceDescriptor;
99
#[cfg(feature = "alloc")]
10-
use alloc::collections::btree_set::BTreeSet;
11-
#[cfg(feature = "alloc")]
1210
use alloc::vec::Vec;
1311
#[cfg(feature = "alloc")]
1412
use core::ffi::c_void;
1513
use core::ptr;
1614
use uefi_macros::unsafe_protocol;
1715
use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol};
1816

19-
#[cfg(doc)]
20-
use super::FullPciIoAddress;
2117
#[cfg(doc)]
2218
use crate::Status;
2319

@@ -84,10 +80,10 @@ impl PciRootBridgeIo {
8480
// ###################################################
8581
// # Convenience functionality
8682

87-
/// Recursively enumerate all devices, device functions and pci-to-pci bridges on this root bridge.
83+
/// Recursively enumerate all devices, device functions and pci(e)-to-pci(e) bridges, starting from this pci root.
8884
///
8985
/// The returned addresses might overlap with the addresses returned by another [`PciRootBridgeIo`] instance.
90-
/// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the returned [`FullPciIoAddress`]es.
86+
/// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the discovered addresses.
9187
/// **WARNING:** Only use the returned addresses with the respective [`PciRootBridgeIo`] instance that returned them.
9288
///
9389
/// # Returns
@@ -96,24 +92,27 @@ impl PciRootBridgeIo {
9692
/// # Errors
9793
/// This can basically fail with all the IO errors found in [`PciIoAccessPci`] methods.
9894
#[cfg(feature = "alloc")]
99-
pub fn enumerate(&mut self) -> crate::Result<BTreeSet<super::FullPciIoAddress>> {
95+
pub fn enumerate(&mut self) -> crate::Result<super::enumeration::PciTree> {
96+
use super::enumeration::{self, PciTree};
10097
use crate::proto::pci::configuration::ResourceRangeType;
101-
use crate::proto::pci::enumeration;
10298

103-
let mut devices = BTreeSet::new();
104-
// In the descriptors, the entry with range_type bus specifies the bus numbers that were
105-
// allocated to devices below this root bridge. The first bus number in this range is
106-
// the starting point. All subsequent numbers are reached via PCI bridge recursion during enumeration.
107-
if let Some(descriptor) = self
108-
.configuration()?
109-
.iter()
110-
.find(|d| d.resource_range_type == ResourceRangeType::Bus)
111-
{
112-
let addr = PciIoAddress::new(descriptor.address_min as u8, 0, 0);
113-
enumeration::visit_bus(self, addr, &mut devices)?;
99+
let mut tree = PciTree::new(self.segment_nr());
100+
for descriptor in self.configuration()? {
101+
// In the descriptors we can query for the current root bridge, Bus entries contain ranges of valid
102+
// bus addresses. These are all bus addresses found below ourselves. These are not only the busses
103+
// linked to **directly** from ourselves, but also recursively. Thus we use PciTree::push_bus() to
104+
// determine whether we have already visited a given bus number.
105+
if descriptor.resource_range_type == ResourceRangeType::Bus {
106+
for bus in (descriptor.address_min as u8)..=(descriptor.address_max as u8) {
107+
if tree.push_bus(bus) {
108+
let addr = PciIoAddress::new(bus, 0, 0);
109+
enumeration::visit_bus(self, addr, &mut tree)?;
110+
}
111+
}
112+
}
114113
}
115114

116-
Ok(devices)
115+
Ok(tree)
117116
}
118117
}
119118

xtask/src/qemu.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,37 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
506506
None
507507
};
508508

509+
// Make PCI tree a little more complicated so the PCI enumeration integration
510+
// test is more interesting. This specific architecture was taken from:
511+
// https://blogs.oracle.com/linux/a-study-of-the-linux-kernel-pci-subsystem-with-qemu
512+
// It employs a complex PCIe switch (x3130) setup, which makes it interesting for testing.
513+
// The bus architecture this generates is found below `0000:00:03.0` in below diagram.
514+
// (actual bus numbers and surrounding devices don't match this diagram)
515+
// -[0000:00]-+-00.0
516+
// +-01.0
517+
// +-02.0
518+
// +-03.0-[01-04]----00.0-[02-04]--+-00.0-[03]----00.0
519+
// | \-01.0-[04]----00.0
520+
// +-1f.0
521+
// +-1f.2
522+
// \-1f.3
523+
// In the diagram, `0000:03:00.0` and `0000:04:00.0` are just dummy SCSI devices
524+
// connected below the PCIe switch, to correct recursion in the bus enumeration implementation.
525+
cmd.args([
526+
"-device",
527+
"ioh3420,id=root_port1,bus=pcie.0",
528+
"-device",
529+
"x3130-upstream,id=upstream1,bus=root_port1",
530+
"-device",
531+
"xio3130-downstream,id=downstream1,bus=upstream1,chassis=9",
532+
"-device",
533+
"virtio-scsi-pci,bus=downstream1",
534+
"-device",
535+
"xio3130-downstream,id=downstream2,bus=upstream1,chassis=10",
536+
"-device",
537+
"virtio-scsi-pci,bus=downstream2",
538+
]);
539+
509540
// Pass CA certificate database to the edk2 firmware, for TLS support.
510541
cmd.args([
511542
"-fw_cfg",

0 commit comments

Comments
 (0)