//! Sequential EL1 model for a two-producer/two-consumer total FIFO order. //! IDs are minted by the real Scheduler, but there is no task context switch //! or SMP claim in this image. use super::*; use turkos_kernel::ipc::{self, MAX_MESSAGE_BYTES, SharedChannel}; use turkos_kernel::task::{Scheduler, TaskId}; fn fail(reason: &str) -> ! { write_str("TURKOS/IPC-MULTI-ERROR: "); write_line(reason); park() } fn check_item(channel: &mut SharedChannel, caller: TaskId, sequence: u64, bytes: &[u8]) { let item = channel .receive(caller) .unwrap_or_else(|_| fail("shared receive")); if item.sequence != sequence || item.message.bytes() != bytes { fail("shared FIFO order or single delivery"); } } pub(super) fn smoke_test() -> ! { 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")); if [a.slot, b.slot, c.slot, d.slot] != [0, 1, 2, 3] || !matches!( SharedChannel::new([a, a], [c, d]), Err(ipc::Error::SameEndpoint) ) { fail("four distinct owners"); } let mut channel = SharedChannel::new([a, b], [c, d]).unwrap_or_else(|_| fail("shared channel create")); write_line("TURKOS/IPC-MULTI-START: A=0 B=1 C=2 D=3 OWNERS=DISTINCT"); if channel.send(a, b"A1") != Ok(1) || channel.send(b, b"B1") != Ok(2) || channel.send(a, b"A2") != Ok(3) || channel.send(b, b"B2") != Ok(4) || channel.queued() != 4 { fail("two-producer total order"); } write_line( "TURKOS/IPC-MULTI-PRODUCERS: OWNERS=A,B,A,B SEQ=1,2,3,4 VALUES=A1,B1,A2,B2 QUEUED=4", ); if channel.send(c, b"BAD") != Err(ipc::Error::WrongProducer) || channel.send(a, b"FULL") != Err(ipc::Error::QueueFull) || channel.send(a, &[0x58; MAX_MESSAGE_BYTES + 1]) != Err(ipc::Error::TooLarge) || channel.queued() != 4 { fail("unauthorized, full and oversize must be atomic"); } write_line( "TURKOS/IPC-MULTI-REJECT: FOREIGN=WRONG-PRODUCER FULL=QUEUE-FULL OVERSIZE=TOO-LARGE QUEUED=4", ); let stale_d = TaskId { slot: d.slot, generation: d.generation + 1, }; if !matches!(channel.receive(a), Err(ipc::Error::WrongConsumer)) || !matches!(channel.receive(stale_d), Err(ipc::Error::WrongConsumer)) || channel.queued() != 4 { fail("consumer owner and generation"); } check_item(&mut channel, c, 1, b"A1"); check_item(&mut channel, d, 2, b"B1"); check_item(&mut channel, c, 3, b"A2"); check_item(&mut channel, d, 4, b"B2"); write_line("TURKOS/IPC-MULTI-CONSUMERS: OWNERS=C,D,C,D SEQ=1,2,3,4 VALUES=A1,B1,A2,B2"); if !matches!(channel.receive(c), Err(ipc::Error::QueueEmpty)) || !matches!(channel.receive(d), Err(ipc::Error::QueueEmpty)) || channel.queued() != 0 { fail("message delivered more than once"); } write_line("TURKOS/IPC-MULTI-EMPTY: C=QUEUE-EMPTY D=QUEUE-EMPTY STALE=WRONG-CONSUMER QUEUED=0"); if channel.send(b, b"B3") != Ok(5) { fail("sequence after ring wrap"); } check_item(&mut channel, c, 5, b"B3"); if channel.queued() != 0 { fail("final shared queue"); } write_line("TURKOS/IPC-MULTI-NEXT: OWNER=B SEQ=5 VALUE=B3 QUEUED=0"); write_line("TURKOS/IPC-MULTI: PASS"); park() }