Skip to content

Commit 83be255

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 83be255

File tree

6 files changed

+140
-68
lines changed

6 files changed

+140
-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: 95 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,86 @@ fn read_device_register_u32<T: Sized + Copy>(
5556
}
5657
}
5758

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

@@ -86,12 +167,12 @@ fn get_secondary_bus_range(
86167
fn visit_function(
87168
proto: &mut PciRootBridgeIo,
88169
addr: PciIoAddress,
89-
queue: &mut BTreeSet<FullPciIoAddress>,
170+
tree: &mut PciTree,
90171
) -> uefi::Result<()> {
91172
if get_vendor_id(proto, addr)? == 0xFFFF {
92173
return Ok(()); // function doesn't exist - bail instantly
93174
}
94-
queue.insert(FullPciIoAddress::new(proto.segment_nr(), addr));
175+
tree.push_device(addr);
95176
let (base_class, sub_class) = get_classes(proto, addr)?;
96177
let header_type = get_header_type(proto, addr)? & 0b01111111;
97178
if base_class == 0x6 && sub_class == 0x4 && header_type == 0x01 {
@@ -106,8 +187,11 @@ fn visit_function(
106187
return Ok(());
107188
}
108189
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)?;
190+
// Recurse into the bus namespaces on the other side of the bridge, if we haven't visited
191+
// the subordinate bus through a more direct path already
192+
if tree.push_bridge(addr, bus) {
193+
visit_bus(proto, PciIoAddress::new(bus, 0, 0), tree)?;
194+
}
111195
}
112196
}
113197
Ok(())
@@ -116,17 +200,17 @@ fn visit_function(
116200
fn visit_device(
117201
proto: &mut PciRootBridgeIo,
118202
addr: PciIoAddress,
119-
queue: &mut BTreeSet<FullPciIoAddress>,
203+
tree: &mut PciTree,
120204
) -> uefi::Result<()> {
121205
if get_vendor_id(proto, addr)? == 0xFFFF {
122206
return Ok(()); // device doesn't exist
123207
}
124-
visit_function(proto, addr.with_function(0), queue)?;
208+
visit_function(proto, addr.with_function(0), tree)?;
125209
if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 {
126210
// This is a multi-function device - also try the remaining functions [1;7]
127211
// These remaining functions can be sparsely populated - as long as function 0 exists.
128212
for fun in 1..=7 {
129-
visit_function(proto, addr.with_function(fun), queue)?;
213+
visit_function(proto, addr.with_function(fun), tree)?;
130214
}
131215
}
132216

@@ -136,11 +220,11 @@ fn visit_device(
136220
pub(crate) fn visit_bus(
137221
proto: &mut PciRootBridgeIo,
138222
addr: PciIoAddress,
139-
queue: &mut BTreeSet<FullPciIoAddress>,
223+
tree: &mut PciTree,
140224
) -> uefi::Result<()> {
141225
// Given a valid bus entry point - simply try all possible devices addresses
142226
for dev in 0..32 {
143-
visit_device(proto, addr.with_device(dev), queue)?;
227+
visit_device(proto, addr.with_device(dev), tree)?;
144228
}
145229
Ok(())
146230
}

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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,23 @@ 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.
511+
cmd.args([
512+
"-device",
513+
"ioh3420,id=root_port1,bus=pcie.0",
514+
"-device",
515+
"x3130-upstream,id=upstream1,bus=root_port1",
516+
"-device",
517+
"xio3130-downstream,id=downstream1,bus=upstream1,chassis=9",
518+
"-device",
519+
"virtio-scsi-pci,bus=downstream1",
520+
"-device",
521+
"xio3130-downstream,id=downstream2,bus=upstream1,chassis=10",
522+
"-device",
523+
"virtio-scsi-pci,bus=downstream2",
524+
]);
525+
509526
// Pass CA certificate database to the edk2 firmware, for TLS support.
510527
cmd.args([
511528
"-fw_cfg",

0 commit comments

Comments
 (0)