//! K320: physical-counter message timeline for a bounded, sequential EL1 //! two-producer/two-consumer channel. No preemption or parallel CPUs claimed. use super::*; use turkos_kernel::ipc::{self, MessageTimeline, SharedChannel}; use turkos_kernel::task::Scheduler; fn fail(reason: &str) -> ! { write_str("TURKOS/IPC-TIMELINE-ERROR: "); write_line(reason); park() } fn counter() -> u64 { let value: u64; unsafe { asm!("isb", "mrs {value}, CNTPCT_EL0", value = out(reg) value, options(nomem, nostack, preserves_flags)) }; value } pub fn smoke_test() -> ! { let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; if frequency < 1000 { fail("counter frequency"); } let delay = frequency / 1000; let mut scheduler = Scheduler::new(); let a = scheduler.spawn().unwrap_or_else(|_| fail("A spawn")); let b = scheduler.spawn().unwrap_or_else(|_| fail("B spawn")); let c = scheduler.spawn().unwrap_or_else(|_| fail("C spawn")); let d = scheduler.spawn().unwrap_or_else(|_| fail("D spawn")); let mut channel = SharedChannel::new([a, b], [c, d]).unwrap_or_else(|_| fail("channel")); let mut timeline = MessageTimeline::new(); write_line("TURKOS/IPC-TIMELINE-START: PRODUCERS=A,B CONSUMERS=C,D ITEMS=0x5"); for index in 0..5usize { let sender = if index & 1 == 0 { a } else { b }; let receiver = if index & 1 == 0 { c } else { d }; let byte = 0x41 + index as u8; let sent = counter(); if channel.send(sender, &[byte]) != Ok(index as u64 + 1) { fail("message sequence"); } let target = sent .checked_add(delay) .unwrap_or_else(|| fail("counter target overflow")); let timeout = sent .checked_add(frequency / 10) .unwrap_or_else(|| fail("counter deadline overflow")); loop { let now = counter(); if now >= target { break; } if now >= timeout { fail("clock did not progress"); } core::hint::spin_loop(); } let message = channel .receive(receiver) .unwrap_or_else(|_| fail("receive")); let received = counter(); if message.sequence != index as u64 + 1 || message.message.bytes() != [byte] || channel.queued() != 0 || channel.receive(receiver) != Err(ipc::Error::QueueEmpty) { fail("FIFO, payload or duplicate receive"); } let latency = timeline .record(message.sequence, sent, received) .unwrap_or_else(|_| fail("timeline order")); if latency < delay || latency > frequency / 10 { fail("latency out of bounded window"); } write_str("TURKOS/IPC-TIMELINE-ITEM: INDEX=0x"); write_hex(index); write_str(" SEQ=0x"); write_hex(message.sequence as usize); write_str(" FROM="); write_str(if index & 1 == 0 { "A" } else { "B" }); write_str(" TO="); write_str(if index & 1 == 0 { "C" } else { "D" }); write_str(" SEND=0x"); write_hex(sent as usize); write_str(" RECEIVE=0x"); write_hex(received as usize); write_str(" LATENCY=0x"); write_hex(latency as usize); write_str(" VALUE=0x"); write_hex(byte as usize); write_str(" QUEUED=0x"); write_hex(channel.queued()); write_str("\r\n"); } let (count, min, max, total) = timeline.summary(); if count != 5 || min < delay || max > frequency / 10 || total < min * count || total > max * count { fail("summary"); } write_str("TURKOS/IPC-TIMELINE-SUMMARY: FREQ=0x"); write_hex(frequency as usize); write_str(" COUNT=0x"); write_hex(count as usize); write_str(" MIN=0x"); write_hex(min as usize); write_str(" MAX=0x"); write_hex(max as usize); write_str(" TOTAL=0x"); write_hex(total as usize); write_str(" READY=0x"); write_hex(scheduler.ready_len()); write_str("\r\n"); write_line("TURKOS/IPC-TIMELINE: PASS"); park() }