//! Physical PCI capability walk with an injected self-loop rejection. use super::*; use os_pci_capabilities::{ConfigReader, Error as CapError, Store}; use os_pci_discovery::{ClaimTable, EcamWindow}; struct PhysicalReader { base: u64, reads: usize } impl ConfigReader for PhysicalReader { fn read_dword(&mut self, offset: u8) -> Result { self.reads += 1; // SAFETY: the caller keeps the DTB-bounded ECAM page mapped as Device // and the walker admits only aligned offsets 0x40..=0xfc. Ok(unsafe { read_volatile((self.base + u64::from(offset)) as *const u32) }) } } struct LoopReader { first: u8, first_word: u32 } impl ConfigReader for LoopReader { fn read_dword(&mut self, offset: u8) -> Result { if offset != self.first { return Err(CapError::ReadFailed); } Ok((self.first_word & !0xff00) | (u32::from(self.first) << 8)) } } pub fn run(boot: &BootData) -> Result<(), &'static str> { let blob = unsafe { core::slice::from_raw_parts(boot.dtb_address as *const u8, boot.dtb_size) }; let host = dtb::discover_pci_ecam(blob).map_err(|_| "k1435-dtb-host")?; let window = EcamWindow::new(host.base, host.size, host.first_bus, host.last_bus) .map_err(|_| "k1435-ecam-window")?; let mut claims = ClaimTable::<1>::new(); let config = claims.claim(window, 0, 1, 0, 0) .map_err(|_| "k1435-config-claim")?; if config.physical != host.base + 0x8000 { return Err("k1435-config-address"); } // SAFETY: boot mapped this complete configuration page as Device-nGnRnE // before TTBR0, and the live claim covers these aligned DWORDs. let identity = unsafe { read_volatile(config.physical as *const u32) }; let command_status = unsafe { read_volatile((config.physical + 4) as *const u32) }; let first_dword = unsafe { read_volatile((config.physical + 0x34) as *const u32) }; if identity & 0xffff != 0x1af4 || command_status & (1 << 20) == 0 { return Err("k1435-capability-status"); } let first = first_dword as u8; let mut reader = PhysicalReader {base: config.physical, reads: 0}; let mut store = Store::<32>::new(); let chain = store.discover(&mut reader, first) .map_err(|_| "k1435-capability-walk")?; if chain.len < 2 || reader.reads != chain.len { return Err("k1435-two-capabilities"); } let before = store.snapshot(); let first_word = chain.entries[0].ok_or("k1435-first-capability")?; let mut injection = LoopReader { first, first_word: u32::from(first_word.id), }; if store.discover(&mut injection, first) != Err(CapError::Cycle) || store.snapshot() != before { return Err("k1435-self-cycle-accepted"); } claims.release(config).map_err(|_| "k1435-claim-release")?; if claims.count() != 0 { return Err("k1435-claim-leak"); } write_str("TURKOS/OS/K1435-CAPS: FIRST=0x"); write_hex(first as usize); write_str(" LEN=0x"); write_hex(chain.len); write_str(" CAP-READS=0x"); write_hex(reader.reads); write_line(" SELF-CYCLE=REJECTED PUBLISHED-STABLE=1 CLAIMS=0"); Ok(()) }