Skip to content

Commit 32357f1

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 32357f1

File tree

6 files changed

+161
-68
lines changed

6 files changed

+161
-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.child_bus_of_iter(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: 102 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,93 @@ 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 should_visit_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+
/// Pushes a new bridge into the topology.
89+
///
90+
/// Returns `false` if the bus is already in the topology and `true`
91+
/// if the bridge was added to the topology.
92+
pub(crate) fn push_bridge(&mut self, addr: PciIoAddress, child_bus: u8) -> bool {
93+
match self.bus_anchors.contains_key(&child_bus) {
94+
true => false,
95+
false => {
96+
self.bus_anchors.insert(child_bus, addr);
97+
true
98+
}
99+
}
100+
}
101+
102+
/// Iterate over all valid PCI device addresses in this tree structure.
103+
pub fn iter(&self) -> btree_set::Iter<'_, PciIoAddress> {
104+
self.devices.iter()
105+
}
106+
107+
/// Get the segment number of this PCI tree.
108+
#[must_use]
109+
pub const fn segment_nr(&self) -> u32 {
110+
self.segment
111+
}
112+
113+
/// Query the address of the parent PCI bridge this `addr`'s bus is subordinate to.
114+
#[must_use]
115+
pub fn parent_for(&self, addr: PciIoAddress) -> Option<PciIoAddress> {
116+
self.bus_anchors.get(&addr.bus).cloned()
117+
}
118+
119+
/// Iterate over all subsequent busses below the given `addr`.
120+
/// This yields 0 results if `addr` doesn't point to a PCI bridge.
121+
pub fn child_bus_of_iter(&self, addr: PciIoAddress) -> impl Iterator<Item = u8> {
122+
self.bus_anchors
123+
.iter()
124+
.filter(move |&(_, parent)| *parent == addr)
125+
.map(|(bus, _)| bus)
126+
.cloned()
127+
}
128+
}
129+
impl IntoIterator for PciTree {
130+
type Item = PciIoAddress;
131+
type IntoIter = btree_set::IntoIter<PciIoAddress>;
132+
133+
fn into_iter(self) -> Self::IntoIter {
134+
self.devices.into_iter()
135+
}
136+
}
137+
impl<'a> IntoIterator for &'a PciTree {
138+
type Item = &'a PciIoAddress;
139+
type IntoIter = btree_set::Iter<'a, PciIoAddress>;
140+
141+
fn into_iter(self) -> Self::IntoIter {
142+
self.devices.iter()
143+
}
144+
}
145+
58146
// ##########################################################################################
59147
// # Query Helpers (read from a device's configuration registers)
60148

@@ -86,12 +174,12 @@ fn get_secondary_bus_range(
86174
fn visit_function(
87175
proto: &mut PciRootBridgeIo,
88176
addr: PciIoAddress,
89-
queue: &mut BTreeSet<FullPciIoAddress>,
177+
tree: &mut PciTree,
90178
) -> uefi::Result<()> {
91179
if get_vendor_id(proto, addr)? == 0xFFFF {
92180
return Ok(()); // function doesn't exist - bail instantly
93181
}
94-
queue.insert(FullPciIoAddress::new(proto.segment_nr(), addr));
182+
tree.push_device(addr);
95183
let (base_class, sub_class) = get_classes(proto, addr)?;
96184
let header_type = get_header_type(proto, addr)? & 0b01111111;
97185
if base_class == 0x6 && sub_class == 0x4 && header_type == 0x01 {
@@ -106,8 +194,11 @@ fn visit_function(
106194
return Ok(());
107195
}
108196
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)?;
197+
// Recurse into the bus namespaces on the other side of the bridge, if we haven't visited
198+
// the subordinate bus through a more direct path already
199+
if tree.push_bridge(addr, bus) {
200+
visit_bus(proto, PciIoAddress::new(bus, 0, 0), tree)?;
201+
}
111202
}
112203
}
113204
Ok(())
@@ -116,17 +207,17 @@ fn visit_function(
116207
fn visit_device(
117208
proto: &mut PciRootBridgeIo,
118209
addr: PciIoAddress,
119-
queue: &mut BTreeSet<FullPciIoAddress>,
210+
tree: &mut PciTree,
120211
) -> uefi::Result<()> {
121212
if get_vendor_id(proto, addr)? == 0xFFFF {
122213
return Ok(()); // device doesn't exist
123214
}
124-
visit_function(proto, addr.with_function(0), queue)?;
215+
visit_function(proto, addr.with_function(0), tree)?;
125216
if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 {
126217
// This is a multi-function device - also try the remaining functions [1;7]
127218
// These remaining functions can be sparsely populated - as long as function 0 exists.
128219
for fun in 1..=7 {
129-
visit_function(proto, addr.with_function(fun), queue)?;
220+
visit_function(proto, addr.with_function(fun), tree)?;
130221
}
131222
}
132223

@@ -136,11 +227,11 @@ fn visit_device(
136227
pub(crate) fn visit_bus(
137228
proto: &mut PciRootBridgeIo,
138229
addr: PciIoAddress,
139-
queue: &mut BTreeSet<FullPciIoAddress>,
230+
tree: &mut PciTree,
140231
) -> uefi::Result<()> {
141232
// Given a valid bus entry point - simply try all possible devices addresses
142233
for dev in 0..32 {
143-
visit_device(proto, addr.with_device(dev), queue)?;
234+
visit_device(proto, addr.with_device(dev), tree)?;
144235
}
145236
Ok(())
146237
}

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.should_visit_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 test 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)