Skip to content

Commit 8577e6f

Browse files
committed
uefi: Add PciRootBridgeIo::enumeration() to recursively enumerate bus devices
1 parent f9b6b11 commit 8577e6f

File tree

4 files changed

+217
-1
lines changed

4 files changed

+217
-1
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth;
88

99
#[cfg(feature = "alloc")]
1010
pub mod configuration;
11+
#[cfg(feature = "alloc")]
12+
mod enumeration;
1113
pub mod root_bridge;
1214

1315
/// IO Address for PCI/register IO operations
@@ -123,6 +125,35 @@ impl Ord for PciIoAddress {
123125
}
124126
}
125127

128+
// --------------------------------------------------------------------------------------------
129+
130+
/// Fully qualified pci address. This address is valid across root bridges.
131+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
132+
pub struct FullPciIoAddress {
133+
/// PCI segment number
134+
segment: u32,
135+
/// Subsequent PCI address
136+
addr: PciIoAddress,
137+
}
138+
impl FullPciIoAddress {
139+
/// Construct a new fully qualified pci address.
140+
pub fn new(segment: u32, addr: PciIoAddress) -> Self {
141+
Self { segment, addr }
142+
}
143+
144+
/// Get the segment number this address belongs to.
145+
pub fn segment(&self) -> u32 {
146+
self.segment
147+
}
148+
149+
/// Get the internal RootBridge-specific portion of the address.
150+
pub fn addr(&self) -> PciIoAddress {
151+
self.addr
152+
}
153+
}
154+
155+
// ############################################################################################
156+
126157
/// Trait implemented by all data types that can natively be read from a PCI device.
127158
/// Note: Not all of them have to actually be supported by the hardware at hand.
128159
pub trait PciIoUnit: Sized + Default {}

uefi/src/proto/pci/root_bridge.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit};
66
use crate::StatusExt;
77
#[cfg(feature = "alloc")]
8-
use crate::proto::pci::configuration::{self, QwordAddressSpaceDescriptor};
8+
use crate::proto::pci::configuration::QwordAddressSpaceDescriptor;
9+
#[cfg(feature = "alloc")]
10+
use alloc::collections::btree_set::BTreeSet;
911
#[cfg(feature = "alloc")]
1012
use alloc::vec::Vec;
1113
#[cfg(feature = "alloc")]
@@ -14,6 +16,8 @@ use core::ptr;
1416
use uefi_macros::unsafe_protocol;
1517
use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol};
1618

19+
#[cfg(doc)]
20+
use super::FullPciIoAddress;
1721
#[cfg(doc)]
1822
use crate::Status;
1923

@@ -67,12 +71,48 @@ impl PciRootBridgeIo {
6771
/// - [`Status::UNSUPPORTED`] The current configuration of this PCI root bridge could not be retrieved.
6872
#[cfg(feature = "alloc")]
6973
pub fn configuration(&self) -> crate::Result<Vec<QwordAddressSpaceDescriptor>> {
74+
use crate::proto::pci::configuration;
75+
7076
let mut resources: *const c_void = ptr::null();
7177
unsafe {
7278
((self.0.configuration)(&self.0, &mut resources))
7379
.to_result_with_val(|| configuration::parse(resources))
7480
}
7581
}
82+
83+
// ###################################################
84+
// # Convenience functionality
85+
86+
/// Recursively enumerate all devices, device functions and pci-to-pci bridges on this root bridge.
87+
///
88+
/// The returned addresses might overlap with the addresses returned by another [`PciRootBridgeIo`] instance.
89+
/// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the returned [`FullPciIoAddress`]es.
90+
/// **WARNING:** Only use the returned addresses with the respective [`PciRootBridgeIo`] instance that returned them.
91+
///
92+
/// # Returns
93+
/// An ordered list of addresses containing all present devices below this RootBridge.
94+
///
95+
/// # Errors
96+
/// This can basically fail with all the IO errors found in [`PciIoAccessPci`] methods.
97+
#[cfg(feature = "alloc")]
98+
pub fn enumerate(&mut self) -> crate::Result<BTreeSet<super::FullPciIoAddress>> {
99+
use crate::proto::pci::configuration::ResourceRangeType;
100+
use crate::proto::pci::enumeration;
101+
102+
let mut devices = BTreeSet::new();
103+
for descriptor in self.configuration()? {
104+
// In the descriptors we can query for the current root bridge, Bus entries contain ranges of valid
105+
// bus addresses. These are starting points for the recursive scanning process performed in
106+
// enumeration::enum_bus
107+
if descriptor.resource_range_type == ResourceRangeType::Bus {
108+
for bus in (descriptor.address_min as u8)..=(descriptor.address_max as u8) {
109+
let addr = PciIoAddress::new(bus, 0, 0);
110+
enumeration::visit_bus(self, addr, &mut devices)?;
111+
}
112+
}
113+
}
114+
Ok(devices)
115+
}
76116
}
77117

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

0 commit comments

Comments
 (0)