Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

## Added
- Added `proto::ata::AtaRequestBuilder::read_pio()`.
- Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`.
- Added `proto::pci::root_bridge::PciRootBridgeIo::enumerate()`.

## Changed

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

# uefi - v0.36.1 (2025-11-05)

Expand Down
114 changes: 114 additions & 0 deletions uefi/src/proto/pci/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Pci root bus resource configuration descriptor parsing.

use core::ffi::c_void;
use core::ptr;

use alloc::slice;
use alloc::vec::Vec;

/// Represents the type of resource described by a QWORD Address Space Descriptor.
/// This corresponds to the `resource_type` field at offset 0x03 in the descriptor.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ResourceRangeType {
/// Memory Range (value = 0)
/// Indicates that the descriptor describes a memory-mapped address range.
/// Commonly used for MMIO regions decoded by the PCI root bridge.
Memory = 0,

/// I/O Range (value = 1)
/// Indicates that the descriptor describes a legacy I/O port range.
/// Used for devices that communicate via port-mapped I/O.
Io = 1,

/// Bus Number Range (value = 2)
/// Indicates that the descriptor describes a range of PCI bus numbers.
/// Used to define the bus hierarchy behind a PCI root bridge.
Bus = 2,

/// Unknown or vendor-specific resource type.
/// Captures any unrecognized value for forward compatibility.
Unknown(u8),
}
impl From<u8> for ResourceRangeType {
fn from(value: u8) -> Self {
match value {
0 => Self::Memory,
1 => Self::Io,
2 => Self::Bus,
other => Self::Unknown(other),
}
}
}

/// Represents a parsed QWORD Address Space Descriptor from UEFI.
/// This structure describes a decoded resource range for a PCI root bridge.
#[derive(Clone, Debug)]
pub struct QwordAddressSpaceDescriptor {
/// Type of resource: Memory, I/O, Bus, or Unknown.
pub resource_range_type: ResourceRangeType,
/// General flags that describe decode behavior (e.g., positive decode).
pub general_flags: u8,
/// Type-specific flags (e.g., cacheability for memory).
pub type_specific_flags: u8,
/// Granularity of the address space (typically 32 or 64).
/// Indicates whether the range is 32-bit or 64-bit.
pub granularity: u64,
/// Minimum address of the range (inclusive).
pub address_min: u64,
/// Maximum address of the range (inclusive).
pub address_max: u64,
/// Translation offset to convert host address to PCI address.
/// Usually zero unless the bridge remaps addresses.
pub translation_offset: u64,
/// Length of the address range (in bytes or bus numbers).
pub address_length: u64,
}

const PCI_RESTBL_QWORDADDRSPEC_TAG: u8 = 0x8a;
const PCI_RESTBL_END_TAG: u8 = 0x79;

/// Parses a list of QWORD Address Space Descriptors from a raw memory region.
/// Stops when it encounters an End Tag descriptor (type 0x79).
pub(crate) fn parse(base: *const c_void) -> Vec<QwordAddressSpaceDescriptor> {
let base: *const u8 = base.cast();

// Phase 1: determine total length
let mut offset = 0;
loop {
let tag = unsafe { ptr::read(base.add(offset)) };
offset += match tag {
PCI_RESTBL_QWORDADDRSPEC_TAG => 3 + 0x2B,
PCI_RESTBL_END_TAG => break,
_ => return Vec::new(), // Unknown tag - bailing
};
}

// Phase 2: parse descriptors from resource table
let mut bfr: &[u8] = unsafe { slice::from_raw_parts(base, offset) };
let mut descriptors = Vec::new();
while !bfr.is_empty() {
match bfr[0] {
PCI_RESTBL_QWORDADDRSPEC_TAG => {
let descriptor = QwordAddressSpaceDescriptor {
resource_range_type: ResourceRangeType::from(bfr[0x03]),
general_flags: bfr[0x04],
type_specific_flags: bfr[0x05],
granularity: u64::from_le_bytes(bfr[0x06..0x06 + 8].try_into().unwrap()),
address_min: u64::from_le_bytes(bfr[0x0E..0x0E + 8].try_into().unwrap()),
address_max: u64::from_le_bytes(bfr[0x16..0x16 + 8].try_into().unwrap()),
translation_offset: u64::from_le_bytes(bfr[0x1E..0x1E + 8].try_into().unwrap()),
address_length: u64::from_le_bytes(bfr[0x26..0x26 + 8].try_into().unwrap()),
};
descriptors.push(descriptor);

bfr = &bfr[3 + 0x2B..];
}
_ => break,
}
}

descriptors
}
145 changes: 145 additions & 0 deletions uefi/src/proto/pci/enumeration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! PCI Bus device function and bridge enumeration.

use core::mem;

use alloc::collections::btree_set::BTreeSet;

use super::root_bridge::PciRootBridgeIo;
use super::{FullPciIoAddress, PciIoAddress};

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciRegister0 {
vendor_id: u16,
device_id: u16,
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciRegister2 {
revision_id: u8,
prog_if: u8,
subclass: u8,
class: u8,
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciRegister3 {
cache_line_size: u8,
latency_timer: u8,
header_type: u8,
bist: u8,
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciHeader1Register6 {
secondary_latency_timer: u8,
subordinate_bus: u8,
secondary_bus: u8,
primary_bus: u8,
}

/// Read the 4byte pci register with the given `addr` and cast it into the given structured representation.
fn read_device_register_u32<T: Sized + Copy>(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
) -> uefi::Result<T> {
unsafe {
let raw = proto.pci().read_one::<u32>(addr)?;
let reg: T = mem::transmute_copy(&raw);
Ok(reg)
}
}

// ##########################################################################################
// # Query Helpers (read from a device's configuration registers)

fn get_vendor_id(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<u16> {
read_device_register_u32::<PciRegister0>(proto, addr.with_register(0)).map(|v| v.vendor_id)
}

fn get_classes(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<(u8, u8)> {
let reg = read_device_register_u32::<PciRegister2>(proto, addr.with_register(2 * 4))?;
Ok((reg.class, reg.subclass))
}

fn get_header_type(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<u8> {
read_device_register_u32::<PciRegister3>(proto, addr.with_register(3 * 4))
.map(|v| v.header_type)
}

fn get_secondary_bus_range(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
) -> uefi::Result<(u8, u8)> {
let reg = read_device_register_u32::<PciHeader1Register6>(proto, addr.with_register(6 * 4))?;
Ok((reg.secondary_bus, reg.subordinate_bus))
}

// ##########################################################################################
// # Recursive visitor implementation

fn visit_function(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
queue: &mut BTreeSet<FullPciIoAddress>,
) -> uefi::Result<()> {
if get_vendor_id(proto, addr)? == 0xFFFF {
return Ok(()); // function doesn't exist - bail instantly
}
queue.insert(FullPciIoAddress::new(proto.segment_nr(), addr));
let (base_class, sub_class) = get_classes(proto, addr)?;
if base_class == 0x6 && sub_class == 0x4 && get_header_type(proto, addr)? == 0x01 {
// This is a PCI-to-PCI bridge controller. The current `addr` is the address with which it's
// mounted in the PCI tree we are currently traversing. Now we query its header, where
// the bridge tells us a range of addresses [secondary;subordinate], with which the other
// side of the bridge is mounted into the PCI tree.
let (secondary_bus_nr, subordinate_bus_nr) = get_secondary_bus_range(proto, addr)?;
if secondary_bus_nr == 0 || subordinate_bus_nr < secondary_bus_nr {
// If the secondary bus number is the root number, or if the range is invalid - this hardware
// is so horribly broken that we refrain from touching it. It might explode - or worse!
return Ok(());
}
for bus in secondary_bus_nr..=subordinate_bus_nr {
// Recurse into the bus namespaces on the other side of the bridge
visit_bus(proto, PciIoAddress::new(bus, 0, 0), queue)?;
}
}
Ok(())
}

fn visit_device(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
queue: &mut BTreeSet<FullPciIoAddress>,
) -> uefi::Result<()> {
if get_vendor_id(proto, addr)? == 0xFFFF {
return Ok(()); // device doesn't exist
}
visit_function(proto, addr.with_function(0), queue)?;
if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 {
// This is a multi-function device - also try the remaining functions [1;7]
// These remaining functions can be sparsely populated - as long as function 0 exists.
for fun in 1..=7 {
visit_function(proto, addr.with_function(fun), queue)?;
}
}

Ok(())
}

pub(crate) fn visit_bus(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
queue: &mut BTreeSet<FullPciIoAddress>,
) -> uefi::Result<()> {
// Given a valid bus entry point - simply try all possible devices addresses
for dev in 0..32 {
visit_device(proto, addr.with_device(dev), queue)?;
}
Ok(())
}
Loading
Loading