//! Isolated one-CPU EL1 timer-preemption experiment. The IRQ return frame is //! the complete task register image; the cooperative CpuContext only enters //! the first task and later returns to the boot observer. use super::*; #[cfg(feature = "task-ipc-timer-test")] use turkos_kernel::ipc::{self, ChannelTable}; #[cfg(feature = "task-ipc-timer-test")] use turkos_kernel::task::TaskId; const STACK_BYTES: usize = 16 * 1024; const A_GUARD: u64 = 0x5455_524b_5052_4541; const B_GUARD: u64 = 0x5455_524b_5052_4542; const TICKS: u64 = if cfg!(feature = "task-fair-test") { 12 } else if cfg!(feature = "task-ipc-timer-test") { 40 } else if cfg!(feature = "task-sleep-test") { 9 } else { 8 }; #[repr(align(4096))] struct Stack([u8; STACK_BYTES]); static mut A_STACK: Stack = Stack([0; STACK_BYTES]); static mut B_STACK: Stack = Stack([0; STACK_BYTES]); static mut A_CALL: task::CpuContext = task::CpuContext::EMPTY; static mut B_CALL: task::CpuContext = task::CpuContext::EMPTY; static mut BOOT_CALL: task::CpuContext = task::CpuContext::EMPTY; static mut A_FRAME: irq::IrqContext = irq::IrqContext::EMPTY; static mut B_FRAME: irq::IrqContext = irq::IrqContext::EMPTY; static mut CURRENT: usize = 0; static mut TICK_COUNT: u64 = 0; static mut A_COUNT: u64 = 0; static mut B_COUNT: u64 = 0; static mut DONE: usize = 0; static mut PERIOD: u64 = 0; static mut DEADLINE: u64 = 0; static mut FIRST_DEADLINE: u64 = 0; static mut A_REG_RESULT: [usize; 4] = [0; 4]; static mut B_REG_RESULT: [usize; 4] = [0; 4]; static mut FAIR_POLICY: task::AgingPair = task::AgingPair::new(); #[cfg(feature = "task-ipc-timer-test")] static mut IPC_CHANNELS: ChannelTable = ChannelTable::new(); #[cfg(feature = "task-ipc-timer-test")] static mut IPC_ID: u64 = 0; #[cfg(feature = "task-ipc-timer-test")] static mut IPC_SENT: u64 = 0; #[cfg(feature = "task-ipc-timer-test")] static mut IPC_RECEIVED: u64 = 0; #[cfg(feature = "task-ipc-timer-test")] static mut IPC_FULL_SEEN: u64 = 0; #[cfg(feature = "task-ipc-timer-test")] static mut IPC_EMPTY_SEEN: u64 = 0; #[cfg(feature = "task-sleep-test")] static mut SLEEP_REQUEST: usize = 0; #[cfg(feature = "task-sleep-test")] static mut SLEEP_ACTIVE: usize = 0; #[cfg(feature = "task-sleep-test")] static mut WAKE_TICK: u64 = 0; #[cfg(feature = "task-sleep-test")] static mut SLEEP_QUEUE: task::SleepQueue = task::SleepQueue::new(); const A_REGS: [usize; 3] = [ 0x4150_5245_5f58_3030, 0x4150_5245_5f58_3333, 0x4150_5245_5f4c_5230, ]; const B_REGS: [usize; 3] = [ 0x4250_5245_5f58_3030, 0x4250_5245_5f58_3333, 0x4250_5245_5f4c_5230, ]; fn fail(reason: &str) -> ! { unsafe { asm!( "msr daifset, #2", "msr CNTP_CTL_EL0, xzr", "isb", options(nomem, nostack) ) }; write_str("TURKOS/PREEMPT-ERROR: "); write_line(reason); park() } fn bounds(slot: usize) -> (usize, usize) { let start = match slot { 0 => unsafe { core::ptr::addr_of_mut!(A_STACK.0).cast::() as usize }, 1 => unsafe { core::ptr::addr_of_mut!(B_STACK.0).cast::() as usize }, _ => fail("invalid task slot"), }; (start, start + STACK_BYTES) } #[inline(never)] fn register_probe(slot: usize) { let expected = if slot == 0 { A_REGS } else { B_REGS }; let target = if slot == 0 { 2usize } else { 3usize }; let x0: usize; let x3: usize; let x30: usize; let observed: usize; let now: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) now, options(nomem, nostack, preserves_flags)) }; let timeout = unsafe { read_volatile(core::ptr::addr_of!(PERIOD)) } .checked_mul(100) .and_then(|duration| now.checked_add(duration)) .unwrap_or_else(|| fail("GP probe timeout overflow")); // The sentinel registers remain live inside one uninterrupted assembly // loop while two physical timer IRQs switch away and back. No software // yield is issued by either task. The physical counter, not a CPU-speed- // dependent spin count, bounds a lost timer to two seconds. unsafe { asm!( "2:", "ldr {observed}, [{flag}]", "cmp {observed}, {target}", "b.hs 3f", "mrs {clock}, CNTPCT_EL0", "cmp {clock}, {timeout}", "b.lo 2b", "3:", flag = in(reg) core::ptr::addr_of!(TICK_COUNT) as usize, target = in(reg) target, observed = out(reg) observed, clock = out(reg) _, timeout = in(reg) timeout, inout("x0") expected[0] => x0, inout("x3") expected[1] => x3, inout("x30") expected[2] => x30, options(nostack) ); } if observed < target || [x0, x3, x30] != expected { write_str("TURKOS/PREEMPT-REG-ERROR: ID="); write_str(if slot == 0 { "A" } else { "B" }); write_str(" X0=0x"); write_hex(x0); write_str(" X3=0x"); write_hex(x3); write_str(" X30=0x"); write_hex(x30); write_str(" TICK=0x"); write_hex(observed); write_str(" TIMEOUT=0x"); write_hex(timeout as usize); write_str("\r\n"); fail("preempted GP registers"); } let result = if slot == 0 { core::ptr::addr_of_mut!(A_REG_RESULT) } else { core::ptr::addr_of_mut!(B_REG_RESULT) }; unsafe { result.write([x0, x3, x30, observed]) }; } #[cfg(feature = "task-ipc-timer-test")] fn ipc_task_step(slot: usize) { let id = unsafe { read_volatile(core::ptr::addr_of!(IPC_ID)) }; if id == 0 { fail("IPC channel missing"); } // A timer may preempt either task outside this bounded critical section. // The channel has no SMP lock, so never allow the peer to enter it while // a producer or consumer is mutating the same ring on this one CPU. unsafe { asm!("msr daifset, #2", "isb", options(nostack, preserves_flags)) }; if slot == 0 { let sent = unsafe { read_volatile(core::ptr::addr_of!(IPC_SENT)) }; if sent < 32 { let byte = sent as u8; let result = unsafe { (&mut *core::ptr::addr_of_mut!(IPC_CHANNELS)).send( id, TaskId { slot: 0, generation: 1, }, &[byte], ) }; match result { Ok(()) => unsafe { write_volatile(core::ptr::addr_of_mut!(IPC_SENT), sent + 1) }, Err(ipc::Error::QueueFull) => unsafe { write_volatile(core::ptr::addr_of_mut!(IPC_FULL_SEEN), 1) }, Err(_) => fail("IPC producer error"), } } } else { let received = unsafe { read_volatile(core::ptr::addr_of!(IPC_RECEIVED)) }; if received < 32 { let result = unsafe { (&mut *core::ptr::addr_of_mut!(IPC_CHANNELS)).receive( id, TaskId { slot: 1, generation: 2, }, ) }; match result { Ok(message) => { if message.bytes() != [received as u8] { fail("IPC FIFO payload"); } unsafe { write_volatile(core::ptr::addr_of_mut!(IPC_RECEIVED), received + 1) }; } Err(ipc::Error::QueueEmpty) => unsafe { write_volatile(core::ptr::addr_of_mut!(IPC_EMPTY_SEEN), 1) }, Err(_) => fail("IPC consumer error"), } } } unsafe { asm!("msr daifclr, #2", "isb", options(nostack, preserves_flags)) }; } fn task_loop(slot: usize) -> ! { let counter = if slot == 0 { core::ptr::addr_of_mut!(A_COUNT) } else { core::ptr::addr_of_mut!(B_COUNT) }; let mut seen = u64::MAX; let mut armed = false; let mut probed = false; loop { if unsafe { read_volatile(core::ptr::addr_of!(DONE)) } != 0 { if unsafe { read_volatile(core::ptr::addr_of!(CURRENT)) } != slot { fail("done task ownership"); } unsafe { asm!("msr daifset, #2", options(nomem, nostack, preserves_flags)) }; let outgoing = if slot == 0 { core::ptr::addr_of_mut!(A_CALL) } else { core::ptr::addr_of_mut!(B_CALL) }; unsafe { __turkos_swap_task_context(outgoing, core::ptr::addr_of!(BOOT_CALL)) }; fail("finished task resumed"); } let tick = unsafe { read_volatile(core::ptr::addr_of!(TICK_COUNT)) }; if tick != seen { let value = unsafe { read_volatile(counter) }; if cfg!(feature = "task-fair-test") || cfg!(feature = "task-ipc-timer-test") || value < 4 { unsafe { write_volatile(counter, value + 1) }; } seen = tick; } #[cfg(feature = "task-sleep-test")] if slot == 1 && unsafe { read_volatile(core::ptr::addr_of!(B_COUNT)) } == 1 && unsafe { read_volatile(core::ptr::addr_of!(SLEEP_REQUEST)) } == 0 { unsafe { write_volatile(core::ptr::addr_of_mut!(SLEEP_REQUEST), 1) }; } #[cfg(feature = "task-sleep-test")] if slot == 1 && unsafe { read_volatile(core::ptr::addr_of!(SLEEP_ACTIVE)) } != 0 { fail("sleeping task executed before wakeup"); } if slot == 0 && !armed { // A has already advanced once. The first timer cannot fire // before A1 exists, because IRQ remains masked until here. let now: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) now, options(nomem, nostack, preserves_flags)) }; let period = unsafe { read_volatile(core::ptr::addr_of!(PERIOD)) }; let first = now .checked_add(period) .unwrap_or_else(|| fail("first deadline overflow")); unsafe { write_volatile(core::ptr::addr_of_mut!(DEADLINE), first); write_volatile(core::ptr::addr_of_mut!(FIRST_DEADLINE), first); asm!( "msr CNTP_CVAL_EL0, {deadline}", "msr CNTP_CTL_EL0, {enable}", "isb", "msr daifclr, #2", deadline = in(reg) first, enable = in(reg) 1usize, options(nomem, nostack) ); } armed = true; } if !cfg!(feature = "task-fair-test") && !cfg!(feature = "task-ipc-timer-test") && !probed { register_probe(slot); probed = true; } #[cfg(feature = "task-ipc-timer-test")] ipc_task_step(slot); core::hint::spin_loop(); } } extern "C" fn entry_a() -> ! { task_loop(0) } extern "C" fn entry_b() -> ! { task_loop(1) } pub(super) fn smoke_test() -> ! { let (a_start, a_top) = bounds(0); let (b_start, b_top) = bounds(1); if a_start & 4095 != 0 || b_start & 4095 != 0 || a_top > b_start && b_top > a_start { fail("stack alignment or overlap"); } unsafe { write_volatile(a_start as *mut u64, A_GUARD); write_volatile(b_start as *mut u64, B_GUARD); write_volatile(core::ptr::addr_of_mut!(__irq_stack_guard), IRQ_GUARD); } #[cfg(feature = "task-ipc-timer-test")] { let channel = unsafe { &mut *core::ptr::addr_of_mut!(IPC_CHANNELS) } .create( TaskId { slot: 0, generation: 1, }, TaskId { slot: 1, generation: 2, }, ) .unwrap_or_else(|_| fail("IPC create")); unsafe { write_volatile(core::ptr::addr_of_mut!(IPC_ID), channel) }; write_line("TURKOS/IPC-TIMER-START: PRODUCER=A CONSUMER=B CAPACITY=0x4 ITEMS=0x20"); } let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; // The fairness probe emits a serial record on every IRQ. Give the // interrupted task time to resume and advance before the next sample; // a 20 ms interval can expire during UART logging under host load. let period_ns = if cfg!(feature = "task-fair-test") { 100_000_000 } else { 20_000_000 }; let period = irq::timer_ticks_for_ns(frequency, period_ns).unwrap_or_else(|| fail("timer frequency")); unsafe { write_volatile(core::ptr::addr_of_mut!(PERIOD), period) }; let a_entry = entry_a as *const () as u64; let b_entry = entry_b as *const () as u64; let a_call = task::CpuContext::initial(a_top as u64, a_entry) .unwrap_or_else(|_| fail("A first context")); unsafe { core::ptr::addr_of_mut!(A_CALL).write(a_call) }; let mut b_frame = irq::IrqContext::EMPTY; b_frame.interrupted_sp = b_top as u64; b_frame.elr = b_entry; b_frame.spsr = 0x345; // EL1h, D/A/F masked, IRQ unmasked. b_frame.vector_slot = 5; unsafe { core::ptr::addr_of_mut!(B_FRAME).write(b_frame) }; for (name, start, top) in [("A", a_start, a_top), ("B", b_start, b_top)] { write_str("TURKOS/PREEMPT-STACK: ID="); write_str(name); write_str(" START=0x"); write_hex(start); write_str(" TOP=0x"); write_hex(top); write_str("\r\n"); } write_str("TURKOS/PREEMPT-ENTRY: A=0x"); write_hex(a_entry as usize); write_str(" B=0x"); write_hex(b_entry as usize); write_str("\r\nTURKOS/PREEMPT-FREQ: 0x"); write_hex(frequency as usize); write_str(" PERIOD=0x"); write_hex(period as usize); write_str("\r\n"); if cfg!(feature = "task-fair-test") { write_line( "TURKOS/FAIR-PRIORITY: A=0x0000000000000002 B=0x0000000000000000 AGE=0x0000000000000001", ); } write_line("TURKOS/PREEMPT: ARMED"); unsafe { __turkos_swap_task_context( core::ptr::addr_of_mut!(BOOT_CALL), core::ptr::addr_of!(A_CALL), ) }; let ticks = unsafe { read_volatile(core::ptr::addr_of!(TICK_COUNT)) }; let a = unsafe { read_volatile(core::ptr::addr_of!(A_COUNT)) }; let b = unsafe { read_volatile(core::ptr::addr_of!(B_COUNT)) }; let done = unsafe { read_volatile(core::ptr::addr_of!(DONE)) }; let a_regs = unsafe { read_volatile(core::ptr::addr_of!(A_REG_RESULT)) }; let b_regs = unsafe { read_volatile(core::ptr::addr_of!(B_REG_RESULT)) }; if ticks != TICKS || (cfg!(feature = "task-fair-test") && (a != 9 || b != 3)) || (cfg!(feature = "task-ipc-timer-test") && (a != 20 || b != 20)) || (!cfg!(feature = "task-fair-test") && !cfg!(feature = "task-ipc-timer-test") && (a != 4 || b != 4)) || done != 1 || (!cfg!(feature = "task-fair-test") && !cfg!(feature = "task-ipc-timer-test") && (a_regs[..3] != A_REGS || b_regs[..3] != B_REGS || a_regs[3] < 2 || b_regs[3] < 3)) || unsafe { read_volatile(a_start as *const u64) } != A_GUARD || unsafe { read_volatile(b_start as *const u64) } != B_GUARD || unsafe { read_volatile(core::ptr::addr_of!(__irq_stack_guard)) } != IRQ_GUARD { fail("final ticks, counts or guards"); } #[cfg(feature = "task-sleep-test")] { if unsafe { read_volatile(core::ptr::addr_of!(SLEEP_REQUEST)) } != 1 || unsafe { read_volatile(core::ptr::addr_of!(SLEEP_ACTIVE)) } != 0 || unsafe { read_volatile(core::ptr::addr_of!(WAKE_TICK)) } != 4 || !unsafe { (&*core::ptr::addr_of!(SLEEP_QUEUE)).is_empty() } { fail("sleep request or wake ownership"); } write_line( "TURKOS/PREEMPT-SLEEP-CHECK: REQUEST=0x0000000000000001 START=0x0000000000000002 WAKE=0x0000000000000004", ); } write_str("TURKOS/PREEMPT-FIRST-DEADLINE: 0x"); write_hex(unsafe { read_volatile(core::ptr::addr_of!(FIRST_DEADLINE)) } as usize); write_str("\r\nTURKOS/PREEMPT-COUNT: 0x"); write_hex(ticks as usize); write_str(" A=0x"); write_hex(a as usize); write_str(" B=0x"); write_hex(b as usize); write_str("\r\nTURKOS/PREEMPT-GUARDS: A=0x"); write_hex(unsafe { read_volatile(a_start as *const u64) } as usize); write_str(" B=0x"); write_hex(unsafe { read_volatile(b_start as *const u64) } as usize); write_str("\r\n"); if cfg!(feature = "task-fair-test") { write_line("TURKOS/FAIR: PASS"); park(); } #[cfg(feature = "task-ipc-timer-test")] { let sent = unsafe { read_volatile(core::ptr::addr_of!(IPC_SENT)) }; let received = unsafe { read_volatile(core::ptr::addr_of!(IPC_RECEIVED)) }; let full = unsafe { read_volatile(core::ptr::addr_of!(IPC_FULL_SEEN)) }; let empty = unsafe { read_volatile(core::ptr::addr_of!(IPC_EMPTY_SEEN)) }; let channel = unsafe { &mut *core::ptr::addr_of_mut!(IPC_CHANNELS) }; let id = unsafe { read_volatile(core::ptr::addr_of!(IPC_ID)) }; if sent != 32 || received != 32 || full != 1 || empty != 1 || channel.queued(id) != Ok(0) { fail("IPC deadline or queue ownership"); } write_str("TURKOS/IPC-TIMER-RESULT: TICKS=0x"); write_hex(ticks as usize); write_str(" A=0x"); write_hex(a as usize); write_str(" B=0x"); write_hex(b as usize); write_str(" SENT=0x"); write_hex(sent as usize); write_str(" RECEIVED=0x"); write_hex(received as usize); write_str(" FULL=0x"); write_hex(full as usize); write_str(" EMPTY=0x"); write_hex(empty as usize); write_str(" QUEUED=0x"); write_hex(channel.queued(id).unwrap_or_else(|_| fail("IPC queue"))); write_str("\r\n"); write_line("TURKOS/IPC-TIMER: PASS"); park(); } #[cfg(not(feature = "task-ipc-timer-test"))] { for (name, regs) in [("A", a_regs), ("B", b_regs)] { write_str("TURKOS/PREEMPT-REG: ID="); write_str(name); write_str(" X0=0x"); write_hex(regs[0]); write_str(" X3=0x"); write_hex(regs[1]); write_str(" X30=0x"); write_hex(regs[2]); write_str(" TICK=0x"); write_hex(regs[3]); write_str("\r\n"); } write_line("TURKOS/PREEMPT: PASS"); park() } } fn reject_irq(iar: usize, reason: &str) -> usize { unsafe { asm!("msr CNTP_CTL_EL0, xzr", "isb", options(nomem, nostack)) }; // Special INTIDs were not acknowledged. Ordinary acknowledgements must // be deactivated even when a frame or deadline check fails. if (iar & 0x00ff_ffff) < 1020 { unsafe { asm!("msr ICC_EOIR1_EL1, {value}", "isb", value = in(reg) iar, options(nomem, nostack)); let control: usize; asm!("mrs {value}, ICC_CTLR_EL1", value = out(reg) control, options(nomem, nostack, preserves_flags)); if control & 2 != 0 { asm!("msr ICC_DIR_EL1, {value}", "isb", value = in(reg) iar, options(nomem, nostack)); } } } write_str("TURKOS/PREEMPT-ERROR: "); write_line(reason); 0 } pub(super) fn return_probe(frame: *const irq::IrqContext) -> usize { let frame_base = core::ptr::addr_of!(__irq_stack_top) as usize - size_of::(); let irq_start = core::ptr::addr_of!(__irq_stack_start) as usize; let mut handler_sp: usize; unsafe { asm!("mov {value}, sp", value = out(reg) handler_sp, options(nomem, nostack, preserves_flags)) }; let slot = unsafe { read_volatile(core::ptr::addr_of!(CURRENT)) }; let (start, top) = bounds(slot); let incoming = unsafe { &*frame }; let interrupted_sp = incoming.interrupted_sp; let text_start = core::ptr::addr_of!(_text_start) as u64; let text_end = core::ptr::addr_of!(_text_end) as u64; let valid = frame as usize == frame_base && irq::handler_sp_in_stack(irq_start, frame_base, handler_sp) && unsafe { read_volatile(core::ptr::addr_of!(__irq_stack_guard)) } == IRQ_GUARD && unsafe { read_volatile(start as *const u64) } == if slot == 0 { A_GUARD } else { B_GUARD } && irq::preempt_task_frame_valid(incoming, start, top, text_start, text_end); let iar: usize; unsafe { asm!("mrs {value}, ICC_IAR1_EL1", value = out(reg) iar, options(nomem, nostack, preserves_flags)) }; if irq::classify_iar(iar as u64, 30) != irq::IarClass::Expected || !valid { return reject_irq(iar, "IRQ frame or source"); } let previous = unsafe { read_volatile(core::ptr::addr_of!(TICK_COUNT)) }; if previous >= TICKS { return reject_irq(iar, "extra tick"); } let tick = previous + 1; let a = unsafe { read_volatile(core::ptr::addr_of!(A_COUNT)) }; let b = unsafe { read_volatile(core::ptr::addr_of!(B_COUNT)) }; let (expected_slot, expected_a, expected_b, next) = if cfg!(feature = "task-fair-test") { const TIMELINE: [(usize, u64, u64, usize); 12] = [ (0, 1, 0, 0), (0, 2, 0, 0), (0, 3, 0, 1), (1, 3, 1, 0), (0, 4, 1, 0), (0, 5, 1, 0), (0, 6, 1, 1), (1, 6, 2, 0), (0, 7, 2, 0), (0, 8, 2, 0), (0, 9, 2, 1), (1, 9, 3, 0), ]; let Some(&(expected_slot, expected_a, expected_b, expected_next)) = TIMELINE.get(previous as usize) else { return reject_irq(iar, "fair timeline overflow"); }; let Ok(next) = (unsafe { &mut *core::ptr::addr_of_mut!(FAIR_POLICY) }).choose(slot) else { return reject_irq(iar, "fair policy task"); }; if next != expected_next { return reject_irq(iar, "priority aging starvation"); } (expected_slot, expected_a, expected_b, next) } else if cfg!(feature = "task-sleep-test") { match tick { 1 => (0, 1, 0, 1), 2 => (1, 1, 1, 0), 3 => (0, 2, 1, 0), 4 => (0, 3, 1, 1), 5 => (1, 3, 2, 0), 6 => (0, 4, 2, 1), 7 => (1, 4, 3, 0), 8 => (0, 4, 3, 1), 9 => (1, 4, 4, 0), _ => return reject_irq(iar, "sleep timeline overflow"), } } else { (previous as usize & 1, (tick + 1) / 2, tick / 2, 1 - slot) }; if a != expected_a || b != expected_b || slot != expected_slot { return reject_irq(iar, "task progress"); } #[cfg(feature = "task-sleep-test")] { if tick == 2 { if unsafe { read_volatile(core::ptr::addr_of!(SLEEP_REQUEST)) } != 1 { return reject_irq(iar, "missing sleep request"); } if unsafe { (&mut *core::ptr::addr_of_mut!(SLEEP_QUEUE)).sleep(1, 4, tick) }.is_err() { return reject_irq(iar, "sleep queue insert"); } unsafe { write_volatile(core::ptr::addr_of_mut!(SLEEP_ACTIVE), 1) }; write_line( "TURKOS/PREEMPT-SLEEP: ID=B START=0x0000000000000002 UNTIL=0x0000000000000004", ); } if tick == 3 && (unsafe { read_volatile(core::ptr::addr_of!(SLEEP_ACTIVE)) } != 1 || !unsafe { (&*core::ptr::addr_of!(SLEEP_QUEUE)).contains(1) } || b != 1) { return reject_irq(iar, "sleeping task ran before deadline"); } if tick == 4 { if unsafe { read_volatile(core::ptr::addr_of!(SLEEP_ACTIVE)) } != 1 || b != 1 || unsafe { (&mut *core::ptr::addr_of_mut!(SLEEP_QUEUE)).wake_due(tick) } != Some(1) { return reject_irq(iar, "wake count or state"); } unsafe { write_volatile(core::ptr::addr_of_mut!(SLEEP_ACTIVE), 0); write_volatile(core::ptr::addr_of_mut!(WAKE_TICK), tick); } write_line("TURKOS/PREEMPT-WAKE: ID=B AT=0x0000000000000004 COUNT=0x0000000000000001"); } } if previous < 2 && !cfg!(feature = "task-fair-test") && !cfg!(feature = "task-ipc-timer-test") { let expected = if slot == 0 { A_REGS } else { B_REGS }; if incoming.x[0] != expected[0] as u64 || incoming.x[3] != expected[1] as u64 || incoming.x[30] != expected[2] as u64 { return reject_irq(iar, "first preempted GP frame"); } write_str("TURKOS/PREEMPT-SAVED: ID="); write_str(if slot == 0 { "A" } else { "B" }); write_str(" X0=0x"); write_hex(incoming.x[0] as usize); write_str(" X3=0x"); write_hex(incoming.x[3] as usize); write_str(" X30=0x"); write_hex(incoming.x[30] as usize); write_str("\r\n"); } let counter: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) counter, options(nomem, nostack, preserves_flags)) }; let old_deadline = unsafe { read_volatile(core::ptr::addr_of!(DEADLINE)) }; if counter < old_deadline { return reject_irq(iar, "early timer"); } if tick < TICKS { let period = unsafe { read_volatile(core::ptr::addr_of!(PERIOD)) }; let Some((next, _)) = irq::next_timer_deadline(old_deadline, period, counter) else { return reject_irq(iar, "deadline overflow"); }; unsafe { write_volatile(core::ptr::addr_of_mut!(DEADLINE), next); asm!("msr CNTP_CVAL_EL0, {value}", "isb", value = in(reg) next, options(nomem, nostack)); } } else { unsafe { asm!("msr CNTP_CTL_EL0, xzr", "isb", options(nomem, nostack)) }; } // The complete interrupted frame remains owned by its task. Copy the // next task's full frame onto the fixed IRQ stack before return.S ERETs. let saved = unsafe { core::ptr::read(frame) }; let saved_ptr = if slot == 0 { core::ptr::addr_of_mut!(A_FRAME) } else { core::ptr::addr_of_mut!(B_FRAME) }; let next_ptr = if next == 0 { core::ptr::addr_of!(A_FRAME) } else { core::ptr::addr_of!(B_FRAME) }; unsafe { saved_ptr.write(saved) }; let replacement = unsafe { core::ptr::read(next_ptr) }; let (next_start, next_top) = bounds(next); if !irq::preempt_task_frame_valid(&replacement, next_start, next_top, text_start, text_end) { return reject_irq(iar, "next frame"); } unsafe { core::ptr::write(frame as *mut irq::IrqContext, replacement) }; // Clear the level source before EOI. EOImode 1 also needs DIR. unsafe { asm!("msr ICC_EOIR1_EL1, {value}", "isb", value = in(reg) iar, options(nomem, nostack)); let control: usize; asm!("mrs {value}, ICC_CTLR_EL1", value = out(reg) control, options(nomem, nostack, preserves_flags)); if control & 2 != 0 { asm!("msr ICC_DIR_EL1, {value}", "isb", value = in(reg) iar, options(nomem, nostack)); } } unsafe { write_volatile(core::ptr::addr_of_mut!(TICK_COUNT), tick); write_volatile(core::ptr::addr_of_mut!(CURRENT), next); if tick == TICKS { write_volatile(core::ptr::addr_of_mut!(DONE), 1); } } write_str("TURKOS/PREEMPT-TICK: INDEX=0x"); write_hex(tick as usize); write_str(" FROM="); write_str(if slot == 0 { "A" } else { "B" }); write_str(" TO="); write_str(if next == 0 { "A" } else { "B" }); write_str(" SP=0x"); write_hex(interrupted_sp as usize); write_str(" NEXT-SP=0x"); write_hex(replacement.interrupted_sp as usize); write_str(" A=0x"); write_hex(a as usize); write_str(" B=0x"); write_hex(b as usize); write_str(" COUNTER=0x"); write_hex(counter as usize); write_str(" IAR=0x"); write_hex(iar); write_str("\r\n"); 1 }