// K1360 native EL0 waitset teardown and peer-death callback race probe. use super::*; use turkos_kernel::runtime::process_table::{Outcome, ProcessId}; use turkos_kernel::runtime::syscall_abi as abi; use super::k1360_teardown_model::{Callback, Error, Kind, ObjectKey, SubscriptionId, Teardown}; static mut OS_K1360_FIRST: Option> = None; static mut OS_K1360_SECOND: Option> = None; static mut OS_K1360_CALLBACK: Option = None; static mut OS_K1360_STAGE: u8 = 0; const CHANNEL: ObjectKey = ObjectKey {kind: Kind::Channel, id: 11}; const TASK: ObjectKey = ObjectKey {kind: Kind::Task, id: 12}; const FILE: ObjectKey = ObjectKey {kind: Kind::File, id: 13}; const FIRST_ID: u64 = (1u64 << 32) | 1; const TASK_ID: u64 = (2u64 << 32) | 2; const FILE_ID: u64 = (3u64 << 32) | 3; fn first() -> &'static mut Teardown { // Mode 63 runs on one CPU with IRQs masked and no concurrent waitset user. unsafe { (&mut *core::ptr::addr_of_mut!(OS_K1360_FIRST)) .as_mut().unwrap_or_else(|| park()) } } fn second() -> &'static mut Teardown { unsafe { (&mut *core::ptr::addr_of_mut!(OS_K1360_SECOND)) .as_mut().unwrap_or_else(|| park()) } } fn stage() -> u8 { unsafe { read_volatile(core::ptr::addr_of!(OS_K1360_STAGE)) } } fn set_stage(value: u8) { unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1360_STAGE), value); } } pub(super) fn begin() { unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1360_FIRST), None); write_volatile(core::ptr::addr_of_mut!(OS_K1360_SECOND), None); write_volatile(core::ptr::addr_of_mut!(OS_K1360_CALLBACK), None); } set_stage(0); } pub(super) fn finish() -> Result<(), &'static str> { let a = first().snapshot(); let b = second().snapshot(); if stage() != 17 || !a.closed || !b.closed || a.resources != 0 || b.resources != 0 || a.rows != [None; 3] || b.rows != [None; 3] || unsafe { read_volatile(core::ptr::addr_of!(OS_K1360_CALLBACK)) }.is_some() { return Err("waitset teardown subscription leak"); } unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1360_FIRST), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1360_SECOND), None); } Ok(()) } pub(super) fn dispatch( episode: &mut OsK1215AEl0Episode, frame: &mut os_k920_exception::Frame, process_table: &mut OsK1105Table<'_>, ) -> u64 { if frame.elr < episode.launch.user_pc || frame.elr >= episode.launch.user_pc + 4096 || !frame.elr.is_multiple_of(4) { park(); } if frame.x[7] == 3 { if episode.case != 1 || episode.calls != 1 || process_table.record(episode.pid).is_err() { park(); } let owner = episode.pid; let arg = frame.x[0]; match frame.x[8] { 100 | 101 | 102 => { let (expected_stage, object, expected_id, next) = match frame.x[8] { 100 => (2, CHANNEL, FIRST_ID, 3), 101 => (3, TASK, TASK_ID, 4), _ => (4, FILE, FILE_ID, 5), }; if stage() != expected_stage || arg != 0 { park(); } let id = first().subscribe(owner, object).unwrap_or_else(|_| park()); if id.raw() != expected_id || first().snapshot().resources != next as usize - 2 { park(); } frame.x[0] = id.raw(); set_stage(next); write_line("TURKOS/OS/K1360C-TRACE: THREE-OBJECT-SUBSCRIPTION"); } 103 => { if stage() != 5 || arg != FIRST_ID { park(); } let callback = first().begin_peer_callback(SubscriptionId::from_raw(arg)) .unwrap_or_else(|_| park()); unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1360_CALLBACK), Some(callback)); } frame.x[0] = 0; set_stage(6); write_line("TURKOS/OS/K1360C-TRACE: PEER-CALLBACK-STARTED"); } 104 => { if stage() != 6 || arg != 0 || first().close(owner) != Ok(3) || first().snapshot().resources != 0 { park(); } frame.x[0] = 3; set_stage(7); write_line("TURKOS/OS/K1360C-TRACE: CLOSE-RELEASED-ALL-THREE"); } 105 => { if stage() != 7 || arg != FIRST_ID { park(); } let callback = unsafe { read_volatile(core::ptr::addr_of!(OS_K1360_CALLBACK)) } .unwrap_or_else(|| park()); let before = first().snapshot(); if first().finish_peer_callback(callback) != Err(Error::Closed) || first().snapshot() != before { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1360_CALLBACK), None); } frame.x[0] = abi::AbiResult::Err(abi::Errno::BadFd).encode(); set_stage(8); write_line("TURKOS/OS/K1360C-TRACE: RACING-PEER-CALLBACK-REJECTED"); } 106 => { if stage() != 8 || arg != 0 { park(); } let before = first().snapshot(); if first().close(owner) != Err(Error::Closed) || first().snapshot() != before { park(); } frame.x[0] = abi::AbiResult::Err(abi::Errno::BadFd).encode(); set_stage(9); } 107 => { if stage() != 9 || arg != 0 { park(); } let before = first().snapshot(); if first().subscribe(owner, CHANNEL) != Err(Error::Closed) || first().snapshot() != before { park(); } frame.x[0] = abi::AbiResult::Err(abi::Errno::BadFd).encode(); set_stage(10); write_line("TURKOS/OS/K1360C-TRACE: CLOSED-WAITSET-CANNOT-RESUBSCRIBE"); } 108 => { if stage() != 10 || arg != 0 { park(); } unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1360_SECOND), Some(Teardown::new(owner))); } if second().subscribe(owner, CHANNEL).map(|id| id.raw()) != Ok(FIRST_ID) || second().subscribe(owner, TASK).map(|id| id.raw()) != Ok(TASK_ID) || second().subscribe(owner, FILE).map(|id| id.raw()) != Ok(FILE_ID) || second().snapshot().resources != 3 { park(); } frame.x[0] = 3; set_stage(11); write_line("TURKOS/OS/K1360C-TRACE: EVENT-FIRST-SET-BOUND-THREE"); } 109 => { if stage() != 11 || arg != FIRST_ID { park(); } let callback = second().begin_peer_callback(SubscriptionId::from_raw(arg)) .unwrap_or_else(|_| park()); unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1360_CALLBACK), Some(callback)); } frame.x[0] = 0; set_stage(12); } 110 => { if stage() != 12 || arg != FIRST_ID { park(); } let callback = unsafe { read_volatile(core::ptr::addr_of!(OS_K1360_CALLBACK)) } .unwrap_or_else(|| park()); if second().finish_peer_callback(callback) != Ok(()) || !second().snapshot().rows[0].unwrap_or_else(|| park()).peer_dead { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1360_CALLBACK), None); } frame.x[0] = 0; set_stage(13); write_line("TURKOS/OS/K1360C-TRACE: PEER-DEATH-WON-BEFORE-CLOSE"); } 111 => { if stage() != 13 || arg != 0 || second().close(owner) != Ok(3) || second().snapshot().resources != 0 { park(); } frame.x[0] = 3; set_stage(14); write_line("TURKOS/OS/K1360C-TRACE: EVENT-FIRST-CLOSE-RELEASED-ALL"); } _ => { frame.x[0] = abi::AbiResult::Err(abi::Errno::NoSys).encode(); } } 0 } else if frame.x[7] == 2 && frame.x[8] == 3 { let request = turkos_os_user_runtime_abi::decode( frame.x[7], frame.x[8], core::array::from_fn(|i| frame.x[i])) .unwrap_or_else(|_| park()); let turkos_os_user_runtime_abi::Request::Exit(code) = request else { park(); }; if episode.case == 0 { if code != 0 || episode.calls != 2 || stage() != 16 { park(); } } else if code != 42 || episode.calls != 1 || stage() != 15 || first().snapshot().resources != 0 || second().snapshot().resources != 0 { park(); } process_table.exit_current(code).unwrap_or_else(|_| park()); let outcome = Outcome::Exited(code); if process_table.record(episode.pid).map(|r| r.outcome) != Ok(Some(outcome)) { park(); } if episode.case == 1 { set_stage(16); } else { set_stage(17); } episode.outcome = Some(outcome); 1 } else { if frame.x[7] != abi::ABI_VERSION { park(); } let mut abi_frame = turkos_os_exceptions::exceptions::Frame::EMPTY; abi_frame.x = frame.x; let call = abi::decode_call(&abi_frame).unwrap_or_else(|_| park()); if call == abi::Call::Role { if episode.calls != 0 { park(); } if episode.case == 0 { if stage() != 0 { park(); } set_stage(1); write_line("TURKOS/OS/K1360C-TRACE: SHELL-READY-FOR-CLOSE"); } else { if stage() != 1 { park(); } let parent = process_table.record(episode.pid) .unwrap_or_else(|_| park()).parent.unwrap_or_else(|| park()); if unsafe { read_volatile(core::ptr::addr_of!(OS_K1215A_INIT_PID)) } != Some(parent) { park(); } unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1360_FIRST), Some(Teardown::new(episode.pid))); } set_stage(2); write_line("TURKOS/OS/K1360C-TRACE: CHILD-OWNS-WAITSET"); } episode.calls = 1; frame.x[0] = episode.case as u64; return 0; } if let abi::Call::RunChild(1) = call { if episode.case != 0 || episode.calls != 1 || stage() != 1 { park(); } return os_k1215a_run_child(episode as *mut _, frame as *mut _, episode.launch.kernel_root); } if let abi::Call::Write(request) = call { if episode.case != 1 || request.fd() != 1 || request.bytes() != 16 || stage() != 14 { park(); } let mut bytes = [0u8; 16]; process_table.copy_current_user_data(episode.pid, request.address(), &mut bytes).unwrap_or_else(|_| park()); if &bytes != b"WAIT:CLOSE:PASS\n" { park(); } for byte in bytes { write_byte(byte); } frame.x[0] = 16; set_stage(15); write_line("TURKOS/OS/K1360C-TRACE: USER-WROTE-CLOSE-RESULT"); return 0; } park() } }