diff --git a/uefi-raw/src/protocol/pci/root_bridge.rs b/uefi-raw/src/protocol/pci/root_bridge.rs index a58ffe6a6..bd1dc4eac 100644 --- a/uefi-raw/src/protocol/pci/root_bridge.rs +++ b/uefi-raw/src/protocol/pci/root_bridge.rs @@ -2,6 +2,7 @@ use crate::table::boot::{AllocateType, MemoryType}; use crate::{Handle, PhysicalAddress, Status, newtype_enum}; +use bitflags::bitflags; use core::ffi::c_void; use uguid::{Guid, guid}; @@ -37,6 +38,29 @@ newtype_enum! { } } +bitflags! { + /// Describes PCI I/O Protocol Attribute bitflags specified in UEFI specification. + /// https://uefi.org/specs/UEFI/2.10_A/14_Protocols_PCI_Bus_Support.html + #[repr(transparent)] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct PciRootBridgeIoProtocolAttribute: u64 { + const PCI_ATTRIBUTE_ISA_MOTHERBOARD_IO = 0x0001; + const PCI_ATTRIBUTE_ISA_IO = 0x0002; + const PCI_ATTRIBUTE_VGA_PALETTE_IO = 0x0004; + const PCI_ATTRIBUTE_VGA_MEMORY = 0x0008; + const PCI_ATTRIBUTE_VGA_IO = 0x0010; + const PCI_ATTRIBUTE_IDE_PRIMARY_IO = 0x0020; + const PCI_ATTRIBUTE_IDE_SECONDARY_IO = 0x0040; + const PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE = 0x0080; + const PCI_ATTRIBUTE_MEMORY_CACHED = 0x0800; + const PCI_ATTRIBUTE_MEMORY_DISABLE = 0x1000; + const PCI_ATTRIBUTE_DUAL_ADDRESS_CYCLE = 0x8000; + const PCI_ATTRIBUTE_ISA_IO_16 = 0x10000; + const PCI_ATTRIBUTE_VGA_PALETTE_IO_16 = 0x20000; + const PCI_ATTRIBUTE_VGA_IO_16 = 0x40000; + } +} + #[derive(Debug)] #[repr(C)] pub struct PciRootBridgeIoAccess { diff --git a/uefi-test-runner/src/proto/pci/mod.rs b/uefi-test-runner/src/proto/pci/mod.rs index 75ecbc0ad..f7c78e0e6 100644 --- a/uefi-test-runner/src/proto/pci/mod.rs +++ b/uefi-test-runner/src/proto/pci/mod.rs @@ -3,5 +3,7 @@ pub mod root_bridge; pub fn test() { - root_bridge::test(); + root_bridge::test_io(); + root_bridge::test_buffer(); + root_bridge::test_mapping(); } diff --git a/uefi-test-runner/src/proto/pci/root_bridge.rs b/uefi-test-runner/src/proto/pci/root_bridge.rs index 2e2ef1607..6933260c6 100644 --- a/uefi-test-runner/src/proto/pci/root_bridge.rs +++ b/uefi-test-runner/src/proto/pci/root_bridge.rs @@ -9,6 +9,10 @@ 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; +use uefi_raw::protocol::pci::root_bridge::{ + PciRootBridgeIoProtocolAttribute, PciRootBridgeIoProtocolOperation, +}; +use uefi_raw::table::boot::MemoryType; const RED_HAT_PCI_VENDOR_ID: u16 = 0x1AF4; const MASS_STORAGE_CTRL_CLASS_CODE: u8 = 0x1; @@ -16,7 +20,7 @@ const SATA_CTRL_SUBCLASS_CODE: u8 = 0x6; const REG_SIZE: u8 = size_of::() as u8; -pub fn test() { +pub fn test_io() { let pci_handles = uefi::boot::find_handles::().unwrap(); let mut sata_ctrl_cnt = 0; @@ -88,6 +92,70 @@ pub fn test() { } } +pub fn test_buffer() { + let pci_handles = uefi::boot::find_handles::().unwrap(); + + for pci_handle in pci_handles { + let pci_proto = get_open_protocol::(pci_handle); + + let buffer = pci_proto + .allocate_buffer::<[u8; 4096]>( + MemoryType::BOOT_SERVICES_DATA, + None, + PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE, + ) + .unwrap(); + let buffer = unsafe { + let buffer = buffer.assume_init(); + buffer.base_ptr().as_mut().unwrap().fill(0); + buffer + }; + assert_eq!(buffer.base_ptr().addr() % 4096, 0); + unsafe { + assert!(buffer.base_ptr().as_mut().unwrap().iter().all(|v| *v == 0)); + } + } +} + +pub fn test_mapping() { + let pci_handles = uefi::boot::find_handles::().unwrap(); + const BUFFER_SIZE: usize = 12342; + + for pci_handle in pci_handles { + let pci_proto = get_open_protocol::(pci_handle); + + let buffer = pci_proto + .allocate_buffer::<[u8; BUFFER_SIZE]>( + MemoryType::BOOT_SERVICES_DATA, + None, + PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE, + ) + .unwrap(); + let buffer = unsafe { + let buffer = buffer.assume_init(); + buffer.base_ptr().as_mut().unwrap().fill(0); + buffer + }; + + let mut mapped_regions = vec![]; + let mut offset = 0; + loop { + let (mapped, mapped_size) = pci_proto + .map( + PciRootBridgeIoProtocolOperation::BUS_MASTER_COMMON_BUFFER64, + &buffer, + offset, + ) + .unwrap(); + mapped_regions.push(mapped); + offset += mapped_size; + if offset == size_of::<[u8; BUFFER_SIZE]>() { + break; + } + } + } +} + fn get_open_protocol(handle: Handle) -> ScopedProtocol

{ let open_opts = OpenProtocolParams { handle, diff --git a/uefi/src/proto/pci/root_bridge/buffer.rs b/uefi/src/proto/pci/root_bridge/buffer.rs new file mode 100644 index 000000000..bdb84de7e --- /dev/null +++ b/uefi/src/proto/pci/root_bridge/buffer.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Defines wrapper for pages allocated by PCI Root Bridge protocol. + +use crate::StatusExt; +use core::cell::UnsafeCell; +use core::fmt::Debug; +use core::mem::{ManuallyDrop, MaybeUninit}; +use core::num::NonZeroUsize; +use core::ptr::NonNull; +use log::{error, trace}; +use uefi_raw::Status; +use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocol; +use uefi_raw::table::boot::PAGE_SIZE; + +/// Smart pointer for wrapping owned pages allocated by PCI Root Bridge protocol. +/// Value stored in this buffer maybe modified by a PCI device. +/// +/// # Lifetime +/// `'p` is the lifetime for Protocol. +/// +/// # Invariant +/// * Value stored in this memory cannot have a larger alignment requirement +/// than page size, which is 4096. +/// * Value stored in this memory cannot be larger than the buffer's size, which is 4096 * `pages` +#[derive(Debug)] +pub struct PciBuffer<'p, T> { + pub(super) base: NonNull>, + pub(super) pages: NonZeroUsize, + pub(super) proto: &'p PciRootBridgeIoProtocol, +} + +impl<'p, T> PciBuffer<'p, MaybeUninit> { + /// Assumes the contents of this buffer have been initialized. + /// + /// # Safety + /// Callers of this function must guarantee that the value stored is valid. + #[must_use] + pub const unsafe fn assume_init(self) -> PciBuffer<'p, T> { + let initialized = PciBuffer { + base: self.base.cast(), + pages: self.pages, + proto: self.proto, + }; + let _ = ManuallyDrop::new(self); + initialized + } +} + +impl<'p, T> PciBuffer<'p, T> { + /// Returns the base pointer of this buffer + #[must_use] + pub const fn base_ptr(&self) -> *mut T { + self.base.as_ptr().cast() + } + + /// Returns the number of pages this buffer uses + #[must_use] + pub const fn pages(&self) -> NonZeroUsize { + self.pages + } + + /// Returns the size of this buffer in bytes + #[must_use] + pub const fn bytes_size(&self) -> NonZeroUsize { + self.pages + .checked_mul(NonZeroUsize::new(PAGE_SIZE).unwrap()) + .expect("Memory size Overflow") + } + + /// Frees underlying memory of this buffer. + /// It is recommended to use this over drop implementation. + pub fn free(self) -> crate::Result { + self.free_inner() + } + + fn free_inner(&self) -> crate::Result { + unsafe { (self.proto.free_buffer)(self.proto, self.pages.get(), self.base.as_ptr().cast()) } + .to_result_with_val(|| { + trace!( + "Freed {} pages at 0x{:X}", + self.pages, + self.base.as_ptr().addr() + ) + }) + } +} + +impl Drop for PciBuffer<'_, T> { + fn drop(&mut self) { + let Err(status) = self.free_inner() else { + return; + }; + match status.status() { + Status::SUCCESS => {} + Status::INVALID_PARAMETER => { + error!("PciBuffer was not created through valid protocol usage!") + } + etc => { + error!("Unexpected error occurred when freeing memory: {:?}", etc); + } + } + } +} diff --git a/uefi/src/proto/pci/root_bridge.rs b/uefi/src/proto/pci/root_bridge/mod.rs similarity index 66% rename from uefi/src/proto/pci/root_bridge.rs rename to uefi/src/proto/pci/root_bridge/mod.rs index 3dd5a71f2..42c255afb 100644 --- a/uefi/src/proto/pci/root_bridge.rs +++ b/uefi/src/proto/pci/root_bridge/mod.rs @@ -3,19 +3,28 @@ //! PCI Root Bridge protocol. use super::{PciIoAddress, PciIoUnit, encode_io_mode_and_unit}; -use crate::StatusExt; #[cfg(feature = "alloc")] use crate::proto::pci::configuration::QwordAddressSpaceDescriptor; +use crate::proto::pci::root_bridge::buffer::PciBuffer; +use crate::proto::pci::root_bridge::region::PciMappedRegion; +use crate::{Status, StatusExt}; #[cfg(feature = "alloc")] use alloc::vec::Vec; -#[cfg(feature = "alloc")] use core::ffi::c_void; +use core::mem::MaybeUninit; +use core::num::NonZeroUsize; use core::ptr; +use core::ptr::{NonNull, null_mut}; +use log::debug; use uefi_macros::unsafe_protocol; -use uefi_raw::protocol::pci::root_bridge::{PciRootBridgeIoAccess, PciRootBridgeIoProtocol}; +use uefi_raw::protocol::pci::root_bridge::{ + PciRootBridgeIoAccess, PciRootBridgeIoProtocol, PciRootBridgeIoProtocolAttribute, + PciRootBridgeIoProtocolOperation, +}; +use uefi_raw::table::boot::{AllocateType, MemoryType, PAGE_SIZE}; -#[cfg(doc)] -use crate::Status; +pub mod buffer; +pub mod region; /// Protocol that provides access to the PCI Root Bridge I/O protocol. /// @@ -51,11 +60,150 @@ impl PciRootBridgeIo { unsafe { (self.0.flush)(&mut self.0).to_result() } } + /// Allocates pages suitable for communicating with PCI devices. + /// + /// # Errors + /// - [`Status::INVALID_PARAMETER`] MemoryType is invalid. + /// - [`Status::UNSUPPORTED`] Attributes is unsupported. The only legal attribute bits are: + /// - [`PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE`] + /// - [`PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_MEMORY_CACHED`] + /// - [`PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_DUAL_ADDRESS_CYCLE`] + /// - [`Status::OUT_OF_RESOURCES`] The memory pages could not be allocated. + pub fn allocate_buffer( + &self, + memory_type: MemoryType, + pages: Option, + attributes: PciRootBridgeIoProtocolAttribute, + ) -> crate::Result>> { + let original_alignment = align_of::(); + // TODO switch to const block once it lands on stable. These checks should be done in compile time. + assert_ne!(original_alignment, 0); + assert!(PAGE_SIZE >= original_alignment); + assert_eq!(PAGE_SIZE % original_alignment, 0); + + let alignment = PAGE_SIZE; + + let pages = if let Some(pages) = pages { + pages + } else { + let size = size_of::(); + assert_ne!(size, 0); + + NonZeroUsize::new(size.div_ceil(alignment)).unwrap() + }; + let size = size_of::(); + // TODO switch to const block once it lands on stable. + assert!(pages.get() * PAGE_SIZE >= size); + + let mut address: *mut T = null_mut(); + let status = unsafe { + (self.0.allocate_buffer)( + &self.0, + AllocateType(0), + memory_type, + pages.get(), + ptr::from_mut(&mut address).cast(), + attributes.bits(), + ) + }; + + match status { + Status::SUCCESS => { + let base = NonNull::new(address.cast()).unwrap(); + debug!("Allocated {} pages at 0x{:X}", pages.get(), address.addr()); + Ok(PciBuffer { + base, + pages, + proto: &self.0, + }) + } + error + @ (Status::INVALID_PARAMETER | Status::UNSUPPORTED | Status::OUT_OF_RESOURCES) => { + Err(error.into()) + } + _ => unreachable!(), + } + } + + /// Allocates pages suitable for communicating with PCI devices and initialize it right away. + /// + /// # Errors + /// Same as [`Self::allocate_buffer`] + /// - [`Status::INVALID_PARAMETER`] MemoryType is invalid. + /// - [`Status::UNSUPPORTED`] Attributes is unsupported. The only legal attribute bits are: + /// - [`PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE`] + /// - [`PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_MEMORY_CACHED`] + /// - [`PciRootBridgeIoProtocolAttribute::PCI_ATTRIBUTE_DUAL_ADDRESS_CYCLE`] + /// - [`Status::OUT_OF_RESOURCES`] The memory pages could not be allocated. + pub fn allocate_buffer_init( + &self, + memory_type: MemoryType, + value: T, + attributes: PciRootBridgeIoProtocolAttribute, + ) -> crate::Result> { + let buffer = self.allocate_buffer(memory_type, None, attributes)?; + unsafe { + buffer.base_ptr().write(MaybeUninit::new(value)); + Ok(buffer.assume_init()) + } + } + + /// Map the given buffer into a PCI Controller-specific address + /// so that devices can read system memory through it. + /// This will not map all bytes in pages allocated in the buffer. Instead it will only + /// map bytse actually occupied by type `T`. + /// + /// # Arguments + /// - `operation` - Indicates if bus master is going to read, write, or do both to the buffer. + /// - `to_map` - Buffer to map. + /// - 'offset' - Offset to add to the base address of buffer. + /// + /// # Returns + /// A mapped region and mapped bytes. It can map up to one DMA operation. Meaning large values + /// will require multiple calls to this function. + pub fn map<'p: 'r, 'r, T>( + &'p self, + operation: PciRootBridgeIoProtocolOperation, + to_map: &'r PciBuffer<'p, T>, + offset: usize, + ) -> crate::Result<(PciMappedRegion<'p, 'r, T>, usize)> { + let host_address = unsafe { + if to_map.pages.get() * PAGE_SIZE < offset { + return Err(Status::INVALID_PARAMETER.into()); + } + to_map.base_ptr().byte_offset(offset as isize) + }; + let mut bytes = size_of::() - offset; + let mut device_address = 0usize; + let mut mapping: *mut c_void = null_mut(); + + let status = unsafe { + (self.0.map)( + &self.0, + operation, + host_address.cast(), + ptr::from_mut(&mut bytes), + ptr::from_mut(&mut device_address).cast(), + ptr::from_mut(&mut mapping), + ) + }; + + status.to_result_with_val(|| { + let region = PciMappedRegion { + host: to_map, + length: 0, + device_address, + key: mapping, + proto: &self.0, + }; + (region, bytes) + }) + } + // TODO: poll I/O // TODO: mem I/O access // TODO: io I/O access - // TODO: map & unmap & copy memory - // TODO: buffer management + // TODO: copy memory // TODO: get/set attributes /// Retrieves the current resource settings of this PCI root bridge in the form of a set of ACPI resource descriptors. diff --git a/uefi/src/proto/pci/root_bridge/region.rs b/uefi/src/proto/pci/root_bridge/region.rs new file mode 100644 index 000000000..1d342d8b1 --- /dev/null +++ b/uefi/src/proto/pci/root_bridge/region.rs @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Defines wrapper for a region mapped by PCI Root Bridge I/O protocol. + +use crate::StatusExt; +use crate::proto::pci::root_bridge::buffer::PciBuffer; +use core::ffi::c_void; +use core::fmt::Debug; +use core::mem::ManuallyDrop; +use core::ptr; +use log::{error, trace}; +use uefi_raw::Status; +use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocol; + +/// Represents a region of memory mapped and made visible to devices +/// by PCI Root Bridge I/O protocol. +/// The region will be unmapped automatically when it is dropped. +/// +/// # Lifetime +/// `'p` is the lifetime for Protocol. +/// Protocol must be available for the entire lifetime of this struct +/// as it provides its unmap function. +/// +/// # Invariant +/// Value stored in its internal buffer cannot have a larger alignment requirement than page size, +/// which is 4096. +/// +/// # Safety +/// Value stored in its internal buffer cannot be larger than the buffer's size, +/// which is 4096 * `pages` +#[derive(Debug)] +pub struct PciMappedRegion<'p: 'r, 'r, T> { + pub(super) host: &'r PciBuffer<'p, T>, + /// Bytes size of the mapped region. + pub(super) length: usize, + pub(super) device_address: usize, + pub(super) key: *const c_void, + pub(super) proto: &'p PciRootBridgeIoProtocol, +} + +/// Represents a region of memory that a PCI device can use. +/// CPU cannot use address in this struct to deference memory. +/// This is effectively the same as rust's slice type. +/// This type only exists to prevent users from accidentally dereferencing it. +#[derive(Debug, Copy, Clone)] +pub struct DeviceRegion { + /// Starting address of the memory region + pub device_address: usize, + + /// Byte length of the memory region. + pub length: usize, +} + +impl<'p: 'r, 'r, T> PciMappedRegion<'p, 'r, T> { + /// Returns access to the underlying buffer. + #[must_use] + pub const fn host(&'r self) -> &'r PciBuffer<'p, T> { + self.host + } + + /// Returns mapped address and length of a region. + /// + /// # Safety + /// **Returned address cannot be used to reference memory from CPU!** + /// **Do not cast it back to pointer or reference** + #[must_use] + pub const fn region(&self) -> DeviceRegion { + DeviceRegion { + device_address: self.device_address, + length: self.length, + } + } + + /// Unmaps underlying memory region. + /// It is recommended to use this over its Drop implementation, which will only log an error + /// if unmapping fails. + pub fn unmap(self) -> crate::Result> { + let region = ManuallyDrop::new(self); + match region.unmap_inner() { + // SAFETY: + // This technically creates an alias to its underlying ExclusivePtr value, + // but we don't do any read/writes through it. + // And the original is discarded right away. + Ok(_) => unsafe { Ok(ptr::read(region.host)) }, + Err(e) => Err(e), + } + } + + fn unmap_inner(&self) -> crate::Result { + unsafe { (self.proto.unmap)(self.proto, self.key) }.to_result_with_val(|| { + let host_start = self.host.base_ptr().addr(); + let host_end = host_start + self.length; + let device_start = self.device_address; + let device_end = device_start + self.length; + trace!( + "Region [Host 0x{:X}..0x{:X}] -> [Device 0x{:}..0x{:X}] was unmapped", + host_start, host_end, device_start, device_end + ); + }) + } +} + +impl<'p: 'r, 'r, T> Drop for PciMappedRegion<'p, 'r, T> { + fn drop(&mut self) { + let Err(status) = self.unmap_inner() else { + return; + }; + match status.status() { + // Effectively unreachable path + Status::SUCCESS => {} + + Status::INVALID_PARAMETER => { + error!("This region was not mapped using PciRootBridgeIo::map"); + } + Status::DEVICE_ERROR => { + error!("The data was not committed to the target system memory."); + } + etc => { + error!( + "Unexpected error occurred when unmapping device memory: {:?}", + etc + ); + } + } + } +} + +impl DeviceRegion { + /// Changes length of a given region. + /// The new region must have a shorter length to ensure + /// it won't contain invalid memory address. + #[must_use] + pub fn with_length(mut self, new_length: usize) -> Self { + assert!(new_length <= self.length); + self.length = new_length; + self + } +} diff --git a/xtask/src/check_raw.rs b/xtask/src/check_raw.rs index ec93182c1..3ed3b0ed9 100644 --- a/xtask/src/check_raw.rs +++ b/xtask/src/check_raw.rs @@ -389,6 +389,9 @@ fn check_macro(item: &ItemMacro, src: &Path) -> Result<(), Error> { let attrs = parse_attrs(&attrs.0, src)?; let reprs = get_reprs(&attrs); + if reprs.is_empty() { + return Err(Error::new(ErrorKind::MissingRepr, src, mac)); + } let allowed_reprs: &[&[Repr]] = &[&[Repr::Transparent]]; if !allowed_reprs.contains(&reprs.as_slice()) { return Err(Error::new(ErrorKind::ForbiddenRepr(reprs), src, mac));