diff --git a/uefi/src/proto/pci/mod.rs b/uefi/src/proto/pci/mod.rs index 98cbaf7c2..352abd530 100644 --- a/uefi/src/proto/pci/mod.rs +++ b/uefi/src/proto/pci/mod.rs @@ -6,6 +6,8 @@ use core::cmp::Ordering; use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocolWidth; +pub mod page; +pub mod region; pub mod root_bridge; /// IO Address for PCI/register IO operations @@ -106,7 +108,7 @@ enum PciIoMode { } fn encode_io_mode_and_unit(mode: PciIoMode) -> PciRootBridgeIoProtocolWidth { - match (mode, core::mem::size_of::()) { + match (mode, size_of::()) { (PciIoMode::Normal, 1) => PciRootBridgeIoProtocolWidth::UINT8, (PciIoMode::Normal, 2) => PciRootBridgeIoProtocolWidth::UINT16, (PciIoMode::Normal, 4) => PciRootBridgeIoProtocolWidth::UINT32, diff --git a/uefi/src/proto/pci/page.rs b/uefi/src/proto/pci/page.rs new file mode 100644 index 000000000..483a20426 --- /dev/null +++ b/uefi/src/proto/pci/page.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Defines wrapper for pages allocated by PCI Root Bridge protocol. +use core::fmt::Debug; +use core::mem::{ManuallyDrop, MaybeUninit}; +use core::num::NonZeroUsize; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; +use log::trace; +use uefi_raw::Status; +use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocol; + +/// Smart pointer for wrapping owned pages allocated by PCI Root Bridge protocol. +/// +/// # Lifetime +/// `'p` is the lifetime for Protocol. +#[derive(Debug)] +pub struct PciPage<'p, T> { + base: NonNull, + pages: NonZeroUsize, + proto: &'p PciRootBridgeIoProtocol, +} + +impl<'p, T> PciPage<'p, MaybeUninit> { + /// Creates wrapper for pages allocated by PCI Root Bridge protocol. + #[must_use] + pub const fn new( + base: NonNull>, + pages: NonZeroUsize, + proto: &'p PciRootBridgeIoProtocol, + ) -> Self { + Self { base, pages, proto } + } + + /// 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) -> PciPage<'p, T> { + let initialized = PciPage { + base: self.base.cast(), + pages: self.pages, + proto: self.proto, + }; + let _ = ManuallyDrop::new(self); + initialized + } +} + +impl AsRef for PciPage<'_, T> { + fn as_ref(&self) -> &T { + unsafe { self.base.as_ref() } + } +} + +impl AsMut for PciPage<'_, T> { + fn as_mut(&mut self) -> &mut T { + unsafe { self.base.as_mut() } + } +} + +impl Deref for PciPage<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl DerefMut for PciPage<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +impl Drop for PciPage<'_, T> { + fn drop(&mut self) { + let status = unsafe { + (self.proto.free_buffer)(self.proto, self.pages.get(), self.base.as_ptr().cast()) + }; + match status { + Status::SUCCESS => { + trace!( + "Freed {} pages at 0x{:X}", + self.pages.get(), + self.base.as_ptr().addr() + ); + } + Status::INVALID_PARAMETER => { + panic!("PciBuffer was not created through valid protocol usage!") + } + _ => unreachable!(), + } + } +} diff --git a/uefi/src/proto/pci/region.rs b/uefi/src/proto/pci/region.rs new file mode 100644 index 000000000..71f292274 --- /dev/null +++ b/uefi/src/proto/pci/region.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Defines wrapper for a region mapped by PCI Root Bridge I/O protocol. +use core::ffi::c_void; +use core::fmt::Debug; +use core::marker::PhantomData; +use log::trace; +use uefi_raw::Status; +use uefi_raw::protocol::pci::root_bridge::PciRootBridgeIoProtocol; + +/// Represents a region of memory mapped by PCI Root Bridge I/O protocol. +/// The region will be unmapped automatically when it is dropped. +/// +/// # Lifetime +/// `'p` is the lifetime for Protocol. +/// `'r` is the lifetime for Mapped Region. +/// Protocol must outlive the mapped region +/// as unmap function can only be accessed through the protocol. +#[derive(Debug)] +pub struct PciMappedRegion<'p, 'r> +where + 'p: 'r, +{ + region: PciRegion, + _lifetime_holder: PhantomData<&'r ()>, + key: *const c_void, + proto: &'p PciRootBridgeIoProtocol, +} + +/// Represents a region of memory in PCI root bridge memory space. +/// 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 PciRegion { + /// Starting address of the memory region + pub device_address: u64, + + /// Byte length of the memory region. + pub length: usize, +} + +impl<'p, 'r> PciMappedRegion<'p, 'r> +where + 'p: 'r, +{ + #[allow(dead_code)] // TODO Implement Map function + pub(crate) fn new( + device_address: u64, + length: usize, + key: *const c_void, + _to_map: &'r T, + proto: &'p PciRootBridgeIoProtocol, + ) -> Self { + let end = device_address + length as u64; + trace!("Mapped new region [0x{device_address:X}..0x{end:X}]"); + Self { + region: PciRegion { + device_address, + length, + }, + _lifetime_holder: PhantomData, + key, + proto, + } + } + + /// Returns mapped address and length of a region. + /// + /// # Warning + /// **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) -> PciRegion { + self.region + } +} + +impl Drop for PciMappedRegion<'_, '_> { + fn drop(&mut self) { + let status = unsafe { (self.proto.unmap)(self.proto, self.key) }; + match status { + Status::SUCCESS => { + let end = self.region.device_address + self.region.length as u64; + trace!( + "Region [0x{:X}..0x{:X}] was unmapped", + self.region.device_address, end + ); + } + Status::INVALID_PARAMETER => { + panic!("This region was not mapped using PciRootBridgeIo::map"); + } + Status::DEVICE_ERROR => { + panic!("The data was not committed to the target system memory."); + } + _ => unreachable!(), + } + } +} + +impl PciRegion { + /// Creates a new region of memory with different length. + /// The new region must have shorter length to ensure + /// it won't contain invalid memory address. + #[must_use] + pub fn with_length(self, new_length: usize) -> Self { + assert!(new_length <= self.length); + Self { + device_address: self.device_address, + length: new_length, + } + } +} diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index b8aed7f05..ae795ea91 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -481,6 +481,13 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { cmd.arg("-device"); cmd.arg("ide-hd,drive=satadisk0,bus=ide.2,serial=AtaPassThru,model=AtaPassThru"); + // Sixth shared memory device used for testing Pci Root Bridge I/O's Copy function. + // This is to provide a PCI device with large enough memory. + cmd.arg("-device"); + cmd.arg("ivshmem-plain,memdev=hostmem,id=hostmem"); + cmd.arg("-object"); + cmd.arg("memory-backend-file,size=1M,share,mem-path=/dev/shm/ivshmem,id=hostmem"); + let qemu_monitor_pipe = Pipe::new(tmp_dir, "qemu-monitor")?; let serial_pipe = Pipe::new(tmp_dir, "serial")?;