Skip to content

Commit 57056e9

Browse files
committed
uefi: Add PciRootBridgeIo::configuration() to query acpi table info
1 parent 8c135d0 commit 57056e9

File tree

4 files changed

+187
-14
lines changed

4 files changed

+187
-14
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::shell::Shell::{var(), set_var(), vars()}`
6+
- Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`.
67

78
## Changed
89
- Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg).
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! Pci root bus resource configuration descriptor parsing.
4+
5+
/// Represents the type of resource described by a QWORD Address Space Descriptor.
6+
/// This corresponds to the `resource_type` field at offset 0x03 in the descriptor.
7+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8+
#[repr(u8)]
9+
pub enum ResourceRangeType {
10+
/// Memory Range (value = 0)
11+
/// Indicates that the descriptor describes a memory-mapped address range.
12+
/// Commonly used for MMIO regions decoded by the PCI root bridge.
13+
Memory = 0,
14+
15+
/// I/O Range (value = 1)
16+
/// Indicates that the descriptor describes a legacy I/O port range.
17+
/// Used for devices that communicate via port-mapped I/O.
18+
Io = 1,
19+
20+
/// Bus Number Range (value = 2)
21+
/// Indicates that the descriptor describes a range of PCI bus numbers.
22+
/// Used to define the bus hierarchy behind a PCI root bridge.
23+
Bus = 2,
24+
25+
/// Unknown or vendor-specific resource type.
26+
/// Captures any unrecognized value for forward compatibility.
27+
Unknown(u8),
28+
}
29+
impl From<u8> for ResourceRangeType {
30+
fn from(value: u8) -> Self {
31+
match value {
32+
0 => Self::Memory,
33+
1 => Self::Io,
34+
2 => Self::Bus,
35+
other => Self::Unknown(other),
36+
}
37+
}
38+
}
39+
40+
/// Represents a parsed QWORD Address Space Descriptor from UEFI.
41+
/// This structure describes a decoded resource range for a PCI root bridge.
42+
#[derive(Clone, Debug)]
43+
pub struct QwordAddressSpaceDescriptor {
44+
/// Type of resource: Memory, I/O, Bus, or Unknown.
45+
pub resource_range_type: ResourceRangeType,
46+
/// General flags that describe decode behavior (e.g., positive decode).
47+
pub general_flags: u8,
48+
/// Type-specific flags (e.g., cacheability for memory).
49+
pub type_specific_flags: u8,
50+
/// Granularity of the address space (typically 32 or 64).
51+
/// Indicates whether the range is 32-bit or 64-bit.
52+
pub granularity: u64,
53+
/// Minimum address of the range (inclusive).
54+
pub address_min: u64,
55+
/// Maximum address of the range (inclusive).
56+
pub address_max: u64,
57+
/// Translation offset to convert host address to PCI address.
58+
/// Usually zero unless the bridge remaps addresses.
59+
pub translation_offset: u64,
60+
/// Length of the address range (in bytes or bus numbers).
61+
pub address_length: u64,
62+
}
63+
64+
/// Parses a list of QWORD Address Space Descriptors from a raw memory region.
65+
/// Stops when it encounters an End Tag descriptor (type 0x79).
66+
#[cfg(feature = "alloc")]
67+
pub(crate) fn parse(
68+
base: *const core::ffi::c_void,
69+
) -> alloc::vec::Vec<QwordAddressSpaceDescriptor> {
70+
use alloc::slice;
71+
use alloc::vec::Vec;
72+
const PCI_RESTBL_QWORDADDRSPEC_TAG: u8 = 0x8a;
73+
const PCI_RESTBL_END_TAG: u8 = 0x79;
74+
75+
let base: *const u8 = base.cast();
76+
77+
// Phase 1: determine total length
78+
let mut offset = 0;
79+
loop {
80+
let tag = unsafe { core::ptr::read(base.add(offset)) };
81+
offset += match tag {
82+
PCI_RESTBL_QWORDADDRSPEC_TAG => 3 + 0x2B,
83+
PCI_RESTBL_END_TAG => break,
84+
_ => panic!("{tag}"), // Unknown tag - bailing
85+
};
86+
}
87+
88+
// Phase 2: parse descriptors from resource table
89+
let mut bfr: &[u8] = unsafe { slice::from_raw_parts(base, offset) };
90+
let mut descriptors = Vec::new();
91+
while !bfr.is_empty() {
92+
match bfr[0] {
93+
PCI_RESTBL_QWORDADDRSPEC_TAG => {
94+
let descriptor = QwordAddressSpaceDescriptor {
95+
resource_range_type: ResourceRangeType::from(bfr[0x03]),
96+
general_flags: bfr[0x04],
97+
type_specific_flags: bfr[0x05],
98+
granularity: u64::from_le_bytes(bfr[0x06..0x06 + 8].try_into().unwrap()),
99+
address_min: u64::from_le_bytes(bfr[0x0E..0x0E + 8].try_into().unwrap()),
100+
address_max: u64::from_le_bytes(bfr[0x16..0x16 + 8].try_into().unwrap()),
101+
translation_offset: u64::from_le_bytes(bfr[0x1E..0x1E + 8].try_into().unwrap()),
102+
address_length: u64::from_le_bytes(bfr[0x26..0x26 + 8].try_into().unwrap()),
103+
};
104+
descriptors.push(descriptor);
105+
106+
bfr = &bfr[3 + 0x2B..];
107+
}
108+
_ => break,
109+
}
110+
}
111+
112+
descriptors
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use crate::proto::pci::configuration::ResourceRangeType;
118+
119+
#[test]
120+
fn parse() {
121+
// example acpi pci qword configuration table export from a qemu vm
122+
const BFR: &[u8] = &[
123+
138, 43, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 255, 111, 0, 0,
124+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 138, 43, 0, 0, 0, 0, 32,
125+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 255, 255, 15, 129, 0, 0, 0, 0, 0, 0, 0,
126+
0, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 138, 43, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0,
127+
0, 0, 0, 0, 192, 0, 0, 0, 255, 255, 15, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
128+
16, 0, 0, 0, 0, 0, 138, 43, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 121, 0,
130+
];
131+
let configuration = super::parse(BFR.as_ptr().cast());
132+
assert_eq!(configuration.len(), 4);
133+
let (mut cnt_mem, mut cnt_io, mut cnt_bus) = (0, 0, 0);
134+
for entry in &configuration {
135+
match entry.resource_range_type {
136+
ResourceRangeType::Memory => cnt_mem += 1,
137+
ResourceRangeType::Io => cnt_io += 1,
138+
ResourceRangeType::Bus => cnt_bus += 1,
139+
_ => unreachable!(),
140+
}
141+
}
142+
assert_eq!(cnt_mem, 2);
143+
assert_eq!(cnt_io, 1);
144+
assert_eq!(cnt_bus, 1);
145+
}
146+
}

uefi/src/proto/pci/mod.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use core::cmp::Ordering;
66

77
use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth;
88

9+
pub mod configuration;
910
pub mod root_bridge;
1011

1112
/// IO Address for PCI/register IO operations
@@ -188,33 +189,36 @@ mod tests {
188189
let addr1_0_0 = PciIoAddress::new(1, 0, 0);
189190

190191
assert_eq!(addr0_0_0.cmp(&addr0_0_0), Ordering::Equal);
191-
assert_eq!(addr0_0_0.cmp(addr0_0_1), Ordering::Less);
192+
assert_eq!(addr0_0_0.cmp(&addr0_0_1), Ordering::Less);
192193
assert_eq!(addr0_0_0.cmp(&addr0_1_0), Ordering::Less);
193194
assert_eq!(addr0_0_0.cmp(&addr1_0_0), Ordering::Less);
194195

195-
assert_eq!(addr0_0_1.cmp(addr0_0_0), Ordering::Greater);
196-
assert_eq!(addr0_0_1.cmp(addr0_0_1), Ordering::Equal);
196+
assert_eq!(addr0_0_1.cmp(&addr0_0_0), Ordering::Greater);
197+
assert_eq!(addr0_0_1.cmp(&addr0_0_1), Ordering::Equal);
197198
assert_eq!(addr0_0_1.cmp(&addr0_1_0), Ordering::Less);
198199
assert_eq!(addr0_0_1.cmp(&addr1_0_0), Ordering::Less);
199200

200-
assert_eq!(addr0_1_0.cmp(addr0_0_0), Ordering::Greater);
201-
assert_eq!(addr0_1_0.cmp(addr0_0_1), Ordering::Greater);
201+
assert_eq!(addr0_1_0.cmp(&addr0_0_0), Ordering::Greater);
202+
assert_eq!(addr0_1_0.cmp(&addr0_0_1), Ordering::Greater);
202203
assert_eq!(addr0_1_0.cmp(&addr0_1_0), Ordering::Equal);
203204
assert_eq!(addr0_1_0.cmp(&addr1_0_0), Ordering::Less);
204205

205-
assert_eq!(addr1_0_0.cmp(addr0_0_0), Ordering::Greater);
206-
assert_eq!(addr1_0_0.cmp(addr0_0_1), Ordering::Greater);
206+
assert_eq!(addr1_0_0.cmp(&addr0_0_0), Ordering::Greater);
207+
assert_eq!(addr1_0_0.cmp(&addr0_0_1), Ordering::Greater);
207208
assert_eq!(addr1_0_0.cmp(&addr0_1_0), Ordering::Greater);
208209
assert_eq!(addr1_0_0.cmp(&addr1_0_0), Ordering::Equal);
209210

210-
assert_eq!(addr0_0_0.cmp(addr0_0_0.with_register(1)), Ordering::Less);
211-
assert_eq!(addr0_0_0.with_register(1).cmp(addr0_0_0), Ordering::Greater);
211+
assert_eq!(addr0_0_0.cmp(&addr0_0_0.with_register(1)), Ordering::Less);
212212
assert_eq!(
213-
addr0_0_0.cmp(addr0_0_0.with_extended_register(1)),
213+
addr0_0_0.with_register(1).cmp(&addr0_0_0),
214+
Ordering::Greater
215+
);
216+
assert_eq!(
217+
addr0_0_0.cmp(&addr0_0_0.with_extended_register(1)),
214218
Ordering::Less
215219
);
216220
assert_eq!(
217-
addr0_0_0.with_extended_register(1).cmp(addr0_0_0),
221+
addr0_0_0.with_extended_register(1).cmp(&addr0_0_0),
218222
Ordering::Greater
219223
);
220224
}

uefi/src/proto/pci/root_bridge.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
//! PCI Root Bridge protocol.
44
5-
use core::ptr;
6-
75
use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit};
86
use crate::StatusExt;
7+
#[cfg(feature = "alloc")]
8+
use crate::proto::pci::configuration::{self, QwordAddressSpaceDescriptor};
9+
#[cfg(feature = "alloc")]
10+
use alloc::vec::Vec;
11+
#[cfg(feature = "alloc")]
12+
use core::ffi::c_void;
13+
use core::ptr;
914
use uefi_macros::unsafe_protocol;
1015
use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol};
1116

@@ -52,7 +57,24 @@ impl PciRootBridgeIo {
5257
// TODO: map & unmap & copy memory
5358
// TODO: buffer management
5459
// TODO: get/set attributes
55-
// TODO: configuration / resource settings
60+
61+
/// Retrieves the current resource settings of this PCI root bridge in the form of a set of ACPI resource descriptors.
62+
///
63+
/// The returned list of descriptors contains information about bus, memory and io ranges that were set up
64+
/// by the firmware.
65+
///
66+
/// # Errors
67+
/// - [`Status::UNSUPPORTED`] The current configuration of this PCI root bridge could not be retrieved.
68+
#[cfg(feature = "alloc")]
69+
pub fn configuration(&self) -> crate::Result<Vec<QwordAddressSpaceDescriptor>> {
70+
// The storage for the resource descriptors is allocated by this function. The caller must treat
71+
// the return buffer as read-only data, and the buffer must not be freed by the caller.
72+
let mut resources: *const c_void = ptr::null();
73+
unsafe {
74+
((self.0.configuration)(&self.0, &mut resources))
75+
.to_result_with_val(|| configuration::parse(resources))
76+
}
77+
}
5678
}
5779

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

0 commit comments

Comments
 (0)