Skip to content

Implement rest of PCI Root Bridge I/O protocol #1705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions uefi-raw/src/protocol/pci/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

pub mod resource;
pub mod root_bridge;
108 changes: 108 additions & 0 deletions uefi-raw/src/protocol/pci/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use bitflags::bitflags;

/// Descriptor for current PCI root bridge's configuration space.
/// Specification:
/// <https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/06_Device_Configuration/Device_Configuration.html#qword-address-space-descriptor>
#[repr(C, packed)]
#[derive(Debug)]
pub struct QWordAddressSpaceDescriptor {
pub tag: u8,
pub descriptor_length: u16,
pub resource_type: ResourceType,
pub flags: GeneralFlags,
pub type_flags: u8,
pub address_granularity: u64,
pub range_min: u64,
pub range_max: u64, // inclusive
pub translation_offset: u64,
pub address_length: u64,
}

newtype_enum! {
/// Indicates which type of resource this descriptor describes.
pub enum ResourceType: u8 => {
/// This resource describes range of memory.
MEMORY = 0,

/// This resource describes range of I/O ports.
IO = 1,

/// This resource describes range of Bus numbers.
BUS = 2,
}
}

bitflags! {
#[repr(transparent)]
#[derive(Debug, Copy, Clone)]
pub struct GeneralFlags: u8 {
/// Indicates maximum address is fixed.
const MAX_ADDRESS_FIXED = 0b1000;

/// Indicates minimum address is fixed.
const MIN_ADDRESS_FIXED = 0b0100;

/// Indicates if this bridge would subtract or positively decode address.
/// 1 This bridge subtractively decodes this address (top level bridges only)
/// 0 This bridge positively decodes this address
const DECODE_TYPE = 0b0010;
}
}

impl QWordAddressSpaceDescriptor {
/// Verifies if given descriptor is valid according to specification.
/// This also checks if all reserved bit fields which are supposed to be 0 are actually 0.
pub fn verify(&self) {
let tag = self.tag;
if tag != 0x8A {
panic!(
"Tag value for QWordAddressSpaceDescriptor should be 0x8A, not {}",
tag
);
}

let length = self.descriptor_length;
if self.descriptor_length != 0x2B {
panic!(
"Length value for QWordAddressSpaceDescriptor should be 0x2B, not {}",
length
);
}

if self.flags.bits() & 0b11110000 != 0 {
panic!("Reserved bits for GeneralFlags are 1")
}

let type_flags = self.type_flags;
match self.resource_type {
ResourceType::MEMORY => {
if type_flags & 0b11000000 != 0 {
panic!("Reserved bits for Memory Type Flags are 1");
}
}
ResourceType::IO => {
if type_flags & 0b11001100 != 0 {
panic!("Reserved bits for IO Type Flags are 1");
}
}
ResourceType::BUS => {
if type_flags != 0 {
panic!("Bus type flags should be 0, not {}", type_flags);
}
}
ResourceType(3..=191) => panic!("Invalid resource type: {}", self.resource_type.0),
ResourceType(192..) => {} // Hardware defined range
}

let min = self.range_min;
let max = self.range_max;
if max < min {
panic!(
"Address range is invalid. Max(0x{:X}) is smaller than Min(0x{:X}).",
max, min
);
}
}
}
37 changes: 37 additions & 0 deletions uefi-raw/src/protocol/pci/root_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::table::boot::{AllocateType, MemoryType};
use crate::{Handle, PhysicalAddress, Status};
use bitflags::bitflags;
use core::ffi::c_void;
use uguid::{Guid, guid};

Expand Down Expand Up @@ -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>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct PciRootBridgeIoProtocolAttribute: u64 {
const ISA_MOTHERBOARD_IO = 0x0001;
const ISA_IO = 0x0002;
const VGA_PALETTE_IO = 0x0004;
const VGA_MEMORY = 0x0008;
const VGA_IO = 0x0010;
const IDE_PRIMARY_IO = 0x0020;
const IDE_SECONDARY_IO = 0x0040;
const MEMORY_WRITE_COMBINE = 0x0080;
const MEMORY_CACHED = 0x0800;
const MEMORY_DISABLE = 0x1000;
const DUAL_ADDRESS_CYCLE = 0x8000;
const ISA_IO_16 = 0x10000;
const VGA_PALETTE_IO_16 = 0x20000;
const VGA_IO_16 = 0x40000;
}
}

#[derive(Debug)]
#[repr(C)]
pub struct PciRootBridgeIoAccess {
Expand Down Expand Up @@ -130,3 +154,16 @@ pub struct PciRootBridgeIoProtocol {
impl PciRootBridgeIoProtocol {
pub const GUID: Guid = guid!("2f707ebb-4a1a-11d4-9a38-0090273fc14d");
}

impl PciRootBridgeIoProtocolWidth {
#[must_use]
pub fn size(self) -> usize {
match self {
Self::UINT8 | Self::FIFO_UINT8 | Self::FILL_UINT8 => 1,
Self::UINT16 | Self::FIFO_UINT16 | Self::FILL_UINT16 => 2,
Self::UINT32 | Self::FIFO_UINT32 | Self::FILL_UINT32 => 4,
Self::UINT64 | Self::FIFO_UINT64 | Self::FILL_UINT64 => 8,
_ => unreachable!(),
}
}
}
8 changes: 7 additions & 1 deletion uefi-test-runner/src/proto/pci/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
pub mod root_bridge;

pub fn test() {
root_bridge::test();
root_bridge::test_io();
root_bridge::test_buffer();
root_bridge::test_mapping();
root_bridge::test_copy();
root_bridge::test_config();
root_bridge::test_attributes();
root_bridge::test_sizes();
}
188 changes: 182 additions & 6 deletions uefi-test-runner/src/proto/pci/root_bridge.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use core::mem;
use core::ptr;
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;
use uefi::proto::pci::root_bridge::{AttributeReport, PciRootBridgeIo};
use uefi_raw::protocol::pci::resource::QWordAddressSpaceDescriptor;
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;
const SATA_CTRL_SUBCLASS_CODE: u8 = 0x6;
const DEVICE_IVSHMEM: u16 = 0x1110;

const REG_SIZE: u8 = mem::size_of::<u32>() as u8;
const REG_SIZE: u8 = size_of::<u32>() as u8;

pub fn test() {
pub fn test_io() {
let pci_handles = uefi::boot::find_handles::<PciRootBridgeIo>().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::<PciRootBridgeIo>(pci_handle);

let pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);
for bus in 0..=255 {
for dev in 0..32 {
for fun in 0..8 {
Expand Down Expand Up @@ -67,6 +72,177 @@ pub fn test() {
assert!(sata_ctrl_cnt > 0);
}

pub fn test_buffer() {
let pci_handles = uefi::boot::find_handles::<PciRootBridgeIo>().unwrap();

for pci_handle in pci_handles {
let pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);
let mut buffer = pci_proto
.allocate_buffer::<[u8; 4096]>(
MemoryType::BOOT_SERVICES_DATA,
None,
PciRootBridgeIoProtocolAttribute::MEMORY_WRITE_COMBINE,
)
.unwrap();

let buffer = unsafe {
buffer.assume_init_mut().fill(0);
buffer.assume_init()
};

assert_eq!(buffer.as_ptr().addr() % 4096, 0);
}
}

pub fn test_mapping() {
let pci_handles = uefi::boot::find_handles::<PciRootBridgeIo>().unwrap();

for pci_handle in pci_handles {
let pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);

let mut buffer = pci_proto
.allocate_buffer::<[u8; 4096]>(
MemoryType::BOOT_SERVICES_DATA,
None,
PciRootBridgeIoProtocolAttribute::MEMORY_WRITE_COMBINE,
)
.unwrap();
let buffer = unsafe {
buffer.assume_init_mut().fill(0);
buffer.assume_init()
};

let mapped = pci_proto
.map(
PciRootBridgeIoProtocolOperation::BUS_MASTER_COMMON_BUFFER64,
buffer.as_ref(),
)
.unwrap();
if mapped.region().device_address == buffer.as_ptr().addr() as u64 {
info!("This PCI device uses identity mapping");
} else {
info!("This PCI device uses different mapping from CPU");
}
}
}

pub fn test_copy() {
let pci_handles = uefi::boot::find_handles::<PciRootBridgeIo>().unwrap();

for pci_handle in pci_handles {
let pci_proto = get_open_protocol::<PciRootBridgeIo>(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 pci_access = pci_proto.pci();
let Ok(reg0) = pci_access.read_one::<u32>(addr.with_register(0)) else {
continue;
};
if reg0 == 0xFFFFFFFF {
continue; // not a valid device
}

let vendor_id = (reg0 & 0xFFFF) as u16;
let device_id = (reg0 >> 16) as u16;

if vendor_id != RED_HAT_PCI_VENDOR_ID {
continue;
}
if device_id != DEVICE_IVSHMEM {
continue;
}

let header_type: u8 = pci_access.read_one(addr.with_register(0xE)).unwrap();
assert_eq!(header_type, 0);

let command_value = pci_access.read_one::<u16>(addr.with_register(4)).unwrap();
pci_access
.write_one::<u16>(addr.with_register(4), command_value & !0x11)
.unwrap();

let bar2 = pci_access
.read_one::<u64>(addr.with_register(0x18))
.unwrap(); // reads both bar2 and bar3 since it's 64bit
assert_eq!(bar2 & 0b1, 0);
assert_eq!((bar2 & 0b110) >> 1, 2); // make sure it's actually 64bit

let bar2_value = bar2 & 0xFFFFFFFFFFFFFFF0;
let bar2_size = {
pci_access
.write_one(addr.with_register(0x18), u32::MAX)
.unwrap();
let value: u32 = pci_access.read_one(addr.with_register(0x18)).unwrap();
let size = (!value).wrapping_add(1);
pci_access
.write_one(addr.with_register(0x18), bar2 as u32)
.unwrap();
size
};
assert!(bar2_size >= 0x1000 * 2);

pci_access
.write_one::<u16>(addr.with_register(4), command_value | 0b10)
.unwrap();

let (src, dst) = unsafe {
let src = ptr::slice_from_raw_parts_mut(
(bar2_value as usize) as *mut u32,
0x1000 / size_of::<u32>(),
)
.as_mut()
.unwrap();
let dst = ptr::slice_from_raw_parts(
(bar2_value as usize + 0x1000) as *mut u32,
0x1000 / size_of::<u32>(),
)
.as_ref()
.unwrap();
(src, dst)
};
src.fill(0xDEADBEEF);

pci_proto.copy::<u32>(dst, src).unwrap();

assert!(dst.iter().all(|&b| b == 0xDEADBEEF));
break;
}
}
}
}
}

pub fn test_config() {
let pci_handles = uefi::boot::find_handles::<PciRootBridgeIo>().unwrap();

for pci_handle in pci_handles {
let pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);
let Ok(configuration) = pci_proto.configuration() else {
continue;
};
info!("Found {} configurations", configuration.len());
}
}

pub fn test_attributes() {
let pci_handles = uefi::boot::find_handles::<PciRootBridgeIo>().unwrap();

for pci_handle in pci_handles {
let pci_proto = get_open_protocol::<PciRootBridgeIo>(pci_handle);
let AttributeReport { supported, .. } = pci_proto.get_attributes();

pci_proto
.set_attributes(PciRootBridgeIoProtocolAttribute::empty(), None)
.unwrap();
pci_proto.set_attributes(supported, None).unwrap();
}
}

pub fn test_sizes() {
assert_eq!(size_of::<QWordAddressSpaceDescriptor>(), 0x2E);
assert_eq!(size_of::<PciIoAddress>(), size_of::<u64>());
}

fn get_open_protocol<P: ProtocolPointer + ?Sized>(handle: Handle) -> ScopedProtocol<P> {
let open_opts = OpenProtocolParams {
handle,
Expand Down
Loading