const CONFIG_SPACE_SIZE: usize = 4096;
const MAX_DEVICES: usize = 32;
const ECAM_BUS_SHIFT: u64 = 20;
const ECAM_DEVFN_SHIFT: u64 = 12;
const ECAM_REG_MASK: u64 = 0xFFF;
pub(crate) const ECAM_BYTES_PER_BUS: u64 = 1 << ECAM_BUS_SHIFT;
const CAM_ENABLE_BIT: u32 = 0x8000_0000;
const HOST_BRIDGE_VENDOR_ID: u16 = 0x8086;
const HOST_BRIDGE_DEVICE_ID: u16 = 0x0D57;
const CLASS_BRIDGE: u8 = 0x06;
const SUBCLASS_HOST: u8 = 0x00;
pub(crate) const REG_VENDOR_ID: u16 = 0x00;
pub(crate) const REG_DEVICE_ID: u16 = 0x02;
pub(crate) const REG_COMMAND: u16 = 0x04;
pub(crate) const REG_STATUS: u16 = 0x06;
pub(crate) const REG_REVISION_ID: u16 = 0x08;
pub(crate) const REG_PROG_IF: u16 = 0x09;
pub(crate) const REG_SUBCLASS: u16 = 0x0A;
pub(crate) const REG_CLASS: u16 = 0x0B;
pub(crate) const REG_HEADER_TYPE: u16 = 0x0E;
pub(crate) const REG_BAR0: u16 = 0x10;
pub(crate) const REG_CAP_PTR: u16 = 0x34;
pub(crate) const REG_INTERRUPT_LINE: u16 = 0x3C;
pub(crate) const REG_INTERRUPT_PIN: u16 = 0x3D;
pub(crate) const PCI_STATUS_CAP_LIST: u16 = 0x0010;
pub(crate) const PCI_COMMAND_MEMORY: u16 = 0x0002;
pub(crate) const PCI_COMMAND_WMASK: u16 = 0x0001 | 0x0002 | 0x0004 | 0x0100 | 0x0400;
pub(crate) trait PciFunction: Send {
fn config_read(&self, reg: u16, data: &mut [u8]);
fn config_write(&mut self, reg: u16, data: &[u8]);
fn bar_window(&self) -> Option<(u64, u64)> {
None
}
fn bar_read(&mut self, offset: u64, data: &mut [u8]) {
let _ = offset;
data.fill(0xFF);
}
fn bar_write(&mut self, offset: u64, data: &[u8]) {
let _ = (offset, data);
}
}
pub(crate) struct ConfigSpace {
space: Box<[u8; CONFIG_SPACE_SIZE]>,
wmask: Box<[u8; CONFIG_SPACE_SIZE]>,
}
impl ConfigSpace {
pub(crate) fn new() -> Self {
Self {
space: Box::new([0u8; CONFIG_SPACE_SIZE]),
wmask: Box::new([0u8; CONFIG_SPACE_SIZE]),
}
}
pub(crate) fn read(&self, reg: u16, data: &mut [u8]) {
let start = reg as usize;
match start.checked_add(data.len()) {
Some(end) if end <= CONFIG_SPACE_SIZE => data.copy_from_slice(&self.space[start..end]),
_ => data.fill(0xFF),
}
}
pub(crate) fn write(&mut self, reg: u16, data: &[u8]) {
let start = reg as usize;
let end = match start.checked_add(data.len()) {
Some(end) if end <= CONFIG_SPACE_SIZE => end,
_ => return,
};
for (i, &b) in (start..end).zip(data.iter()) {
let w = self.wmask[i];
self.space[i] = (self.space[i] & !w) | (b & w);
}
}
pub(crate) fn set_u8(&mut self, reg: u16, val: u8) {
debug_assert!(
(reg as usize) < CONFIG_SPACE_SIZE,
"config-space set_u8 out of range"
);
self.space[reg as usize] = val;
}
pub(crate) fn set_u16(&mut self, reg: u16, val: u16) {
let reg = reg as usize;
debug_assert!(
reg + 2 <= CONFIG_SPACE_SIZE,
"config-space set_u16 out of range"
);
self.space[reg..reg + 2].copy_from_slice(&val.to_le_bytes());
}
pub(crate) fn set_u32(&mut self, reg: u16, val: u32) {
let reg = reg as usize;
debug_assert!(
reg + 4 <= CONFIG_SPACE_SIZE,
"config-space set_u32 out of range"
);
self.space[reg..reg + 4].copy_from_slice(&val.to_le_bytes());
}
pub(crate) fn set_wmask_u16(&mut self, reg: u16, mask: u16) {
let reg = reg as usize;
debug_assert!(
reg + 2 <= CONFIG_SPACE_SIZE,
"config-space set_wmask_u16 out of range"
);
self.wmask[reg..reg + 2].copy_from_slice(&mask.to_le_bytes());
}
pub(crate) fn set_wmask_u32(&mut self, reg: u16, mask: u32) {
let reg = reg as usize;
debug_assert!(
reg + 4 <= CONFIG_SPACE_SIZE,
"config-space set_wmask_u32 out of range"
);
self.wmask[reg..reg + 4].copy_from_slice(&mask.to_le_bytes());
}
}
pub(crate) struct HostBridge {
cfg: ConfigSpace,
}
impl HostBridge {
fn new() -> Self {
let mut cfg = ConfigSpace::new();
cfg.set_u16(REG_VENDOR_ID, HOST_BRIDGE_VENDOR_ID);
cfg.set_u16(REG_DEVICE_ID, HOST_BRIDGE_DEVICE_ID);
cfg.set_u8(REG_REVISION_ID, 0);
cfg.set_u8(REG_PROG_IF, 0);
cfg.set_u8(REG_SUBCLASS, SUBCLASS_HOST);
cfg.set_u8(REG_CLASS, CLASS_BRIDGE);
let _ = REG_HEADER_TYPE;
cfg.set_wmask_u16(REG_COMMAND, PCI_COMMAND_WMASK);
Self { cfg }
}
}
impl PciFunction for HostBridge {
fn config_read(&self, reg: u16, data: &mut [u8]) {
self.cfg.read(reg, data);
}
fn config_write(&mut self, reg: u16, data: &[u8]) {
self.cfg.write(reg, data);
}
}
pub(crate) struct PciBus {
ecam_base: u64,
ecam_size: u64,
funcs: [Option<Box<dyn PciFunction>>; MAX_DEVICES],
config_address: u32,
}
impl PciBus {
pub(crate) fn new(ecam_base: u64, ecam_size: u64) -> Self {
let mut funcs: [Option<Box<dyn PciFunction>>; MAX_DEVICES] = std::array::from_fn(|_| None);
funcs[0] = Some(Box::new(HostBridge::new()));
Self {
ecam_base,
ecam_size,
funcs,
config_address: 0,
}
}
pub(crate) fn add_function(&mut self, slot: usize, func: Box<dyn PciFunction>) {
debug_assert!(
slot != 0 && slot < MAX_DEVICES,
"slot 0 is the host bridge; slot must be in 1..{MAX_DEVICES}"
);
if slot != 0 && slot < MAX_DEVICES {
self.funcs[slot] = Some(func);
} else {
tracing::error!(
slot,
max = MAX_DEVICES,
"PCI function install dropped: slot 0 is the host bridge and \
slot must be in 1..MAX_DEVICES — the device will not enumerate"
);
}
}
pub(crate) fn ecam_contains(&self, gpa: u64) -> bool {
gpa >= self.ecam_base && gpa < self.ecam_base + self.ecam_size
}
pub(crate) fn ecam_read(&self, gpa: u64, data: &mut [u8]) {
let offset = gpa.wrapping_sub(self.ecam_base);
if offset >= self.ecam_size {
data.fill(0xFF);
return;
}
let (bus, devfn, reg) = decode_ecam(offset);
self.read_config(bus, devfn, reg, data);
}
pub(crate) fn ecam_write(&mut self, gpa: u64, data: &[u8]) {
let offset = gpa.wrapping_sub(self.ecam_base);
if offset >= self.ecam_size {
return;
}
let (bus, devfn, reg) = decode_ecam(offset);
self.write_config(bus, devfn, reg, data);
}
pub(crate) fn bar_mmio_contains(&self, gpa: u64) -> bool {
self.bar_owner(gpa).is_some()
}
pub(crate) fn bar_mmio_read(&mut self, gpa: u64, data: &mut [u8]) {
match self.bar_owner(gpa) {
Some((slot, base)) => {
self.funcs[slot]
.as_mut()
.unwrap()
.bar_read(gpa - base, data);
}
None => data.fill(0xFF),
}
}
pub(crate) fn bar_mmio_write(&mut self, gpa: u64, data: &[u8]) {
if let Some((slot, base)) = self.bar_owner(gpa) {
self.funcs[slot]
.as_mut()
.unwrap()
.bar_write(gpa - base, data);
}
}
fn bar_owner(&self, gpa: u64) -> Option<(usize, u64)> {
self.funcs.iter().enumerate().find_map(|(slot, f)| {
let (base, len) = f.as_ref()?.bar_window()?;
(gpa >= base && gpa < base + len).then_some((slot, base))
})
}
pub(crate) fn cam_set_address(&mut self, val: u32) {
self.config_address = val;
}
pub(crate) fn cam_get_address(&self) -> u32 {
self.config_address
}
pub(crate) fn cam_data_read(&self, byte_off: u8, data: &mut [u8]) {
match self.cam_decode(byte_off) {
Some((bus, devfn, reg)) => {
self.read_config(bus, devfn, reg, data);
}
None => data.fill(0xFF),
}
}
pub(crate) fn cam_data_write(&mut self, byte_off: u8, data: &[u8]) {
if let Some((bus, devfn, reg)) = self.cam_decode(byte_off) {
self.write_config(bus, devfn, reg, data);
}
}
fn cam_decode(&self, byte_off: u8) -> Option<(u8, u8, u16)> {
if self.config_address & CAM_ENABLE_BIT == 0 {
return None;
}
let bus = ((self.config_address >> 16) & 0xFF) as u8;
let devfn = ((self.config_address >> 8) & 0xFF) as u8;
let reg_dword = (self.config_address & 0xFC) | ((self.config_address >> 16) & 0xF00);
let reg = (reg_dword + byte_off as u32) as u16;
Some((bus, devfn, reg))
}
fn slot_of(&self, bus: u8, devfn: u8) -> Option<usize> {
let slot = (devfn >> 3) as usize;
let function = devfn & 0x7;
if bus != 0 || function != 0 || slot >= MAX_DEVICES {
return None;
}
self.funcs[slot].as_ref().map(|_| slot)
}
fn read_config(&self, bus: u8, devfn: u8, reg: u16, data: &mut [u8]) {
match self.slot_of(bus, devfn) {
Some(slot) => self.funcs[slot].as_ref().unwrap().config_read(reg, data),
None => data.fill(0xFF),
}
}
fn write_config(&mut self, bus: u8, devfn: u8, reg: u16, data: &[u8]) {
if let Some(slot) = self.slot_of(bus, devfn) {
self.funcs[slot].as_mut().unwrap().config_write(reg, data);
}
}
}
fn decode_ecam(offset: u64) -> (u8, u8, u16) {
let bus = (offset >> ECAM_BUS_SHIFT) as u8;
let devfn = ((offset >> ECAM_DEVFN_SHIFT) & 0xFF) as u8;
let reg = (offset & ECAM_REG_MASK) as u16;
(bus, devfn, reg)
}
#[cfg(test)]
mod tests {
use super::*;
const BASE: u64 = 0xE000_0000;
const SIZE: u64 = ECAM_BYTES_PER_BUS;
fn read_u32(bus: &PciBus, devfn: u8, reg: u16) -> u32 {
let gpa = BASE + ((devfn as u64) << ECAM_DEVFN_SHIFT) + reg as u64;
let mut data = [0u8; 4];
bus.ecam_read(gpa, &mut data);
u32::from_le_bytes(data)
}
#[test]
fn host_bridge_present_at_00_00_0() {
let bus = PciBus::new(BASE, SIZE);
let vendor_device = read_u32(&bus, 0, REG_VENDOR_ID);
assert_eq!(vendor_device & 0xFFFF, HOST_BRIDGE_VENDOR_ID as u32);
assert_eq!(vendor_device >> 16, HOST_BRIDGE_DEVICE_ID as u32);
}
#[test]
fn host_bridge_class_is_host_bridge() {
let bus = PciBus::new(BASE, SIZE);
let class_dword = read_u32(&bus, 0, 0x08);
assert_eq!((class_dword >> 24) as u8, CLASS_BRIDGE);
assert_eq!((class_dword >> 16) as u8, SUBCLASS_HOST);
}
#[test]
fn absent_device_reads_all_ones() {
let bus = PciBus::new(BASE, SIZE);
assert_eq!(read_u32(&bus, 1 << 3, REG_VENDOR_ID), 0xFFFF_FFFF);
for slot in 1..MAX_DEVICES as u8 {
assert_eq!(read_u32(&bus, slot << 3, REG_VENDOR_ID), 0xFFFF_FFFF);
}
}
#[test]
fn nonzero_bus_reads_all_ones() {
let bus = PciBus::new(BASE, ECAM_BYTES_PER_BUS * 2);
let gpa = BASE + (1u64 << ECAM_BUS_SHIFT);
let mut data = [0u8; 4];
bus.ecam_read(gpa, &mut data);
assert_eq!(u32::from_le_bytes(data), 0xFFFF_FFFF);
}
#[test]
fn nonzero_function_reads_all_ones() {
let bus = PciBus::new(BASE, SIZE);
assert_eq!(read_u32(&bus, 1, REG_VENDOR_ID), 0xFFFF_FFFF);
}
#[test]
fn out_of_window_reads_all_ones() {
let bus = PciBus::new(BASE, SIZE);
let mut data = [0u8; 4];
bus.ecam_read(BASE - 4, &mut data);
assert_eq!(u32::from_le_bytes(data), 0xFFFF_FFFF);
bus.ecam_read(BASE + SIZE, &mut data);
assert_eq!(u32::from_le_bytes(data), 0xFFFF_FFFF);
assert!(!bus.ecam_contains(BASE - 1));
assert!(bus.ecam_contains(BASE));
assert!(bus.ecam_contains(BASE + SIZE - 1));
assert!(!bus.ecam_contains(BASE + SIZE));
}
#[test]
fn overread_past_config_space_is_all_ones_not_panic() {
let bus = PciBus::new(BASE, SIZE);
let gpa = BASE + (ECAM_REG_MASK - 1); let mut data = [0u8; 4];
bus.ecam_read(gpa, &mut data);
assert_eq!(u32::from_le_bytes(data), 0xFFFF_FFFF);
}
#[test]
fn command_register_is_writable_identity_is_read_only() {
let mut bus = PciBus::new(BASE, SIZE);
let cmd_gpa = BASE + REG_COMMAND as u64;
bus.ecam_write(cmd_gpa, &(0x0006u16).to_le_bytes());
let mut cmd = [0u8; 2];
bus.ecam_read(cmd_gpa, &mut cmd);
assert_eq!(u16::from_le_bytes(cmd) & 0x0006, 0x0006);
bus.ecam_write(BASE + REG_VENDOR_ID as u64, &(0x1234u16).to_le_bytes());
assert_eq!(
read_u32(&bus, 0, REG_VENDOR_ID) & 0xFFFF,
HOST_BRIDGE_VENDOR_ID as u32
);
}
#[test]
fn command_write_rejects_unwritable_bits() {
let mut bus = PciBus::new(BASE, SIZE);
let cmd_gpa = BASE + REG_COMMAND as u64;
bus.ecam_write(cmd_gpa, &(0x0008u16).to_le_bytes());
let mut cmd = [0u8; 2];
bus.ecam_read(cmd_gpa, &mut cmd);
assert_eq!(u16::from_le_bytes(cmd) & 0x0008, 0);
}
#[test]
fn decode_ecam_extracts_bus_devfn_reg() {
let offset = (3u64 << ECAM_BUS_SHIFT) | (0x1Au64 << ECAM_DEVFN_SHIFT) | 0x0AC;
assert_eq!(decode_ecam(offset), (3, 0x1A, 0x0AC));
}
fn cam_addr(bus: u8, devfn: u8, reg: u8) -> u32 {
CAM_ENABLE_BIT | ((bus as u32) << 16) | ((devfn as u32) << 8) | (reg as u32 & 0xFC)
}
#[test]
fn cam_reads_host_bridge_vendor() {
let mut bus = PciBus::new(BASE, SIZE);
bus.cam_set_address(cam_addr(0, 0, REG_VENDOR_ID as u8));
assert_eq!(bus.cam_get_address() & CAM_ENABLE_BIT, CAM_ENABLE_BIT);
let mut data = [0u8; 4];
bus.cam_data_read(0, &mut data);
let vendor_device = u32::from_le_bytes(data);
assert_eq!(vendor_device & 0xFFFF, HOST_BRIDGE_VENDOR_ID as u32);
assert_eq!(vendor_device >> 16, HOST_BRIDGE_DEVICE_ID as u32);
}
#[test]
fn cam_disabled_latch_reads_all_ones() {
let bus = PciBus::new(BASE, SIZE);
let mut data = [0u8; 4];
bus.cam_data_read(0, &mut data);
assert_eq!(u32::from_le_bytes(data), 0xFFFF_FFFF);
}
#[test]
fn cam_absent_function_reads_all_ones() {
let mut bus = PciBus::new(BASE, SIZE);
bus.cam_set_address(cam_addr(0, 1 << 3, REG_VENDOR_ID as u8));
let mut data = [0u8; 4];
bus.cam_data_read(0, &mut data);
assert_eq!(u32::from_le_bytes(data), 0xFFFF_FFFF);
}
#[test]
fn cam_byte_offset_selects_within_dword() {
let mut bus = PciBus::new(BASE, SIZE);
bus.cam_set_address(cam_addr(0, 0, 0x08));
let mut byte = [0u8; 1];
bus.cam_data_read(3, &mut byte);
assert_eq!(byte[0], CLASS_BRIDGE);
}
#[test]
fn cam_hostile_misaligned_read_no_panic() {
let mut bus = PciBus::new(BASE, SIZE);
bus.cam_set_address(cam_addr(0, 0, 0xFC));
let mut data = [0u8; 4];
bus.cam_data_read(3, &mut data);
assert_eq!(u32::from_le_bytes(data), 0);
}
#[test]
fn cam_decodes_extended_register_not_aliased_to_base() {
let mut bus = PciBus::new(BASE, SIZE);
let addr = CAM_ENABLE_BIT | ((0x100u32 & 0xF00) << 16);
bus.cam_set_address(addr);
let mut data = [0u8; 4];
bus.cam_data_read(0, &mut data);
assert_eq!(u32::from_le_bytes(data), 0);
}
#[test]
fn ecam_decodes_extended_register_not_aliased_to_base() {
let bus = PciBus::new(BASE, SIZE);
assert_eq!(read_u32(&bus, 0, 0x100), 0);
assert_eq!(
read_u32(&bus, 0, REG_VENDOR_ID) & 0xFFFF,
HOST_BRIDGE_VENDOR_ID as u32
);
}
#[test]
fn cam_and_ecam_share_config_space() {
let mut bus = PciBus::new(BASE, SIZE);
bus.cam_set_address(cam_addr(0, 0, REG_COMMAND as u8));
bus.cam_data_write(0, &(0x0006u32).to_le_bytes());
let mut cmd = [0u8; 2];
bus.ecam_read(BASE + REG_COMMAND as u64, &mut cmd);
assert_eq!(u16::from_le_bytes(cmd) & 0x0006, 0x0006);
}
}