// K1320 native EL0 shared-reference and last-close notification probe. // The Channel object is a kernel-minted probe name. The wait is a nonowning // poll token; this gate does not exercise a blocking scheduler wake. use super::*; use turkos_kernel::runtime::handles::{CloseResult, Error, Handle, Object, Rights, SharedObjects, WaitResult, WaitToken}; use turkos_kernel::runtime::process_table::{Outcome, ProcessId}; use turkos_kernel::runtime::syscall_abi as abi; static mut OS_K1320_SHARED: SharedObjects = SharedObjects::new(); static mut OS_K1320_STAGE: u8 = 0; const SHELL: u64 = (1u64 << 32) | 1; const CHILD: u64 = (1u64 << 32) | 2; const HEALTHY: u64 = (2u64 << 32) | 1; fn shared() -> &'static mut SharedObjects { // Mode 55 runs on one CPU with IRQs masked and no concurrent table user. unsafe { &mut *core::ptr::addr_of_mut!(OS_K1320_SHARED) } } fn stage() -> u8 { unsafe { read_volatile(core::ptr::addr_of!(OS_K1320_STAGE)) } } fn set_stage(value: u8) { unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1320_STAGE), value); } } pub(super) fn begin() { unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1320_SHARED), SharedObjects::new()); } set_stage(0); } pub(super) fn finish() -> Result<(), &'static str> { let snapshot = shared().snapshot(); if stage() != 15 || shared().handles() != 0 || shared().live_resources() != 0 || snapshot.generations != [2, 1] || snapshot.entries != [None; 2] || snapshot.resources != [None; 2] || snapshot.watchers != [None; 2] || snapshot.next_wait != 2 { return Err("shared lifetime owner leak"); } 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 child = episode.pid; match frame.x[8] { 27 => { let (handle, object, next) = match stage() { 2 => (CHILD, Object::Channel(52), 3), 11 => (HEALTHY, Object::Channel(53), 12), _ => park(), }; if frame.x[0] != handle || shared().query(child, Handle::from_raw(handle)) != Ok(object) { park(); } frame.x[0] = match object { Object::Channel(id) => id, _ => park() }; set_stage(next); 0 } 28 => { if stage() != 3 || frame.x[0] != CHILD { park(); } let token = shared().watch_close(child, Handle::from_raw(CHILD)) .unwrap_or_else(|_| park()); if token.raw() != 1 || shared().snapshot().watchers[0].is_none() { park(); } frame.x[0] = token.raw(); set_stage(4); write_line("TURKOS/OS/K1320L-TRACE: CHILD-WATCH-REGISTERED-NONOWNING"); 0 } 29 => { if frame.x[0] != 1 { park(); } let token = WaitToken::from_raw(frame.x[0]); match stage() { 4 | 6 => { let before = shared().snapshot(); if shared().poll_close(child, token) != Ok(WaitResult::Pending) || shared().snapshot() != before { park(); } frame.x[0] = 0; set_stage(if stage() == 4 { 5 } else { 7 }); write_line("TURKOS/OS/K1320L-TRACE: WAIT-PENDING"); } 8 => { if shared().poll_close(child, token) != Ok(WaitResult::Closed) || shared().snapshot().resources != [None; 2] { park(); } frame.x[0] = 1; set_stage(9); write_line("TURKOS/OS/K1320L-TRACE: WAITER-CLOSED-ONCE"); } 9 => { let before = shared().snapshot(); if shared().poll_close(child, token) != Err(Error::BadFd) || shared().snapshot() != before { park(); } frame.x[0] = abi::AbiResult::Err(abi::Errno::BadFd).encode(); set_stage(10); write_line("TURKOS/OS/K1320L-TRACE: REPEATED-WAIT-EBADF"); } _ => park(), } 0 } 30 => { if stage() != 5 || frame.x[0] != 0 { park(); } let parent = process_table.record(child) .unwrap_or_else(|_| park()).parent.unwrap_or_else(|| park()); if shared().close(parent, Handle::from_raw(SHELL)) != Ok(CloseResult::Remaining(1)) || shared().owned(parent) != 0 || shared().owned(child) != 1 || shared().live_resources() != 1 { park(); } frame.x[0] = 1; set_stage(6); write_line("TURKOS/OS/K1320L-TRACE: PARENT-CLOSE-ONE-CHILD-REF-HEALTHY"); 0 } 31 => { let (handle, next) = match stage() { 7 => (CHILD, 8), 12 => (HEALTHY, 13), _ => park(), }; if frame.x[0] != handle || shared().close(child, Handle::from_raw(handle)) != Ok(CloseResult::LastClosed) || shared().handles() != 0 || shared().live_resources() != 0 { park(); } if stage() == 7 { if shared().snapshot().resources[0] != Some((Object::Channel(52), 0, true)) { park(); } write_line("TURKOS/OS/K1320L-TRACE: LAST-CLOSE-PUBLISHES-CLOSED"); } else { write_line("TURKOS/OS/K1320L-TRACE: HEALTHY-SECOND-OBJECT-CLOSED"); } frame.x[0] = 0; set_stage(next); 0 } 32 => { if stage() != 10 || frame.x[0] != 0 { park(); } let handle = shared().open_kernel(child, Object::Channel(53), Rights::WRITE).unwrap_or_else(|_| park()); if handle.raw() != HEALTHY || shared().live_resources() != 1 { park(); } frame.x[0] = handle.raw(); set_stage(11); write_line("TURKOS/OS/K1320L-TRACE: HEALTHY-NEW-OBJECT-AFTER-CLOSED"); 0 } _ => { 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() != 15 { park(); } } else if code != 42 || episode.calls != 1 || stage() != 14 || shared().owned(episode.pid) != 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 { if shared().close_owner(episode.pid) != 0 || shared().live_resources() != 0 { park(); } set_stage(15); write_line("TURKOS/OS/K1320L-TRACE: CHILD-EXIT-OWNER-EMPTY"); } 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(); } let handle = shared().open_kernel(episode.pid, Object::Channel(52), Rights::WRITE) .unwrap_or_else(|_| park()); if handle.raw() != SHELL || shared().live_resources() != 1 { park(); } set_stage(1); write_line("TURKOS/OS/K1320L-TRACE: SHELL-OWNS-FIRST-REFERENCE"); } 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(); } let handle = shared().grant_kernel(parent, episode.pid, Handle::from_raw(SHELL), Rights::WRITE) .unwrap_or_else(|_| park()); if handle.raw() != CHILD || shared().handles() != 2 || shared().live_resources() != 1 || shared().snapshot().resources[0] != Some((Object::Channel(52), 2, false)) { park(); } set_stage(2); write_line("TURKOS/OS/K1320L-TRACE: TWO-REAL-PROCESSES-TWO-REFERENCES"); } 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() != 13 { 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"CLOSED:ONCE:52!\n" { park(); } for byte in bytes { write_byte(byte); } frame.x[0] = 16; set_stage(14); write_line("TURKOS/OS/K1320L-TRACE: USER-WROTE-CLOSED-RESULT"); return 0; } park() } }