//! Disposable Virtio PCI common configuration and queue lifecycle probe. use super::*; use os_pci_capabilities::{self, ConfigReader}; use os_pci_discovery::{ClaimTable, EcamWindow}; use os_pci_resources::{Width, decode_memory_bar_sizing}; use os_virtio_pci_common::{Function, MemoryBar, Registry, decode_common}; struct Config(u64); impl ConfigReader for Config { fn read_dword(&mut self, offset: u8) -> Result { Ok(unsafe { read_volatile((self.0 + u64::from(offset)) as *const u32) }) } } unsafe fn read8(base: u64, offset: u64) -> u8 { unsafe { read_volatile((base + offset) as *const u8) } } unsafe fn read16(base: u64, offset: u64) -> u16 { unsafe { read_volatile((base + offset) as *const u16) } } unsafe fn read32(base: u64, offset: u64) -> u32 { unsafe { read_volatile((base + offset) as *const u32) } } unsafe fn write8(base: u64, offset: u64, value: u8) { unsafe { write_volatile((base + offset) as *mut u8, value) } } unsafe fn write16(base: u64, offset: u64, value: u16) { unsafe { write_volatile((base + offset) as *mut u16, value) } } unsafe fn write32(base: u64, offset: u64, value: u32) { unsafe { write_volatile((base + offset) as *mut u32, value) } } unsafe fn write64_halves(base: u64, offset: u64, value: u64) { unsafe { write32(base, offset, value as u32); write32(base, offset + 4, (value >> 32) as u32); } } pub fn run(boot: &BootData, tables: &mmu::Tables, allocator: &mut memory::FrameAllocator) -> 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(|_| "k1470-ecam")?; let (start, end) = host.memory_aperture.ok_or("k1470-aperture")?; let bar_base = end.checked_sub(0x4000).ok_or("k1470-bar-base")?; if bar_base < start || bar_base & 0x3fff != 0 || !tables.device_page_matches(bar_base) { return Err("k1470-device-mapping"); } let window = EcamWindow::new(host.base, host.size, host.first_bus, host.last_bus) .map_err(|_| "k1470-ecam-window")?; let mut configs = ClaimTable::<1>::new(); let mut chosen = None; for slot in [1u8, 2u8] { let address = window.address(0, slot, 0, 0).map_err(|_| "k1470-bdf")?.physical; let identity = unsafe { read32(address, 0) }; if identity & 0xffff == 0x1af4 && (identity >> 16 == 0x1005 || identity >> 16 == 0x1044) { if chosen.replace((slot, address, identity)).is_some() { return Err("k1470-duplicate-rng"); } } } let (slot, config, identity) = chosen.ok_or("k1470-rng-missing")?; let handle = configs.claim(window, 0, slot, 0, 0) .map_err(|_| "k1470-config-claim")?; if handle.physical != config { return Err("k1470-config-address"); } let first = unsafe { read8(config, 0x34) }; let chain = os_pci_capabilities::walk::<32>(&mut Config(config), first) .map_err(|_| "k1470-cap-chain")?; let mut common = None; for entry in chain.entries[..chain.len].iter().flatten() { if entry.id == 0x09 && unsafe { read8(config, u64::from(entry.offset) + 3) } == 1 { if common.replace(entry.offset).is_some() { return Err("k1470-multiple-common"); } } } let cap_offset = common.ok_or("k1470-common-missing")?; let cap_length = unsafe { read8(config, u64::from(cap_offset) + 2) } as usize; if cap_length < 16 || usize::from(cap_offset) + cap_length > 256 { return Err("k1470-common-cap-length"); } let mut cap = [0u8; 256]; for (index, byte) in cap[..cap_length].iter_mut().enumerate() { *byte = unsafe { read8(config, u64::from(cap_offset) + index as u64) }; } if cap[4] != 4 { return Err("k1470-common-bar-index"); } let command = unsafe { read16(config, 4) }; let old_low = unsafe { read32(config, 0x20) }; let old_high = unsafe { read32(config, 0x24) }; let (mask_low, mask_high); unsafe { write16(config, 4, command & !2); write32(config, 0x20, u32::MAX); write32(config, 0x24, u32::MAX); asm!("dsb sy", options(nostack, preserves_flags)); mask_low = read32(config, 0x20); mask_high = read32(config, 0x24); write32(config, 0x24, old_high); write32(config, 0x20, old_low); write16(config, 4, command); asm!("dsb sy", options(nostack, preserves_flags)); } let sizing = decode_memory_bar_sizing(old_low, mask_low, mask_high) .map_err(|_| "k1470-bar-sizing")?; if sizing.width != Width::Bits64 || sizing.size != 0x4000 { return Err("k1470-bar-size"); } let region = decode_common(&cap[..cap_length], MemoryBar { index: 4, base: bar_base, size: sizing.size, }).map_err(|_| "k1470-common-bounds")?; if region.base < bar_base || region.mapped_end() > bar_base + 0x1000 { return Err("k1470-common-page"); } let mut registry = Registry::<1>::new(); let owner = registry.claim(Function::new(0, 0, slot, 0) .map_err(|_| "k1470-function")?, region).map_err(|_| "k1470-claim")?; unsafe { write16(config, 4, command & !2); write32(config, 0x20, (bar_base as u32) | (old_low & 0xf)); write32(config, 0x24, (bar_base >> 32) as u32); write16(config, 4, command | 2); asm!("dsb sy", options(nostack, preserves_flags)); } if unsafe { read32(config, 0x20) } & !0xf != bar_base as u32 { return Err("k1470-bar-readback"); } let base = region.base; unsafe { write8(base, 0x14, 0); } let reset_status = unsafe { read8(base, 0x14) }; unsafe { write16(base, 0x16, 0); } let reset_queue = unsafe { read16(base, 0x1c) }; registry.observe_reset(owner, reset_status, reset_queue) .map_err(|_| "k1470-initial-reset")?; let queues = unsafe { read16(base, 0x12) }; if queues == 0 { return Err("k1470-no-queues"); } let free_before = allocator.remaining_pages(); let mut frames = [0u64; 3]; let mut allocated = 0usize; for frame in &mut frames { match allocator.allocate() { Some(address) => { *frame = address; allocated += 1; } None => break, } } if allocated != 3 { for frame in &frames[..allocated] { allocator.release(*frame).map_err(|_| "k1470-alloc-rollback")?; } return Err("k1470-queue-frames"); } for frame in frames { unsafe { core::ptr::write_bytes(frame as *mut u8, 0, memory::PAGE_SIZE as usize); } } let mut failure = None; let (offered_low, offered_high, max_queue, size, status_ready, queue_ready) = unsafe { write8(base, 0x14, 1); write8(base, 0x14, 3); write32(base, 0, 0); let offered_low = read32(base, 4); write32(base, 0, 1); let offered_high = read32(base, 4); if offered_high & 1 == 0 { failure = Some("k1470-version-one"); } write32(base, 8, 0); write32(base, 0xc, 0); write32(base, 8, 1); write32(base, 0xc, 1); write8(base, 0x14, 0xb); if read8(base, 0x14) != 0xb { failure = Some("k1470-features-ok"); } write16(base, 0x16, 0); let max_queue = read16(base, 0x18); let mut size = 8u16; while size > max_queue && size > 1 { size >>= 1; } if max_queue == 0 || max_queue > 1024 || size > max_queue { failure = Some("k1470-queue-maximum"); } if failure.is_none() { write16(base, 0x18, size); write64_halves(base, 0x20, frames[0]); write64_halves(base, 0x28, frames[1]); write64_halves(base, 0x30, frames[2]); asm!("dsb sy", options(nostack, preserves_flags)); write16(base, 0x1c, 1); if read16(base, 0x1c) != 1 { failure = Some("k1470-queue-enable"); } else { write8(base, 0x14, 0xf); if read8(base, 0x14) != 0xf { failure = Some("k1470-driver-ok"); } else if registry.mark_active(owner, 0xf, 1).is_err() { failure = Some("k1470-owner-active"); } } } (offered_low, offered_high, max_queue, size, read8(base, 0x14), read16(base, 0x1c)) }; unsafe { write8(base, 0x14, 0); asm!("dsb sy", options(nostack, preserves_flags)); } let final_status = unsafe { read8(base, 0x14) }; let final_queue = unsafe { read16(base, 0x1c) }; if final_status != 0 || final_queue != 0 { return Err("k1470-reset-not-observed"); } registry.observe_reset(owner, final_status, final_queue) .map_err(|_| "k1470-model-reset")?; registry.release(owner).map_err(|_| "k1470-owner-release")?; for frame in frames { allocator.release(frame).map_err(|_| "k1470-frame-release")?; } unsafe { write16(config, 4, command & !2); write32(config, 0x24, old_high); write32(config, 0x20, old_low); write16(config, 4, command); asm!("dsb sy", options(nostack, preserves_flags)); } let restored = unsafe { read32(config, 0x20) == old_low && read32(config, 0x24) == old_high && read16(config, 4) == command }; let unmapped = unsafe { k1450_pci_device_map::unmap_bar_page(bar_base) }.is_ok(); configs.release(handle).map_err(|_| "k1470-config-release")?; if !restored || !unmapped || configs.count() != 0 || registry.count() != 0 || allocator.remaining_pages() != free_before { return Err("k1470-owner-restoration"); } if let Some(reason) = failure { return Err(reason); } if status_ready != 0xf || queue_ready != 1 { return Err("k1470-publication"); } write_str("TURKOS/OS/K1470-PCI-COMMON: ID=0x"); write_hex(identity as usize); write_str(" SLOT=0x"); write_hex(slot as usize); write_str(" BAR=0x"); write_hex(bar_base as usize); write_str(" CAP=0x"); write_hex(cap_offset as usize); write_str(" OFFSET=0x"); write_hex((region.base - bar_base) as usize); write_str(" QMAX=0x"); write_hex(max_queue as usize); write_str(" QSIZE=0x"); write_hex(size as usize); write_str(" HOSTLOW=0x"); write_hex(offered_low as usize); write_str(" HOSTHIGH=0x"); write_hex(offered_high as usize); write_line(" STATUS=0xf QUEUE=1 RESET=0 BAR=RESTORED UNMAPPED=1 CLAIMS=0 FRAMES=RETURNED"); Ok(()) }