diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index aa36a49c6..3527e0b29 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -20,6 +20,7 @@ pub fn test() { loaded_image::test(); media::test(); network::test(); + pci::test(); pi::test(); rng::test(); shell_params::test(); @@ -84,6 +85,7 @@ mod media; mod misc; mod network; mod nvme; +mod pci; mod pi; mod rng; mod scsi; diff --git a/uefi-test-runner/src/proto/pci/mod.rs b/uefi-test-runner/src/proto/pci/mod.rs new file mode 100644 index 000000000..75ecbc0ad --- /dev/null +++ b/uefi-test-runner/src/proto/pci/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pub mod root_bridge; + +pub fn test() { + root_bridge::test(); +} diff --git a/uefi-test-runner/src/proto/pci/root_bridge.rs b/uefi-test-runner/src/proto/pci/root_bridge.rs new file mode 100644 index 000000000..a905baf5e --- /dev/null +++ b/uefi-test-runner/src/proto/pci/root_bridge.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use core::mem; +use uefi::Handle; +use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, image_handle}; +use uefi::proto::ProtocolPointer; +use uefi::proto::pci::PciIoAddress; +use uefi::proto::pci::root_bridge::PciRootBridgeIo; + +const RED_HAT_PCI_VENDOR_ID: u16 = 0x1AF4; +const MASS_STORAGE_CTRL_CLASS_CODE: u8 = 0x1; +const SATA_CTRL_SUBCLASS_CODE: u8 = 0x6; + +const REG_SIZE: u8 = mem::size_of::() as u8; + +pub fn test() { + let pci_handles = uefi::boot::find_handles::().unwrap(); + + let mut red_hat_dev_cnt = 0; + let mut mass_storage_ctrl_cnt = 0; + let mut sata_ctrl_cnt = 0; + + for pci_handle in pci_handles { + let mut pci_proto = get_open_protocol::(pci_handle); + + for bus in 0..=255 { + for dev in 0..32 { + for fun in 0..8 { + let addr = PciIoAddress::new(bus, dev, fun); + let Ok(reg0) = pci_proto.pci().read_one::(addr.with_register(0)) else { + continue; + }; + if reg0 == 0xFFFFFFFF { + continue; // not a valid device + } + let reg1 = pci_proto + .pci() + .read_one::(addr.with_register(2 * REG_SIZE)) + .unwrap(); + + let vendor_id = (reg0 & 0xFFFF) as u16; + let device_id = (reg0 >> 16) as u16; + 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; + } + } + + log::info!( + "PCI Device: [{}, {}, {}]: vendor={:04X}, device={:04X}, class={:02X}, subclass={:02X}", + bus, + dev, + fun, + vendor_id, + device_id, + class_code, + subclass_code + ); + } + } + } + } + + assert!(red_hat_dev_cnt > 0); + assert!(mass_storage_ctrl_cnt > 0); + assert!(sata_ctrl_cnt > 0); +} + +fn get_open_protocol(handle: Handle) -> ScopedProtocol

{ + let open_opts = OpenProtocolParams { + handle, + agent: image_handle(), + controller: None, + }; + let open_attrs = OpenProtocolAttributes::GetProtocol; + unsafe { uefi::boot::open_protocol(open_opts, open_attrs).unwrap() } +} diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 75bd767f0..480f5e798 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -3,6 +3,7 @@ ## Added - Added `ConfigTableEntry::MEMORY_ATTRIBUTES_GUID` and `ConfigTableEntry::IMAGE_SECURITY_DATABASE_GUID`. - Added `proto::usb::io::UsbIo`. +- Added `proto::pci::PciRootBridgeIo`. ## Changed - **Breaking:** `boot::stall` now take `core::time::Duration` instead of `usize`. diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 77474248e..defc0e4f9 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -22,6 +22,7 @@ pub mod misc; pub mod network; #[cfg(feature = "alloc")] pub mod nvme; +pub mod pci; pub mod pi; pub mod rng; #[cfg(feature = "alloc")] diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs new file mode 100644 index 000000000..348b59864 --- /dev/null +++ b/uefi/src/proto/pci/mod.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! PCI Bus specific protocols. + +use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; + +pub mod root_bridge; + +/// IO Address for PCI/register IO operations +#[repr(C, packed)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PciIoAddress { + /// Register number within the PCI device. + pub reg: u8, + /// Function number within the PCI device. + pub fun: u8, + /// Device number within the PCI bus. + pub dev: u8, + /// Bus number in the PCI hierarchy. + pub bus: u8, + /// Extended register number within the PCI device. + pub ext_reg: u32, +} + +impl PciIoAddress { + /// Create address pointing to the device identified by `bus`, `dev` and `fun` ids. + #[must_use] + pub const fn new(bus: u8, dev: u8, fun: u8) -> Self { + Self { + bus, + dev, + fun, + reg: 0, + ext_reg: 0, + } + } + + /// Configure the **byte**-offset of the register to access. + #[must_use] + pub const fn with_register(&self, reg: u8) -> Self { + let mut addr = *self; + addr.reg = reg; + addr.ext_reg = 0; + addr + } + + /// Configure the **byte**-offset of the extended register to access. + #[must_use] + pub const fn with_extended_register(&self, ext_reg: u32) -> Self { + let mut addr = *self; + addr.reg = 0; + addr.ext_reg = ext_reg; + addr + } +} + +impl From for u64 { + fn from(value: PciIoAddress) -> Self { + unsafe { core::mem::transmute(value) } + } +} + +/// Trait implemented by all data types that can natively be read from a PCI device. +/// Note: Not all of them have to actually be supported by the hardware at hand. +pub trait PciIoUnit: Sized + Default {} +impl PciIoUnit for u8 {} +impl PciIoUnit for u16 {} +impl PciIoUnit for u32 {} +impl PciIoUnit for u64 {} + +#[allow(dead_code)] +enum PciIoMode { + Normal, + Fifo, + Fill, +} + +fn encode_io_mode_and_unit(mode: PciIoMode) -> PciRootBridgeIoProtocolWidth { + match (mode, core::mem::size_of::()) { + (PciIoMode::Normal, 1) => PciRootBridgeIoProtocolWidth::UINT8, + (PciIoMode::Normal, 2) => PciRootBridgeIoProtocolWidth::UINT16, + (PciIoMode::Normal, 4) => PciRootBridgeIoProtocolWidth::UINT32, + (PciIoMode::Normal, 8) => PciRootBridgeIoProtocolWidth::UINT64, + + (PciIoMode::Fifo, 1) => PciRootBridgeIoProtocolWidth::FIFO_UINT8, + (PciIoMode::Fifo, 2) => PciRootBridgeIoProtocolWidth::FIFO_UINT16, + (PciIoMode::Fifo, 4) => PciRootBridgeIoProtocolWidth::FIFO_UINT32, + (PciIoMode::Fifo, 8) => PciRootBridgeIoProtocolWidth::FIFO_UINT64, + + (PciIoMode::Fill, 1) => PciRootBridgeIoProtocolWidth::FILL_UINT8, + (PciIoMode::Fill, 2) => PciRootBridgeIoProtocolWidth::FILL_UINT16, + (PciIoMode::Fill, 4) => PciRootBridgeIoProtocolWidth::FILL_UINT32, + (PciIoMode::Fill, 8) => PciRootBridgeIoProtocolWidth::FILL_UINT64, + + _ => unreachable!("Illegal PCI IO-Mode / Unit combination"), + } +} diff --git a/uefi/src/proto/pci/root_bridge.rs b/uefi/src/proto/pci/root_bridge.rs new file mode 100644 index 000000000..8d396df67 --- /dev/null +++ b/uefi/src/proto/pci/root_bridge.rs @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! PCI Root Bridge protocol. + +use core::ptr; + +use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit}; +use crate::StatusExt; +use uefi_macros::unsafe_protocol; +use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol}; + +/// Protocol that provides access to the PCI Root Bridge I/O protocol. +/// +/// # UEFI Spec Description +/// Provides the basic Memory, I/O, PCI configuration, and DMA interfaces that are +/// used to abstract accesses to PCI controllers behind a PCI Root Bridge Controller. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(PciRootBridgeIoProtocol::GUID)] +pub struct PciRootBridgeIo(PciRootBridgeIoProtocol); + +impl PciRootBridgeIo { + /// Get the segment number where this PCI root bridge resides. + #[must_use] + pub const fn segment_nr(&self) -> u32 { + self.0.segment_number + } + + /// Access PCI I/O operations on this root bridge. + pub const fn pci(&mut self) -> PciIoAccessPci<'_> { + PciIoAccessPci { + proto: &mut self.0, + io_access: &mut self.0.pci, + } + } + + /// Flush all PCI posted write transactions from a PCI host bridge to system memory. + /// + /// # Errors + /// - [`crate::Status::DEVICE_ERROR`] The PCI posted write transactions were not flushed from the PCI host bridge + /// due to a hardware error. + pub fn flush(&mut self) -> crate::Result<()> { + unsafe { (self.0.flush)(&mut self.0).to_result() } + } + + // TODO: poll I/O + // TODO: mem I/O access + // TODO: io I/O access + // TODO: map & unmap & copy memory + // TODO: buffer management + // TODO: get/set attributes + // TODO: configuration / resource settings +} + +/// Struct for performing PCI I/O operations on a root bridge. +#[derive(Debug)] +pub struct PciIoAccessPci<'a> { + proto: *mut PciRootBridgeIoProtocol, + io_access: &'a mut PciRootBridgeIoAccess, +} + +impl PciIoAccessPci<'_> { + /// Reads a single value of type `U` from the specified PCI address. + /// + /// # Arguments + /// - `addr` - The PCI address to read from. + /// + /// # Returns + /// - The read value of type `U`. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The read request could not be completed due to a lack of resources. + pub fn read_one(&self, addr: PciIoAddress) -> crate::Result { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Normal); + let mut result = U::default(); + unsafe { + (self.io_access.read)( + self.proto, + width_mode, + addr.into(), + 1, + ptr::from_mut(&mut result).cast(), + ) + .to_result_with_val(|| result) + } + } + + /// Writes a single value of type `U` to the specified PCI address. + /// + /// # Arguments + /// - `addr` - The PCI address to write to. + /// - `data` - The value to write. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The write request could not be completed due to a lack of resources. + pub fn write_one(&self, addr: PciIoAddress, data: U) -> crate::Result<()> { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Normal); + unsafe { + (self.io_access.write)( + self.proto, + width_mode, + addr.into(), + 1, + ptr::from_ref(&data).cast(), + ) + .to_result() + } + } + + /// Reads multiple values from the specified PCI address range. + /// + /// # Arguments + /// - `addr` - The starting PCI address to read from. + /// - `data` - A mutable slice to store the read values. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The read operation could not be completed due to a lack of resources. + pub fn read(&self, addr: PciIoAddress, data: &mut [U]) -> crate::Result<()> { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Normal); + unsafe { + (self.io_access.read)( + self.proto, + width_mode, + addr.into(), + data.len(), + data.as_mut_ptr().cast(), + ) + .to_result() + } + } + + /// Writes multiple values to the specified PCI address range. + /// + /// # Arguments + /// - `addr` - The starting PCI address to write to. + /// - `data` - A slice containing the values to write. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The write operation could not be completed due to a lack of resources. + pub fn write(&self, addr: PciIoAddress, data: &[U]) -> crate::Result<()> { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Normal); + unsafe { + (self.io_access.write)( + self.proto, + width_mode, + addr.into(), + data.len(), + data.as_ptr().cast(), + ) + .to_result() + } + } + + /// Fills a PCI address range with the specified value. + /// + /// # Arguments + /// - `addr` - The starting PCI address to fill. + /// - `count` - The number of units to write. + /// - `data` - The value to fill the address range with. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The operation could not be completed due to a lack of resources. + pub fn fill_write( + &self, + addr: PciIoAddress, + count: usize, + data: U, + ) -> crate::Result<()> { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Fill); + unsafe { + (self.io_access.write)( + self.proto, + width_mode, + addr.into(), + count, + ptr::from_ref(&data).cast(), + ) + .to_result() + } + } + + /// Reads a sequence of values of type `U` from the specified PCI address by repeatedly accessing it. + /// + /// # Arguments + /// - `addr` - The PCI address to read from. + /// - `data` - A mutable slice to store the read values. + /// + /// # Behavior + /// This reads from the same memory region (starting at `addr` and ending at `addr + size_of::()`) repeatedly. + /// The resulting `data` buffer will contain the elements returned by reading the same address multiple times sequentially. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The read operation could not be completed due to a lack of resources. + pub fn fifo_read(&self, addr: PciIoAddress, data: &mut [U]) -> crate::Result<()> { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Fifo); + unsafe { + (self.io_access.read)( + self.proto, + width_mode, + addr.into(), + data.len(), + data.as_mut_ptr().cast(), + ) + .to_result() + } + } + + /// Writes a sequence of values of type `U` to the specified PCI address repeatedly. + /// + /// # Arguments + /// - `addr` - The PCI address to write to. + /// - `data` - A slice containing the values to write. + /// + /// # Behavior + /// This sequentially writes all elements within the given `data` buffer to the same memory region + /// (starting at `addr` and ending at `addr + size_of::()`) sequentially. + /// + /// # Errors + /// - [`crate::Status::INVALID_PARAMETER`] The requested width is invalid for this PCI root bridge. + /// - [`crate::Status::OUT_OF_RESOURCES`] The write operation could not be completed due to a lack of resources. + pub fn fifo_write(&self, addr: PciIoAddress, data: &[U]) -> crate::Result<()> { + let width_mode = encode_io_mode_and_unit::(super::PciIoMode::Fifo); + unsafe { + (self.io_access.write)( + self.proto, + width_mode, + addr.into(), + data.len(), + data.as_ptr().cast(), + ) + .to_result() + } + } +}