Skip to content

Commit 50999b8

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

File tree

4 files changed

+218
-1
lines changed

4 files changed

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

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)