diff --git a/uefi-test-runner/src/proto/pci/root_bridge.rs b/uefi-test-runner/src/proto/pci/root_bridge.rs index 06eeb9337..70d30b1b3 100644 --- a/uefi-test-runner/src/proto/pci/root_bridge.rs +++ b/uefi-test-runner/src/proto/pci/root_bridge.rs @@ -1,9 +1,14 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 +use alloc::collections::btree_set::BTreeSet; +use alloc::string::ToString; use uefi::Handle; use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, image_handle}; use uefi::proto::ProtocolPointer; +use uefi::proto::device_path::DevicePath; +use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; use uefi::proto::pci::root_bridge::PciRootBridgeIo; +use uefi::proto::scsi::pass_thru::ExtScsiPassThru; const RED_HAT_PCI_VENDOR_ID: u16 = 0x1AF4; const MASS_STORAGE_CTRL_CLASS_CODE: u8 = 0x1; @@ -14,22 +19,21 @@ const REG_SIZE: u8 = size_of::() as u8; pub fn test() { let pci_handles = uefi::boot::find_handles::().unwrap(); + let mut sata_ctrl_cnt = 0; let mut red_hat_dev_cnt = 0; let mut mass_storage_ctrl_cnt = 0; - let mut sata_ctrl_cnt = 0; + let mut mass_storage_dev_paths = BTreeSet::new(); for pci_handle in pci_handles { let mut pci_proto = get_open_protocol::(pci_handle); + let root_device_path = get_open_protocol::(pci_handle); - let devices = pci_proto.enumerate().unwrap(); - for fqaddr in devices { - let addr = fqaddr.addr(); - let Ok(reg0) = pci_proto.pci().read_one::(addr.with_register(0)) else { - continue; - }; - if reg0 == 0xFFFFFFFF { - continue; // not a valid device - } + let pci_tree = pci_proto.enumerate().unwrap(); + for addr in pci_tree.iter().cloned() { + let reg0 = pci_proto + .pci() + .read_one::(addr.with_register(0)) + .unwrap(); let reg1 = pci_proto .pci() .read_one::(addr.with_register(2 * REG_SIZE)) @@ -37,30 +41,51 @@ pub fn test() { let vendor_id = (reg0 & 0xFFFF) as u16; let device_id = (reg0 >> 16) as u16; + let class_code = (reg1 >> 24) as u8; + let subclass_code = ((reg1 >> 16) & 0xFF) as u8; + let device_path = pci_tree.device_path(&root_device_path, addr).unwrap(); + let device_path_str = device_path + .to_string(DisplayOnly(false), AllowShortcuts(false)) + .unwrap() + .to_string(); + if vendor_id == RED_HAT_PCI_VENDOR_ID { red_hat_dev_cnt += 1; } - - let class_code = (reg1 >> 24) as u8; - let subclass_code = ((reg1 >> 16) & 0xFF) as u8; if class_code == MASS_STORAGE_CTRL_CLASS_CODE { mass_storage_ctrl_cnt += 1; - if subclass_code == SATA_CTRL_SUBCLASS_CODE { sata_ctrl_cnt += 1; } + mass_storage_dev_paths.insert(device_path_str.clone()); } let (bus, dev, fun) = (addr.bus, addr.dev, addr.fun); log::info!( - "PCI Device: [{bus}, {dev}, {fun}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X}" + "PCI Device: [{bus:02x}, {dev:02x}, {fun:02x}]: vendor={vendor_id:04X}, device={device_id:04X}, class={class_code:02X}, subclass={subclass_code:02X} - {}", + device_path_str ); + for child_bus in pci_tree.iter_subsequent_busses_for(addr) { + log::info!(" \\- Bus: {child_bus:02x}"); + } } } + assert!(sata_ctrl_cnt > 0); assert!(red_hat_dev_cnt > 0); assert!(mass_storage_ctrl_cnt > 0); - assert!(sata_ctrl_cnt > 0); + assert_eq!(mass_storage_ctrl_cnt, mass_storage_dev_paths.len()); + + // Check that all `ExtScsiPassThru` instances' device paths have been found + let scsi_handles = uefi::boot::find_handles::().unwrap(); + for scsi_handle in scsi_handles { + let device_path = get_open_protocol::(scsi_handle); + let device_path = device_path + .to_string(DisplayOnly(false), AllowShortcuts(false)) + .unwrap() + .to_string(); + assert!(mass_storage_dev_paths.contains(&device_path)); + } } fn get_open_protocol(handle: Handle) -> ScopedProtocol

{ diff --git a/uefi-test-runner/src/proto/scsi/pass_thru.rs b/uefi-test-runner/src/proto/scsi/pass_thru.rs index 8c63e25ca..71786bf87 100644 --- a/uefi-test-runner/src/proto/scsi/pass_thru.rs +++ b/uefi-test-runner/src/proto/scsi/pass_thru.rs @@ -16,9 +16,9 @@ fn test_allocating_api() { // by default respectively. We manually configure an additional SCSI controller. // Thus, we should see two controllers with support for EXT_SCSI_PASS_THRU on this platform #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - assert_eq!(scsi_ctrl_handles.len(), 2); + assert_eq!(scsi_ctrl_handles.len(), 4); #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] - assert_eq!(scsi_ctrl_handles.len(), 1); + assert_eq!(scsi_ctrl_handles.len(), 3); let mut found_drive = false; for handle in scsi_ctrl_handles { diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index ff89f01e7..f1739eabb 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -5,6 +5,9 @@ - Added `proto::shell::Shell::{var(), set_var(), vars()}` - Added `proto::pci::root_bridge::PciRootBridgeIo::configuration()`. - Added `proto::pci::root_bridge::PciRootBridgeIo::enumerate()`. +- Added `proto::device_path::DevicePath::to_pool()`. +- Added `proto::device_path::DevicePathUtilities::duplicate_path()`. +- Added `proto::pci::enumeration::PciTree::device_path()`. ## Changed - Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg). diff --git a/uefi/src/proto/device_path/mod.rs b/uefi/src/proto/device_path/mod.rs index 254fd5d6a..dd1e30638 100644 --- a/uefi/src/proto/device_path/mod.rs +++ b/uefi/src/proto/device_path/mod.rs @@ -590,6 +590,14 @@ impl DevicePath { unsafe { mem::transmute(data) } } + /// Returns an owned pool copy of this path. + #[cfg(feature = "alloc")] + pub fn to_pool(&self) -> Result { + open_utility_protocol()? + .duplicate_path(self) + .map_err(|_| DevicePathUtilitiesError::OutOfMemory) + } + /// Transforms the device path to its string representation using the /// [`DevicePathToText`] protocol. #[cfg(feature = "alloc")] diff --git a/uefi/src/proto/device_path/util.rs b/uefi/src/proto/device_path/util.rs index bc1dbab74..8d0e9cc37 100644 --- a/uefi/src/proto/device_path/util.rs +++ b/uefi/src/proto/device_path/util.rs @@ -29,6 +29,22 @@ impl DevicePathUtilities { unsafe { (self.0.get_device_path_size)(device_path.as_ffi_ptr().cast()) } } + /// Create a new device path by cloning the given `path` into newly allocated memory. + /// + /// # Arguments + /// - `path`: A reference to the device path to clone. + /// + /// # Returns + /// A [`PoolDevicePath`] instance created by cloning the given `path`. + pub fn duplicate_path(&self, path: &DevicePath) -> crate::Result { + unsafe { + let ptr = (self.0.duplicate_device_path)(path.as_ffi_ptr().cast()); + NonNull::new(ptr.cast_mut()) + .map(|p| PoolDevicePath(PoolAllocation::new(p.cast()))) + .ok_or_else(|| Status::OUT_OF_RESOURCES.into()) + } + } + /// Creates a new device path by appending the second device path to the first. /// /// # Arguments diff --git a/uefi/src/proto/pci/enumeration.rs b/uefi/src/proto/pci/enumeration.rs index 4564cc96f..be692a3b1 100644 --- a/uefi/src/proto/pci/enumeration.rs +++ b/uefi/src/proto/pci/enumeration.rs @@ -2,12 +2,16 @@ //! PCI Bus device function and bridge enumeration. -use core::mem; +use core::mem::{self, MaybeUninit}; -use alloc::collections::btree_set::BTreeSet; +use alloc::collections::btree_map::BTreeMap; +use alloc::collections::btree_set::{self, BTreeSet}; +use crate::proto::device_path::build::{BuildError, DevicePathBuilder}; +use crate::proto::device_path::{self, DevicePath, DevicePathUtilitiesError, PoolDevicePath}; + +use super::PciIoAddress; use super::root_bridge::PciRootBridgeIo; -use super::{FullPciIoAddress, PciIoAddress}; #[allow(unused)] #[derive(Clone, Copy, Debug)] @@ -55,6 +59,159 @@ fn read_device_register_u32( } } +// ########################################################################################## + +/// Error type used by the device path construction of [`PciTree`]. +#[derive(Debug)] +pub enum PciDevicePathBuildError { + /// The given [`PciIoAddress`] was invalid or not path of the enumeration. + InvalidAddress, + /// Error while constructing the pci device DevicePath. + PathBuildError(BuildError), + /// Error while + DevicePathUtilitiesError(DevicePathUtilitiesError), +} +impl From for PciDevicePathBuildError { + fn from(value: BuildError) -> Self { + Self::PathBuildError(value) + } +} +impl From for PciDevicePathBuildError { + fn from(value: DevicePathUtilitiesError) -> Self { + Self::DevicePathUtilitiesError(value) + } +} + +// ------------------------------------------------------------------------------------------ + +/// Struct representing the tree structure of PCI devices. +/// This allows iterating over all valid PCI device addresses in a tree, as well as query +/// the tree topology. +#[derive(Debug)] +pub struct PciTree { + segment: u32, + devices: BTreeSet, + bus_anchors: BTreeMap, +} +impl PciTree { + pub(crate) const fn new(segment: u32) -> Self { + Self { + segment, + devices: BTreeSet::new(), + bus_anchors: BTreeMap::new(), + } + } + + pub(crate) fn push_bus(&self, bus: u8) -> bool { + !self.bus_anchors.contains_key(&bus) + } + + pub(crate) fn push_device(&mut self, addr: PciIoAddress) { + self.devices.insert(addr); + } + + pub(crate) fn push_bridge(&mut self, addr: PciIoAddress, child_bus: u8) -> bool { + match self.bus_anchors.contains_key(&child_bus) { + true => false, + false => { + self.bus_anchors.insert(child_bus, addr); + true + } + } + } + + /// Iterate over all valid PCI device addresses in this tree structure. + pub fn iter(&self) -> btree_set::Iter<'_, PciIoAddress> { + self.devices.iter() + } + + /// Get the segment number of this PCI tree. + #[must_use] + pub const fn segment_nr(&self) -> u32 { + self.segment + } + + /// Query the address of the parent PCI bridge this `addr`'s bus is subordinate to. + #[must_use] + pub fn parent_for(&self, addr: PciIoAddress) -> Option { + self.bus_anchors.get(&addr.bus).cloned() + } + + /// Iterate over all subsequent busses below the given `addr`. + /// This yields 0 results if `addr` doesn't point to a PCI bridge. + pub fn iter_subsequent_busses_for(&self, addr: PciIoAddress) -> impl Iterator { + self.bus_anchors + .iter() + .filter(move |&(_, parent)| *parent == addr) + .map(|(bus, _)| bus) + .cloned() + } + + /// Construct a device path for the given PCI `addr` and append it to the given `root_path`. + /// + /// # Arguments + /// - `root_path`: The [`DevicePath`] instance corresponding to the [`PciRootBridgeIo`] instance that + /// produced this [`PciTree`]. This path is prepended to the generated device paths. + /// - `addr`: [`PciIoAddress`] of the device + pub fn device_path( + &self, + root_path: &DevicePath, + addr: PciIoAddress, + ) -> Result { + use device_path::build; + + if !self.devices.contains(&addr) { + return Err(PciDevicePathBuildError::InvalidAddress); + } + + // A PCI [`DevicePath`] can have max. 255 PCI segments, each of which is 6 bytes in size. + // These are prepended by the given `root_path`. A construction buffer of 2048 bytes + // should thus suffice for all realistic scenarios. + let mut bfr = [MaybeUninit::uninit(); 2048]; + let mut builder = DevicePathBuilder::with_buf(&mut bfr); + for node in root_path.node_iter() { + builder = builder.push(&node)?; + } + + // A pci device path is built by appending segments of `dev` and `fun` address byte pairs + // starting from a pci root bus to the specified address. Since the child <-> parent + // relationship is stored from child to parent, we start at the address and recurse back + // to the parent for path generation. + fn inner<'a>( + root: &PciTree, + mut builder: DevicePathBuilder<'a>, + addr: PciIoAddress, + ) -> Result, BuildError> { + if let Some(parent) = root.parent_for(addr) { + builder = inner(root, builder, parent)?; + } + builder.push(&build::hardware::Pci { + function: addr.fun, + device: addr.dev, + }) + } + + builder = inner(self, builder, addr)?; + Ok(builder.finalize()?.to_pool()?) + } +} +impl IntoIterator for PciTree { + type Item = PciIoAddress; + type IntoIter = btree_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.devices.into_iter() + } +} +impl<'a> IntoIterator for &'a PciTree { + type Item = &'a PciIoAddress; + type IntoIter = btree_set::Iter<'a, PciIoAddress>; + + fn into_iter(self) -> Self::IntoIter { + self.devices.iter() + } +} + // ########################################################################################## // # Query Helpers (read from a device's configuration registers) @@ -86,12 +243,12 @@ fn get_secondary_bus_range( fn visit_function( proto: &mut PciRootBridgeIo, addr: PciIoAddress, - queue: &mut BTreeSet, + tree: &mut PciTree, ) -> 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)); + tree.push_device(addr); let (base_class, sub_class) = get_classes(proto, addr)?; let header_type = get_header_type(proto, addr)? & 0b01111111; if base_class == 0x6 && sub_class == 0x4 && header_type == 0x01 { @@ -106,8 +263,11 @@ fn visit_function( 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)?; + // Recurse into the bus namespaces on the other side of the bridge, if we haven't visited + // the subordinate bus through a more direct path already + if tree.push_bridge(addr, bus) { + visit_bus(proto, PciIoAddress::new(bus, 0, 0), tree)?; + } } } Ok(()) @@ -116,17 +276,17 @@ fn visit_function( fn visit_device( proto: &mut PciRootBridgeIo, addr: PciIoAddress, - queue: &mut BTreeSet, + tree: &mut PciTree, ) -> uefi::Result<()> { if get_vendor_id(proto, addr)? == 0xFFFF { return Ok(()); // device doesn't exist } - visit_function(proto, addr.with_function(0), queue)?; + visit_function(proto, addr.with_function(0), tree)?; 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)?; + visit_function(proto, addr.with_function(fun), tree)?; } } @@ -136,11 +296,11 @@ fn visit_device( pub(crate) fn visit_bus( proto: &mut PciRootBridgeIo, addr: PciIoAddress, - queue: &mut BTreeSet, + tree: &mut PciTree, ) -> 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)?; + visit_device(proto, addr.with_device(dev), tree)?; } Ok(()) } diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index fb667f98d..8c0d46c28 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -8,7 +8,7 @@ use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; pub mod configuration; #[cfg(feature = "alloc")] -mod enumeration; +pub mod enumeration; pub mod root_bridge; /// IO Address for PCI/register IO operations @@ -124,36 +124,6 @@ impl Ord for PciIoAddress { } } -// -------------------------------------------------------------------------------------------- - -/// Fully qualified pci address. This address is valid across root bridges. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct FullPciIoAddress { - /// PCI segment number - segment: u32, - /// Subsequent PCI address - addr: PciIoAddress, -} -impl FullPciIoAddress { - /// Construct a new fully qualified pci address. - #[must_use] - pub const fn new(segment: u32, addr: PciIoAddress) -> Self { - Self { segment, addr } - } - - /// Get the segment number this address belongs to. - #[must_use] - pub const fn segment(&self) -> u32 { - self.segment - } - - /// Get the internal RootBridge-specific portion of the address. - #[must_use] - pub const fn addr(&self) -> PciIoAddress { - self.addr - } -} - // ############################################################################################ /// Trait implemented by all data types that can natively be read from a PCI device. diff --git a/uefi/src/proto/pci/root_bridge.rs b/uefi/src/proto/pci/root_bridge.rs index c17975085..1be793951 100644 --- a/uefi/src/proto/pci/root_bridge.rs +++ b/uefi/src/proto/pci/root_bridge.rs @@ -7,8 +7,6 @@ use crate::StatusExt; #[cfg(feature = "alloc")] use crate::proto::pci::configuration::QwordAddressSpaceDescriptor; #[cfg(feature = "alloc")] -use alloc::collections::btree_set::BTreeSet; -#[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "alloc")] use core::ffi::c_void; @@ -16,8 +14,6 @@ use core::ptr; use uefi_macros::unsafe_protocol; use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol}; -#[cfg(doc)] -use super::FullPciIoAddress; #[cfg(doc)] use crate::Status; @@ -84,10 +80,10 @@ impl PciRootBridgeIo { // ################################################### // # Convenience functionality - /// Recursively enumerate all devices, device functions and pci-to-pci bridges on this root bridge. + /// Recursively enumerate all devices, device functions and pci(e)-to-pci(e) bridges, starting from this pci root. /// /// The returned addresses might overlap with the addresses returned by another [`PciRootBridgeIo`] instance. - /// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the returned [`FullPciIoAddress`]es. + /// Make sure to perform some form of cross-[`PciRootBridgeIo`] deduplication on the discovered addresses. /// **WARNING:** Only use the returned addresses with the respective [`PciRootBridgeIo`] instance that returned them. /// /// # Returns @@ -96,24 +92,27 @@ impl PciRootBridgeIo { /// # Errors /// This can basically fail with all the IO errors found in [`PciIoAccessPci`] methods. #[cfg(feature = "alloc")] - pub fn enumerate(&mut self) -> crate::Result> { + pub fn enumerate(&mut self) -> crate::Result { + use super::enumeration::{self, PciTree}; use crate::proto::pci::configuration::ResourceRangeType; - use crate::proto::pci::enumeration; - let mut devices = BTreeSet::new(); - // In the descriptors, the entry with range_type bus specifies the bus numbers that were - // allocated to devices below this root bridge. The first bus number in this range is - // the starting point. All subsequent numbers are reached via PCI bridge recursion during enumeration. - if let Some(descriptor) = self - .configuration()? - .iter() - .find(|d| d.resource_range_type == ResourceRangeType::Bus) - { - let addr = PciIoAddress::new(descriptor.address_min as u8, 0, 0); - enumeration::visit_bus(self, addr, &mut devices)?; + let mut tree = PciTree::new(self.segment_nr()); + for descriptor in self.configuration()? { + // In the descriptors we can query for the current root bridge, Bus entries contain ranges of valid + // bus addresses. These are all bus addresses found below ourselves. These are not only the busses + // linked to **directly** from ourselves, but also recursively. Thus we use PciTree::push_bus() to + // determine whether we have already visited a given bus number. + if descriptor.resource_range_type == ResourceRangeType::Bus { + for bus in (descriptor.address_min as u8)..=(descriptor.address_max as u8) { + if tree.push_bus(bus) { + let addr = PciIoAddress::new(bus, 0, 0); + enumeration::visit_bus(self, addr, &mut tree)?; + } + } + } } - Ok(devices) + Ok(tree) } } diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index b8aed7f05..4c461dd0a 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -506,6 +506,23 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { None }; + // Make PCI tree a little more complicated so the PCI enumeration integration + // test is more interesting. + cmd.args([ + "-device", + "ioh3420,id=root_port1,bus=pcie.0", + "-device", + "x3130-upstream,id=upstream1,bus=root_port1", + "-device", + "xio3130-downstream,id=downstream1,bus=upstream1,chassis=9", + "-device", + "virtio-scsi-pci,bus=downstream1", + "-device", + "xio3130-downstream,id=downstream2,bus=upstream1,chassis=10", + "-device", + "virtio-scsi-pci,bus=downstream2", + ]); + // Pass CA certificate database to the edk2 firmware, for TLS support. cmd.args([ "-fw_cfg",