use crate::device::{
bus::{Request, RequestSize, SingleThreadedBusDevice},
register_set::{RegisterSet, RegisterSetBuilder},
};
use super::{
constants::config_space::{
self, command, header_type, mask::CAPABILITIES_POINTER as CAPABILITY_POINTER_MASK, offset,
status, MAX_BARS,
},
traits::RequestKind,
};
const INITIAL_CAPABILITY_OFFSET: u8 = 0x40;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BarInfo {
pub size: u32,
pub kind: RequestKind,
}
impl BarInfo {
const fn new(size: u32, kind: RequestKind) -> Self {
Self { size, kind }
}
}
#[derive(Debug, Clone)]
pub struct ConfigSpaceBuilder {
reg_builder: RegisterSetBuilder<{ config_space::SIZE }>,
multifunction: bool,
revision: u8,
interrupt_pin: u8,
interrupt_line: u8,
status: u16,
bars: [Option<BarInfo>; MAX_BARS],
next_capability_offset: u8,
last_capability_pointer: u8,
has_custom_registers: bool,
}
impl ConfigSpaceBuilder {
#[must_use]
pub fn new(vendor: u16, device: u16) -> Self {
let mut reg_builder = RegisterSetBuilder::<{ config_space::SIZE }>::new();
reg_builder
.u16_le_ro_at(offset::VENDOR, vendor)
.u16_le_ro_at(offset::DEVICE, device)
.u16_le_at(offset::COMMAND, 0, command::WRITABLE_BITS)
.u8_rw_at(offset::CACHE_LINE_SIZE, 0)
.u8_rw_at(offset::LATENCY_TIMER, 0)
.u8_ro_at(offset::BIST, 0)
.u32_le_ro_at(offset::ROM_BAR, 0)
.u8_ro_at(offset::MIN_GNT, 0)
.u8_ro_at(offset::MAX_LAT, 0);
for i in 0..MAX_BARS {
reg_builder.u32_le_ro_at(offset::BAR_0 + i * 4, 0);
}
Self {
reg_builder,
multifunction: false,
revision: 0,
interrupt_pin: 0,
interrupt_line: 255,
status: 0,
bars: [None; MAX_BARS],
next_capability_offset: INITIAL_CAPABILITY_OFFSET,
last_capability_pointer: offset::CAPABILITIES_POINTER.try_into().unwrap(),
has_custom_registers: false,
}
}
#[must_use]
pub fn class(mut self, class: u8, subclass: u8, prog_if: u8) -> Self {
self.reg_builder
.u8_ro_at(offset::CLASS, class)
.u8_ro_at(offset::SUBCLASS, subclass)
.u8_ro_at(offset::PROG_IF, prog_if);
self
}
#[must_use]
#[allow(unused)]
pub const fn revision(mut self, revision: u8) -> Self {
self.revision = revision;
self
}
#[must_use]
#[allow(unused)]
pub fn subsystem(mut self, subsystem_vendor_id: u16, subsystem_id: u16) -> Self {
self.reg_builder
.u16_le_ro_at(offset::SUBSYSTEM_VENDOR_ID, subsystem_vendor_id)
.u16_le_ro_at(offset::SUBSYSTEM_ID, subsystem_id);
self
}
#[must_use]
#[allow(unused)]
pub const fn multifunction(mut self) -> Self {
self.multifunction = true;
self
}
#[must_use]
#[allow(unused)]
pub const fn interrupt_pin(mut self, irq_pin: u8) -> Self {
self.interrupt_pin = irq_pin;
self
}
#[must_use]
#[allow(unused)]
pub const fn interrupt_line(mut self, irq_line: u8) -> Self {
self.interrupt_line = irq_line;
self
}
#[allow(unused)]
pub fn custom_registers<F>(mut self, custom_regs_fn: F) -> Self
where
F: FnOnce(&mut RegisterSetBuilder<{ config_space::SIZE }>),
{
assert_eq!(
self.next_capability_offset, INITIAL_CAPABILITY_OFFSET,
"Cannot add custom registers when PCI capabilities have been added"
);
custom_regs_fn(&mut self.reg_builder);
self.has_custom_registers = true;
self
}
#[must_use]
pub fn mem32_nonprefetchable_bar(mut self, index: u8, size: u32) -> Self {
let index: usize = index.into();
assert!(index < MAX_BARS);
assert_eq!(self.bars[index], None);
assert!(size.is_power_of_two());
assert!(size >= 16);
self.reg_builder
.u32_le_at(config_space::offset::BAR_0 + index * 4, 0, !(size - 1));
self.bars[index] = Some(BarInfo::new(size, RequestKind::Memory));
self
}
#[must_use]
pub fn capability<const CAP_SIZE: usize>(
mut self,
capability_id: u8,
regs: &RegisterSet<CAP_SIZE>,
) -> Self {
let offset = self.next_capability_offset;
assert_eq!(offset & !CAPABILITY_POINTER_MASK, 0);
assert!(
!self.has_custom_registers,
"Custom registers interact poorly with our automated placement of capabilities"
);
let header_size = 2;
let next_offset = usize::from(offset) + header_size + CAP_SIZE;
assert!(next_offset <= u8::MAX.into());
self.next_capability_offset =
((next_offset + !usize::from(CAPABILITY_POINTER_MASK)) as u8) & CAPABILITY_POINTER_MASK;
self.reg_builder
.u8_ro_at(self.last_capability_pointer.into(), offset)
.u8_ro_at(offset.into(), capability_id)
.register_set_at(usize::from(offset) + header_size, regs);
self.last_capability_pointer = offset + 1;
self
}
fn has_bar(&self, bar_no: u8, required_kind: RequestKind, minimum_size: u32) -> bool {
if let Some(BarInfo { size, kind }) = self.bars[usize::from(bar_no)] {
kind == required_kind && size >= minimum_size
} else {
false
}
}
#[must_use]
pub fn msix_capability(
self,
msix_count: u16,
table_bar_no: u8,
table_bar_offset: u32,
pba_bar_no: u8,
pba_bar_offset: u32,
) -> Self {
assert!(msix_count > 0);
assert!(msix_count <= config_space::msix::MAX_VECTORS);
const MSIX_TABLE_ENTRY_SIZE: u32 = 16;
assert_eq!(table_bar_offset & 0x3, 0);
assert!(
self.has_bar(
table_bar_no,
RequestKind::Memory,
table_bar_offset + u32::from(msix_count) * MSIX_TABLE_ENTRY_SIZE
),
"MSI-X capability points to mismatching BAR for the MSI-X table"
);
assert_eq!(pba_bar_offset & 0x3, 0);
let pba_bytes = u32::from(msix_count).div_ceil(8);
assert!(self.has_bar(
pba_bar_no,
RequestKind::Memory,
pba_bar_offset + pba_bytes.div_ceil(8)
));
let msix_cap: RegisterSet<10> = RegisterSetBuilder::<10>::new()
.u16_le_at(
0,
msix_count - 1,
config_space::msix::control::WRITABLE_BITS,
)
.u32_le_ro_at(2, table_bar_offset | u32::from(table_bar_no))
.u32_le_rw_at(6, pba_bar_offset | u32::from(pba_bar_no))
.into();
self.capability(config_space::capability_id::MSI_X, &msix_cap)
}
#[must_use]
pub fn config_space(mut self) -> ConfigSpace {
ConfigSpace {
bars: self.bars,
config_space: self
.reg_builder
.u8_rw_at(offset::IRQ_LINE, self.interrupt_line)
.u8_ro_at(offset::IRQ_PIN, self.interrupt_pin)
.u16_le_ro_at(
offset::STATUS,
self.status
| if self.next_capability_offset == INITIAL_CAPABILITY_OFFSET {
0
} else {
status::CAPABILITIES
},
)
.u8_ro_at(offset::REVISION, self.revision)
.u8_ro_at(
offset::HEADER_TYPE,
header_type::TYPE_00
| if self.multifunction {
header_type::MULTIFUNCTION
} else {
0
},
)
.u8_ro_at(self.last_capability_pointer.into(), 0)
.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct ConfigSpace {
config_space: RegisterSet<{ config_space::SIZE }>,
bars: [Option<BarInfo>; MAX_BARS],
}
struct CapabilityIterator<'a> {
config_space: &'a ConfigSpace,
cap_offset: u8,
}
impl Iterator for CapabilityIterator<'_> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
if self.cap_offset == 0 {
return None;
}
let cap_ptr =
self.config_space
.read(Request::new(self.cap_offset.into(), RequestSize::Size1)) as u8
& CAPABILITY_POINTER_MASK;
if cap_ptr == 0 {
self.cap_offset = 0;
None
} else {
self.cap_offset = cap_ptr + 1;
Some(cap_ptr)
}
}
}
impl ConfigSpace {
#[must_use]
pub fn read(&self, req: Request) -> u64 {
self.config_space.read(req)
}
#[allow(unused)]
pub fn iter_capability_offsets(&self) -> impl Iterator<Item = u8> + '_ {
CapabilityIterator {
config_space: self,
cap_offset: config_space::offset::CAPABILITIES_POINTER
.try_into()
.unwrap(),
}
}
pub fn bar(&self, bar_no: u8) -> Option<BarInfo> {
self.bars.get(usize::from(bar_no)).and_then(|&b| b)
}
}
impl SingleThreadedBusDevice for ConfigSpace {
fn size(&self) -> u64 {
self.config_space.size()
}
fn read(&mut self, req: Request) -> u64 {
self.config_space.read(req)
}
fn write(&mut self, req: Request, value: u64) {
self.config_space.write(req, value);
}
}
#[cfg(test)]
mod tests {
use crate::device::bus::RequestSize;
use super::*;
#[test]
fn device_vendor_id_are_set() {
let example_vendor_id = 0xDEAD;
let example_device_id = 0xBEEF;
let cfg_space: ConfigSpace =
ConfigSpaceBuilder::new(example_vendor_id, example_device_id).config_space();
for (offset, value) in [
(offset::VENDOR, example_vendor_id),
(offset::DEVICE, example_device_id),
] {
assert_eq!(
cfg_space.read(Request::new(offset as u64, RequestSize::Size2)),
u64::from(value)
);
}
}
#[test]
fn class_codes_are_set() {
let example_class = 0xDE;
let example_subclass = 0xAD;
let example_prog_if = 0x11;
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.class(example_class, example_subclass, example_prog_if)
.config_space();
for (offset, value) in [
(offset::CLASS, example_class),
(offset::SUBCLASS, example_subclass),
(offset::PROG_IF, example_prog_if),
] {
assert_eq!(
cfg_space.read(Request::new(offset as u64, RequestSize::Size1)),
u64::from(value)
);
}
}
#[test]
fn subsystem_ids_are_set() {
let example_subsystem_vendor = 0xDEAD;
let example_subsystem = 0xBEEF;
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.subsystem(example_subsystem_vendor, example_subsystem)
.config_space();
for (offset, value) in [
(offset::SUBSYSTEM_VENDOR_ID, example_subsystem_vendor),
(offset::SUBSYSTEM_ID, example_subsystem),
] {
assert_eq!(
cfg_space.read(Request::new(offset as u64, RequestSize::Size2)),
u64::from(value)
);
}
}
#[test]
fn create_single_function_device_by_default() {
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0).config_space();
assert_eq!(
cfg_space.read(Request::new(offset::HEADER_TYPE as u64, RequestSize::Size1))
& u64::from(header_type::MULTIFUNCTION),
0
);
}
#[test]
fn can_create_multifunction_device() {
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0).multifunction().config_space();
assert_eq!(
cfg_space.read(Request::new(offset::HEADER_TYPE as u64, RequestSize::Size1))
& u64::from(header_type::MULTIFUNCTION),
u64::from(header_type::MULTIFUNCTION)
);
}
#[test]
fn can_add_custom_registers() {
let example_offset = 0xC0;
let example_value = 0xAA;
let mut cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.custom_registers(|r| {
r.u8_rw_at(example_offset, example_value);
})
.config_space();
let req = Request::new(example_offset as u64, RequestSize::Size1);
assert_eq!(cfg_space.read(req), u64::from(example_value));
cfg_space.write(req, 0xBB);
assert_eq!(cfg_space.read(req), 0xBB);
}
#[test]
fn revision_defaults_to_zero() {
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0).config_space();
assert_eq!(
cfg_space.read(Request::new(offset::REVISION as u64, RequestSize::Size1)),
0
);
}
#[test]
fn can_set_revision() {
let example_revision = 0x12;
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.revision(example_revision)
.config_space();
assert_eq!(
cfg_space.read(Request::new(offset::REVISION as u64, RequestSize::Size1)),
u64::from(example_revision)
);
}
#[test]
fn expose_no_capabilities_by_default() {
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0).config_space();
assert_eq!(
cfg_space.read(Request::new(offset::STATUS as u64, RequestSize::Size2))
& u64::from(status::CAPABILITIES),
0
);
assert_eq!(
cfg_space.read(Request::new(
offset::CAPABILITIES_POINTER as u64,
RequestSize::Size1
)),
0
);
}
#[test]
fn can_add_one_capability() {
let example_id = 0x12;
let example_capability: RegisterSet<2> = RegisterSetBuilder::<2>::new()
.u16_le_ro_at(0, 0xAABB)
.into();
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.capability(example_id, &example_capability)
.config_space();
assert_eq!(
cfg_space.read(Request::new(offset::STATUS as u64, RequestSize::Size2))
& u64::from(status::CAPABILITIES),
u64::from(status::CAPABILITIES)
);
let cap_ptr = cfg_space.read(Request::new(
offset::CAPABILITIES_POINTER as u64,
RequestSize::Size1,
)) & u64::from(CAPABILITY_POINTER_MASK);
assert_eq!(
cfg_space.read(Request::new(cap_ptr, RequestSize::Size1)),
u64::from(example_id)
);
assert_eq!(
cfg_space.read(Request::new(cap_ptr + 1, RequestSize::Size1)),
0
);
assert_eq!(
cfg_space.read(Request::new(cap_ptr + 2, RequestSize::Size2)),
0xAABB
);
}
#[test]
fn capabilities_are_correctly_chained() {
let example_id_1 = 0x12;
let example_capability_1: RegisterSet<4> = RegisterSetBuilder::<4>::new()
.u32_le_ro_at(0, 0xAABBCCDD)
.into();
let example_id_2 = 0x23;
let example_capability_2: RegisterSet<2> = RegisterSetBuilder::<2>::new()
.u16_le_ro_at(0, 0x1122)
.into();
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.capability(example_id_1, &example_capability_1)
.capability(example_id_2, &example_capability_2)
.config_space();
let cap_1_ptr = cfg_space.read(Request::new(
offset::CAPABILITIES_POINTER as u64,
RequestSize::Size1,
)) & u64::from(CAPABILITY_POINTER_MASK);
let cap_2_ptr = cfg_space.read(Request::new(cap_1_ptr + 1, RequestSize::Size1))
& u64::from(CAPABILITY_POINTER_MASK);
assert_eq!(
cfg_space.read(Request::new(cap_2_ptr, RequestSize::Size1)),
u64::from(example_id_2)
);
assert_eq!(
cfg_space.read(Request::new(cap_2_ptr + 1, RequestSize::Size1)),
0
);
assert_eq!(
cfg_space.read(Request::new(cap_2_ptr + 2, RequestSize::Size2)),
0x1122
);
}
#[test]
fn bars_sizing_works() {
const BAR_SIZE: u32 = 0x1000;
let mut cfg_space = ConfigSpaceBuilder::new(0, 0)
.mem32_nonprefetchable_bar(1, BAR_SIZE)
.config_space();
cfg_space.write(
Request::new(offset::BAR_1 as u64, RequestSize::Size4),
0xFFFF_FFFF,
);
let bar_val = cfg_space.read(Request::new(offset::BAR_1 as u64, RequestSize::Size4));
assert_eq!(bar_val, 0xFFFF_F000);
}
#[test]
#[should_panic]
fn can_only_refer_to_existing_bars_in_msix_cap() {
let _ = ConfigSpaceBuilder::new(0, 0)
.msix_capability(16, 1, 0x1234_5670, 2, 0x2345_6780)
.config_space();
}
#[test]
fn can_create_msix_capability() {
let cfg_space = ConfigSpaceBuilder::new(0, 0)
.mem32_nonprefetchable_bar(1, 0x8000_0000)
.mem32_nonprefetchable_bar(2, 0x8000_0000)
.msix_capability(16, 1, 0x1234_5670, 2, 0x2345_6780)
.config_space();
let msix_ptr = cfg_space.read(Request::new(
offset::CAPABILITIES_POINTER as u64,
RequestSize::Size1,
)) & u64::from(CAPABILITY_POINTER_MASK);
assert_eq!(
cfg_space.read(Request::new(msix_ptr, RequestSize::Size4)),
0x0f0011
);
assert_eq!(
cfg_space.read(Request::new(msix_ptr + 4, RequestSize::Size4)),
0x1234_5671
);
assert_eq!(
cfg_space.read(Request::new(msix_ptr + 8, RequestSize::Size4)),
0x2345_6782
);
}
#[test]
fn capability_iterator_works() {
let no_cap_cfg_space = ConfigSpaceBuilder::new(0, 0).config_space();
assert_eq!(no_cap_cfg_space.iter_capability_offsets().next(), None);
let example_id_1 = 0x23;
let example_id_2 = 0x34;
let empty_capability: RegisterSet<0> = RegisterSetBuilder::<0>::new().into();
let cfg_space: ConfigSpace = ConfigSpaceBuilder::new(0, 0)
.capability(example_id_1, &empty_capability)
.capability(example_id_2, &empty_capability)
.config_space();
let offsets: Vec<u8> = cfg_space.iter_capability_offsets().collect();
assert_eq!(offsets.len(), 2);
assert_eq!(
cfg_space.read(Request::new(offsets[0].into(), RequestSize::Size1)),
u64::from(example_id_1)
);
assert_eq!(
cfg_space.read(Request::new(offsets[1].into(), RequestSize::Size1)),
u64::from(example_id_2)
);
}
#[test]
fn can_query_bars() {
let cfg_space = ConfigSpaceBuilder::new(0, 0)
.mem32_nonprefetchable_bar(0, 0x8000_0000)
.config_space();
assert_eq!(
cfg_space.bar(0),
Some(BarInfo {
size: 0x8000_0000,
kind: RequestKind::Memory
})
);
assert_eq!(cfg_space.bar(1), None);
}
}