//! Raise one MSI-X vector from a disposable QEMU PCIe root-port hotplug event. use super::*; use os_pci_capabilities::{self, ConfigReader}; use os_pci_discovery::{ClaimTable, EcamWindow}; use os_pci_msix::{DoorbellWindow, Message, VectorTable}; 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) }) } } fn counter() -> u64 { let value: u64; unsafe { asm!("mrs {value}, cntpct_el0", value = out(reg) value, options(nomem, nostack, preserves_flags)); } value } 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(|_| "k1460-ecam")?; let msi = dtb::discover_pci_msi_frame(blob).map_err(|_| "k1460-msi-frame")?; let (bar, end) = host.memory_aperture.ok_or("k1460-aperture")?; if bar != 0x1000_0000 || end - bar < 0x1000 || msi.size != 0x1000 || msi.base != boot.interrupts.gic_distributor + 0x2_0000 { return Err("k1460-addresses"); } let typer = unsafe { read_volatile((msi.base + 8) as *const u32) }; let spi = typer >> 16; let count = typer & 0xffff; let doorbell = msi.base + 0x40; if spi != 80 || count < 1 || spi + count > 1020 { return Err("k1460-msi-typer"); } let message = Message { address: doorbell, data: spi }; let doorbell_window = DoorbellWindow::new(doorbell, doorbell + 4) .map_err(|_| "k1460-doorbell-window")?; let mut vectors = VectorTable::<1>::new(1).map_err(|_| "k1460-vector-table")?; vectors.claim_masked(0).map_err(|_| "k1460-vector-claim")?; let window = EcamWindow::new(host.base, host.size, host.first_bus, host.last_bus) .map_err(|_| "k1460-ecam-window")?; let mut configs = ClaimTable::<1>::new(); let port = configs.claim(window, 0, 4, 0, 0).map_err(|_| "k1460-port-claim")?; let config = port.physical; if config != host.base + 0x20000 || unsafe { read_volatile(config as *const u32) } != 0x000c_1b36 { return Err("k1460-port-identity"); } let first = unsafe { read_volatile((config + 0x34) as *const u8) }; let chain = os_pci_capabilities::walk::<8>(&mut Config(config), first) .map_err(|_| "k1460-capability-chain")?; let msix = chain.entries[..chain.len].iter().flatten() .find(|cap| cap.id == 0x11).ok_or("k1460-msix-capability")?.offset as u64; let pcie = chain.entries[..chain.len].iter().flatten() .find(|cap| cap.id == 0x10).ok_or("k1460-pcie-capability")?.offset as u64; let control_addr = config + msix + 2; let old_msix = unsafe { read_volatile(control_addr as *const u16) }; let table = unsafe { read_volatile((config + msix + 4) as *const u32) }; let pba = unsafe { read_volatile((config + msix + 8) as *const u32) }; if old_msix & 0x7ff != 0 || old_msix & 0xc000 != 0 || table != 0 || pba != 0x800 || pcie != 0x54 { return Err("k1460-msix-layout"); } let pending = (boot.interrupts.gic_distributor + 0x208) as *const u32; let clear_pending = (boot.interrupts.gic_distributor + 0x288) as *mut u32; let pending_bit = 1u32 << (spi - 64); let group = (boot.interrupts.gic_distributor + 0x88) as *mut u32; let configuration = (boot.interrupts.gic_distributor + 0xc14) as *mut u32; let enable = (boot.interrupts.gic_distributor + 0x108) as *mut u32; let disable = (boot.interrupts.gic_distributor + 0x188) as *mut u32; if unsafe { read_volatile(pending) } & pending_bit != 0 { return Err("k1460-preexisting-pending"); } let old_command = unsafe { read_volatile((config + 4) as *const u16) }; let old_bus = unsafe { read_volatile((config + 0x18) as *const u32) }; let old_bar = unsafe { read_volatile((config + 0x10) as *const u32) }; let slot_control_addr = config + pcie + 0x18; let old_slot_control = unsafe { read_volatile(slot_control_addr as *const u16) }; let old_group = unsafe { read_volatile(group) }; let old_configuration = unsafe { read_volatile(configuration) }; let old_enable = unsafe { read_volatile(enable) }; let old_daif: u64; unsafe { asm!("mrs {value}, daif", value = out(reg) old_daif, options(nomem, nostack, preserves_flags)); } unsafe { asm!("msr daifset, #2", "isb", options(nostack, preserves_flags)); write_volatile(group, old_group | pending_bit); write_volatile(configuration, (old_configuration & !3) | 2); write_volatile(enable, pending_bit); asm!("dsb sy", options(nostack, preserves_flags)); write_volatile(doorbell as *mut u32, spi); asm!("dsb sy", options(nostack, preserves_flags)); } let mut calibrated = false; for _ in 0..2_000 { if unsafe { read_volatile(pending) } & pending_bit != 0 { calibrated = true; break; } } write_str("TURKOS/OS/K1460-PREFLIGHT: GIC-PENDING=0x"); write_hex(calibrated as usize); write_str(" CONFIG=0x"); write_hex(unsafe { read_volatile(configuration) } as usize); write_line(""); unsafe { write_volatile(clear_pending, pending_bit); asm!("dsb sy", options(nostack, preserves_flags)); write_volatile((config + 4) as *mut u16, old_command & !2); write_volatile((config + 0x10) as *mut u32, bar as u32); write_volatile((config + 0x18) as *mut u32, 0x0001_0100); write_volatile((config + 4) as *mut u16, old_command | 6); asm!("dsb sy", options(nostack, preserves_flags)); } let old_entry = unsafe { [read_volatile(bar as *const u32), read_volatile((bar + 4) as *const u32), read_volatile((bar + 8) as *const u32), read_volatile((bar + 12) as *const u32)] }; unsafe { write_volatile((bar + 12) as *mut u32, 1); write_volatile(bar as *mut u32, doorbell as u32); write_volatile((bar + 4) as *mut u32, (doorbell >> 32) as u32); write_volatile((bar + 8) as *mut u32, spi); write_volatile(control_addr as *mut u16, old_msix | 0xc000); write_volatile(slot_control_addr as *mut u16, old_slot_control | (1 << 5) | (1 << 3)); asm!("dsb sy", options(nostack, preserves_flags)); } let programmed = unsafe { read_volatile((config + 0x10) as *const u32) & !0xf == bar as u32 && read_volatile(bar as *const u32) == doorbell as u32 && read_volatile((bar + 4) as *const u32) == (doorbell >> 32) as u32 && read_volatile((bar + 8) as *const u32) == spi && read_volatile((bar + 12) as *const u32) & 1 != 0 }; if programmed && calibrated { vectors.arm(0, message, doorbell_window).map_err(|_| "k1460-vector-arm")?; unsafe { write_volatile(control_addr as *mut u16, (old_msix | 0x8000) & !0x4000); write_volatile((bar + 12) as *mut u32, 0); asm!("dsb sy", options(nostack, preserves_flags)); } write_line("TURKOS/OS/K1460-ARMED"); } let frequency: u64; unsafe { asm!("mrs {value}, cntfrq_el0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)); } let deadline = counter().saturating_add(frequency.saturating_mul(15)); let mut asserted = false; if programmed && calibrated { while counter() < deadline { if unsafe { read_volatile(pending) } & pending_bit != 0 { asserted = true; break; } unsafe { asm!("yield", options(nomem, nostack, preserves_flags)); } } } let slot_status = unsafe { read_volatile((slot_control_addr + 2) as *const u16) }; write_str("TURKOS/OS/K1460-OBSERVE: SLOT=0x"); write_hex(slot_status as usize); write_str(" MSIX=0x"); write_hex(unsafe { read_volatile(control_addr as *const u16) } as usize); write_str(" VECTOR=0x"); write_hex(unsafe { read_volatile((bar + 12) as *const u32) } as usize); write_str(" PENDING=0x"); write_hex(unsafe { read_volatile(pending) } as usize); write_line(""); unsafe { write_volatile((bar + 12) as *mut u32, 1); write_volatile(control_addr as *mut u16, old_msix | 0x4000); if asserted { write_volatile(clear_pending, pending_bit); write_volatile((slot_control_addr + 2) as *mut u16, slot_status & 0x1f); } asm!("dsb sy", options(nostack, preserves_flags)); } if programmed { vectors.mask(0).map_err(|_| "k1460-vector-mask")?; } unsafe { write_volatile(bar as *mut u32, old_entry[0]); write_volatile((bar + 4) as *mut u32, old_entry[1]); write_volatile((bar + 8) as *mut u32, old_entry[2]); write_volatile((bar + 12) as *mut u32, old_entry[3]); asm!("dsb sy", options(nostack, preserves_flags)); } let table_restored = unsafe { read_volatile(bar as *const u32) == old_entry[0] && read_volatile((bar + 4) as *const u32) == old_entry[1] && read_volatile((bar + 8) as *const u32) == old_entry[2] && read_volatile((bar + 12) as *const u32) == old_entry[3] }; unsafe { write_volatile(control_addr as *mut u16, old_msix); write_volatile(slot_control_addr as *mut u16, old_slot_control); write_volatile((config + 4) as *mut u16, old_command & !2); write_volatile((config + 0x10) as *mut u32, old_bar); write_volatile((config + 0x18) as *mut u32, old_bus); write_volatile((config + 4) as *mut u16, old_command); if old_enable & pending_bit == 0 { write_volatile(disable, pending_bit); } write_volatile(configuration, old_configuration); write_volatile(group, old_group); asm!("dsb sy", options(nostack, preserves_flags)); } let restored = unsafe { table_restored && read_volatile(control_addr as *const u16) == old_msix && read_volatile(slot_control_addr as *const u16) == old_slot_control && read_volatile((config + 0x10) as *const u32) == old_bar && read_volatile((config + 0x18) as *const u32) == old_bus && read_volatile((config + 4) as *const u16) == old_command && read_volatile(configuration) == old_configuration && read_volatile(group) == old_group && read_volatile(enable) & pending_bit == old_enable & pending_bit && read_volatile(pending) & pending_bit == 0 }; let bar_unmapped = unsafe { k1450_pci_device_map::unmap_bar_page(bar) }.is_ok(); // The GIC mapping covering this DTB frame predates this probe. Preserve it. unsafe { asm!("msr daif, {value}", "isb", value = in(reg) old_daif, options(nostack, preserves_flags)); } vectors.release(0).map_err(|_| "k1460-vector-release")?; configs.release(port).map_err(|_| "k1460-port-release")?; if !calibrated || !programmed || !asserted || !restored || !bar_unmapped || vectors.count() != 0 || configs.count() != 0 { return Err("k1460-msix-assert-restore"); } write_str("TURKOS/OS/K1460-MSIX: SPI=0x"); write_hex(spi as usize); write_str(" DOORBELL=0x"); write_hex(doorbell as usize); write_str(" SLOT-STATUS=0x"); write_hex(slot_status as usize); write_line(" GIC-PENDING=OBSERVED ACK=1 BAR=RESTORED BAR-UNMAPPED=1 MSI-MAP=PRESERVED CLAIMS=0"); Ok(()) }