//! K531: real SGI entry, then a hardware spurious IAR inside that IRQ vector. //! The trigger is deliberately consumed before the second dispatcher read. //! This models a withdrawn/consumed source, not an electrical hardware fault. //! Special INTIDs never reach the acknowledged-source EOI helper. use super::*; static mut SEEN: u64 = 0; static mut GICR: u64 = 0; fn fail(reason: &str) -> usize { write_str("TURKOS/IRQ-EDGE-ERROR: "); write_line(reason); 0 } pub(super) fn handle(frame: *const irq::IrqContext, low: usize, top: usize, sp: usize) -> usize { if frame.is_null() { return fail("null frame"); } let context = unsafe { &*frame }; if frame as usize != top - size_of::() || !irq::handler_sp_in_stack(low, frame as usize, sp) || context.vector_slot != 5 || context.spsr & 0x8f != 5 || context.interrupted_sp & 15 != 0 || context.x[20] != 0x5310 || context.x[21] != 0x5311 || context.q[0] != [0x5a5a_5a5a_5a5a_5a5a; 2] || context.q[31] != [0xa5a5_a5a5_a5a5_a5a5; 2] || unsafe { read_volatile(core::ptr::addr_of!(__irq_stack_guard)) } != IRQ_GUARD || unsafe { read_volatile(core::ptr::addr_of!(SEEN)) } != 0 { return fail("vector/context"); } let trigger: u64; unsafe { asm!("mrs {value}, ICC_IAR1_EL1", value = out(reg) trigger, options(nomem, nostack)) }; if irq::classify_iar(trigger, 2) != irq::IarClass::Expected { return fail("SGI trigger"); } let gicr = unsafe { GICR }; unsafe { write_volatile((gicr + 0x10180) as *mut u32, 4); asm!("dsb sy", options(nostack)); let mut ready = false; for _ in 0..1_000_000 { if read_volatile(gicr as *const u32) & 8 == 0 { ready = true; break; } } if !ready || read_volatile((gicr + 0x10100) as *const u32) & 4 != 0 { return fail("trigger source not masked"); } // Exactly this acknowledged SGI, not either special value, is EOI'd. asm!("msr ICC_EOIR1_EL1, {value}", "isb", value = in(reg) trigger, options(nomem, nostack)); let control: u64; asm!("mrs {value}, ICC_CTLR_EL1", value = out(reg) control, options(nomem, nostack)); if control & 2 != 0 { asm!("msr ICC_DIR_EL1, {value}", "isb", value = in(reg) trigger, options(nomem, nostack)); } } let special: u64; let again: u64; let priority_before: u64; let priority_after: u64; unsafe { asm!("mrs {priority}, ICC_RPR_EL1", "mrs {iar}, ICC_IAR1_EL1", priority = out(reg) priority_before, iar = out(reg) special, options(nomem, nostack)); // The repeat hardware read witnesses the no-EOI path. No constant is // substituted for IAR, and no EOIR/DIR instruction is in this branch. asm!("mrs {iar}, ICC_IAR1_EL1", "mrs {priority}, ICC_RPR_EL1", iar = out(reg) again, priority = out(reg) priority_after, options(nomem, nostack)); } if irq::classify_iar(special, 30) != irq::IarClass::Special(1023) || irq::classify_iar(again, 30) != irq::IarClass::Special(1023) || priority_before != priority_after { return fail("hardware special dispatch"); } write_str("TURKOS/IRQ-SPURIOUS-VECTOR: SLOT=5 TRIGGER=0x"); write_hex(trigger as usize); write_str(" SPECIAL=0x"); write_hex(special as usize); write_str(" REPEAT=0x"); write_hex(again as usize); write_line(" TRIGGER-EOI=1 SPECIAL-EOI=0 RPR=UNCHANGED"); unsafe { write_volatile(core::ptr::addr_of_mut!(SEEN), 1) }; 1 } pub(super) fn run(gicr: u64) -> ! { let mpidr: u64; unsafe { asm!("mrs {value}, MPIDR_EL1", value = out(reg) mpidr, options(nomem, nostack)) }; let command = irq::sgi1r_for_mpidr(mpidr, 2).unwrap_or_else(|| park()); unsafe { GICR = gicr; write_volatile(core::ptr::addr_of_mut!(__irq_stack_guard), IRQ_GUARD); #[cfg(feature = "irq-spurious-vector-guard-test")] write_volatile(core::ptr::addr_of_mut!(__irq_stack_guard), 0); } write_line("TURKOS/IRQ-SPURIOUS-VECTOR: ARMED REAL-SGI=2 CONSUME-THEN-SPECIAL"); let seen: u64; let x20: u64; let x21: u64; let q0: u64; let q31: u64; unsafe { asm!( "movi v0.16b, #0x5a", "movi v31.16b, #0xa5", "msr ICC_SGI1R_EL1, {command}", "isb", "msr daifclr, #2", "2:", "ldr {seen}, [{pointer}]", "cbnz {seen}, 3f", "subs {budget}, {budget}, #1", "b.ne 2b", "3:", "msr daifset, #2", "isb", "umov {q0}, v0.d[0]", "umov {q31}, v31.d[0]", command = in(reg) command, pointer = in(reg) core::ptr::addr_of!(SEEN), budget = inout(reg) 10_000_000u64 => _, seen = out(reg) seen, inout("x20") 0x5310u64 => x20, inout("x21") 0x5311u64 => x21, q0 = lateout(reg) q0, q31 = lateout(reg) q31, out("v0") _, out("v31") _, ); } if seen == 1 && x20 == 0x5310 && x21 == 0x5311 && q0 == 0x5a5a_5a5a_5a5a_5a5a && q31 == 0xa5a5_a5a5_a5a5_a5a5 { write_line("TURKOS/IRQ-SPURIOUS-RETURN: ERET GP-SIMD=RESTORED PASS"); } else { fail("ERET witnesses/timeout"); } park() }