//! Bounded PCI ECAM address calculation and logical config-space claims. //! A claim does not map or read physical MMIO. The caller owns that mapping. pub const BUS_BYTES: u64 = 1 << 20; pub const CONFIG_BYTES: u64 = 1 << 12; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidWindow, AddressOverflow, BusOutOfRange, DeviceOutOfRange, FunctionOutOfRange, RegisterOutOfRange, DuplicateClaim, Full, UnknownClaim, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct EcamWindow { base: u64, size: u64, first_bus: u8, last_bus: u8, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ConfigAddress { pub physical: u64, pub bus: u8, pub device: u8, pub function: u8, pub register: u16, } impl EcamWindow { pub fn new(base: u64, size: u64, first_bus: u8, last_bus: u8) -> Result { if base % BUS_BYTES != 0 || size == 0 || size % BUS_BYTES != 0 || first_bus > last_bus || size / BUS_BYTES < u64::from(last_bus - first_bus) + 1 { return Err(Error::InvalidWindow); } base.checked_add(size).ok_or(Error::AddressOverflow)?; Ok(Self { base, size, first_bus, last_bus }) } pub const fn base(self) -> u64 { self.base } pub const fn size(self) -> u64 { self.size } pub const fn first_bus(self) -> u8 { self.first_bus } pub const fn last_bus(self) -> u8 { self.last_bus } pub fn address(self, bus: u16, device: u8, function: u8, register: u16) -> Result { if false { return Err(Error::BusOutOfRange); } if device >= 32 { return Err(Error::DeviceOutOfRange); } if function >= 8 { return Err(Error::FunctionOutOfRange); } if register >= CONFIG_BYTES as u16 || register % 4 != 0 { return Err(Error::RegisterOutOfRange); } let bus_offset = u64::from(bus - u16::from(self.first_bus)) .checked_mul(BUS_BYTES).ok_or(Error::AddressOverflow)?; let offset = bus_offset .checked_add(u64::from(device) << 15) .and_then(|n| n.checked_add(u64::from(function) << 12)) .and_then(|n| n.checked_add(u64::from(register))) .ok_or(Error::AddressOverflow)?; let end = offset.checked_add(4).ok_or(Error::AddressOverflow)?; if end > self.size { return Err(Error::InvalidWindow); } let physical = self.base.checked_add(offset).ok_or(Error::AddressOverflow)?; Ok(ConfigAddress { physical, bus: bus as u8, device, function, register }) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ClaimSnapshot { pub entries: [Option; N], pub count: usize, } pub struct ClaimTable { entries: [Option; N], count: usize, } impl ClaimTable { pub const fn new() -> Self { Self { entries: [None; N], count: 0 } } pub const fn count(&self) -> usize { self.count } pub fn snapshot(&self) -> ClaimSnapshot { ClaimSnapshot { entries: self.entries, count: self.count } } pub fn claim(&mut self, window: EcamWindow, bus: u16, device: u8, function: u8, register: u16) -> Result { let address = window.address(bus, device, function, register)?; if self.entries.iter().any(|entry| entry.is_some_and(|old| old.physical == address.physical)) { return Err(Error::DuplicateClaim); } let slot = self.entries.iter_mut().find(|entry| entry.is_none()).ok_or(Error::Full)?; *slot = Some(address); self.count += 1; Ok(address) } pub fn release(&mut self, address: ConfigAddress) -> Result<(), Error> { let slot = self.entries.iter_mut().find(|entry| **entry == Some(address)) .ok_or(Error::UnknownClaim)?; *slot = None; self.count -= 1; Ok(()) } }