Skip to content

Commit 41a55f3

Browse files
committed
uefi: Add PciRootBridgeIo::enumerate() to enumerate pci device tree
1 parent 36ad85c commit 41a55f3

File tree

4 files changed

+182
-2
lines changed

4 files changed

+182
-2
lines changed

uefi/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Added
44
- Added `proto::ata::AtaRequestBuilder::read_pio()`.
55
- Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`.
6+
- Added `proto::pci::root_bridge::PciRootBridgeIo::enumerate()`.
67

78
## Changed
89
- Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg).

uefi/src/proto/pci/enumeration.rs

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

uefi/src/proto/pci/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use core::cmp::Ordering;
77
use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth;
88

99
pub mod configuration;
10+
mod enumeration;
1011
pub mod root_bridge;
1112

1213
/// IO Address for PCI/register IO operations

uefi/src/proto/pci/root_bridge.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ use core::{ffi::c_void, ptr};
77
use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit};
88
use crate::{
99
StatusExt,
10-
proto::pci::configuration::{self, QwordAddressSpaceDescriptor},
10+
proto::pci::{
11+
FullPciIoAddress,
12+
configuration::{self, QwordAddressSpaceDescriptor, ResourceRangeType},
13+
enumeration,
14+
},
1115
};
12-
use alloc::vec::Vec;
16+
use alloc::{collections::btree_set::BTreeSet, vec::Vec};
1317
use uefi_macros::unsafe_protocol;
1418
use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol};
1519

@@ -71,6 +75,36 @@ impl PciRootBridgeIo {
7175
.to_result_with_val(|| configuration::parse(resources))
7276
}
7377
}
78+
79+
// ###################################################
80+
// # Convenience functionality
81+
82+
/// Recursively enumerate all devices, device functions and pci-to-pci bridges on this root bridge.
83+
///
84+
/// The returned addresses might overlap with the addresses returned by another [`PciRootBridgeIo`] instance.
85+
/// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the returned [`FullPciIoAddress`]es.
86+
/// **WARNING:** Only use the returned addresses with the respective [`PciRootBridgeIo`] instance that returned them.
87+
///
88+
/// # Returns
89+
/// An ordered list of addresses containing all present devices below this RootBridge.
90+
///
91+
/// # Errors
92+
/// This can basically fail with all the IO errors found in [`PciIoAccessPci`] methods.
93+
pub fn enumerate(&mut self) -> crate::Result<BTreeSet<FullPciIoAddress>> {
94+
let mut devices = BTreeSet::new();
95+
for descriptor in self.configuration()? {
96+
// In the descriptors we can query for the current root bridge, Bus entries contain ranges of valid
97+
// bus addresses. These are starting points for the recursive scanning process performed in
98+
// enumeration::enum_bus
99+
if descriptor.resource_range_type == ResourceRangeType::Bus {
100+
for bus in (descriptor.address_min as u8)..=(descriptor.address_max as u8) {
101+
let addr = PciIoAddress::new(bus, 0, 0);
102+
enumeration::visit_bus(self, addr, &mut devices)?;
103+
}
104+
}
105+
}
106+
Ok(devices)
107+
}
74108
}
75109

76110
/// Struct for performing PCI I/O operations on a root bridge.

0 commit comments

Comments
 (0)