//! Bounded Virtio PCI common configuration discovery and ownership model. pub const VENDOR_CAPABILITY: u8 = 0x09; pub const COMMON_CFG: u8 = 1; pub const CAP_BYTES: usize = 16; pub const BASE_CONFIG_BYTES: u64 = 0x38; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidCapability, UnsupportedBar, InvalidBar, InvalidRegion, DuplicateFunction, Overlap, Full, MissingClaim, StaleClaim, StillActive, WrongPhase, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Function { pub segment: u16, pub bus: u8, pub device: u8, pub function: u8 } impl Function { pub fn new(segment: u16, bus: u8, device: u8, function: u8) -> Result { if device >= 32 || function >= 8 { return Err(Error::InvalidCapability); } Ok(Self {segment, bus, device, function}) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MemoryBar { pub index: u8, pub base: u64, pub size: u64 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Region { pub base: u64, pub advertised_length: u32 } impl Region { pub fn mapped_end(self) -> u64 { self.base + BASE_CONFIG_BYTES } } /// `cap` is the complete conventional capability, including any extra bytes. /// The caller has already decoded the BAR as a claimed memory resource. pub fn decode_common(cap: &[u8], bar: MemoryBar) -> Result { if cap.len() < CAP_BYTES || cap[0] != VENDOR_CAPABILITY || cap[2] < CAP_BYTES as u8 || usize::from(cap[2]) > cap.len() || cap[3] != COMMON_CFG { return Err(Error::InvalidCapability); } if cap[4] >= 6 || cap[4] != bar.index { return Err(Error::UnsupportedBar); } if bar.base == 0 || bar.size < BASE_CONFIG_BYTES || bar.base.checked_add(bar.size).is_none() { return Err(Error::InvalidBar); } let offset = u64::from(u32::from_le_bytes(cap[8..12].try_into().unwrap())); let length = u32::from_le_bytes(cap[12..16].try_into().unwrap()); if offset & 3 != 0 || u64::from(length) < BASE_CONFIG_BYTES || offset.checked_add(u64::from(length)).is_none_or(|end| end > bar.size) { return Err(Error::InvalidRegion); } let base = bar.base.checked_add(offset).ok_or(Error::InvalidRegion)?; base.checked_add(BASE_CONFIG_BYTES).ok_or(Error::InvalidRegion)?; Ok(Region {base, advertised_length: length}) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Claim { pub function: Function, pub region: Region, pub generation: u64 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Phase { Unproven, ResetObserved, Active } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Slot { claim: Claim, phase: Phase } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { entries: [Option; N], pub count: usize, next_generation: u64 } pub struct Registry { entries: [Option; N], count: usize, next_generation: u64, } impl Registry { pub const fn new() -> Self { Self {entries: [None; N], count: 0, next_generation: 1} } pub const fn count(&self) -> usize { self.count } pub fn snapshot(&self) -> Snapshot { Snapshot {entries: self.entries, count: self.count, next_generation: self.next_generation} } fn slot_mut(&mut self, claim: Claim) -> Result<&mut Slot, Error> { let slot = self.entries.iter_mut().flatten() .find(|slot| slot.claim.function == claim.function) .ok_or(Error::MissingClaim)?; if slot.claim != claim { return Err(Error::StaleClaim); } Ok(slot) } pub fn claim(&mut self, function: Function, region: Region) -> Result { let end = region.base.checked_add(BASE_CONFIG_BYTES).ok_or(Error::InvalidRegion)?; if region.base == 0 || region.base & 3 != 0 || u64::from(region.advertised_length) < BASE_CONFIG_BYTES { return Err(Error::InvalidRegion); } if self.entries.iter().flatten().any(|slot| slot.claim.function == function) { return Err(Error::DuplicateFunction); } if self.entries.iter().flatten().any(|slot| region.base < slot.claim.region.mapped_end() && slot.claim.region.base < end) { return Err(Error::Overlap); } let index = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?; let generation = self.next_generation; self.next_generation = generation.checked_add(1).ok_or(Error::Full)?; let claim = Claim {function, region, generation}; self.entries[index] = Some(Slot {claim, phase: Phase::Unproven}); self.count += 1; Ok(claim) } /// Called only after physical reset reads return both zero values. pub fn observe_reset(&mut self, claim: Claim, status: u8, queue_enable: u16) -> Result<(), Error> { let slot = self.slot_mut(claim)?; if status != 0 || queue_enable != 0 { return Err(Error::StillActive); } slot.phase = Phase::ResetObserved; Ok(()) } /// Called only after ordered queue memory publication and a readback of enable=1. pub fn mark_active(&mut self, claim: Claim, status: u8, queue_enable: u16) -> Result<(), Error> { let slot = self.slot_mut(claim)?; if slot.phase != Phase::ResetObserved { return Err(Error::WrongPhase); } if status & 0x04 == 0 || queue_enable != 1 { return Err(Error::WrongPhase); } slot.phase = Phase::Active; Ok(()) } pub fn phase(&mut self, claim: Claim) -> Result { Ok(self.slot_mut(claim)?.phase) } pub fn release(&mut self, claim: Claim) -> Result<(), Error> { let index = self.entries.iter().position(|slot| slot.is_some_and(|slot| slot.claim.function == claim.function)) .ok_or(Error::MissingClaim)?; let slot = self.entries[index].ok_or(Error::MissingClaim)?; if slot.claim != claim { return Err(Error::StaleClaim); } if false { return Err(Error::StillActive); } self.entries[index] = None; self.count -= 1; Ok(()) } }