use core::{array, marker::PhantomData, mem}; use turkos_os_frame_metadata::metadata; use turkos_os_process_lifecycle_owners::{authority as a, process_table as p, spawn::Execution}; use turkos_os_threads::threads; pub const MAX_BUFFERS: usize = 16; /// Pure ingress check for a future raw native adapter, before forming any /// mutable Runtime/backend loan. The observation must already come from the /// trusted actual root/stack observer, never a user requested context. pub fn task_ingress(e: Execution, cpu: u16) -> Result<(), Error> { if cpu == u16::MAX || e.process.cpu != cpu || !e.process.irq_masked || e.process.in_irq || e.root == 0 || e.root & 4095 != 0 || e.kernel_stack_owner == 0 { return Err(Error::Context); } Ok(()) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Configuration, Context, Foreign, Stale, Capacity, NoMem, Invalid, Backend, Resource, Quarantined, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Process { pub record: p::Record, pub principal: a::Principal, pub baseline: a::Usage, pub page_limit: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Owned { pub process: p::ProcessId, pub principal: a::Principal, pub frame: metadata::Handle, pub owner: u64, pub pins: usize, pub rw: bool, pub capacity: usize, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Inventory { pub pages: usize, pub pins: usize, } pub struct Acquisition { pub result: Result<(), Error>, pub actual: Option, } pub struct Release { pub result: Result<(), Error>, pub actual: Option, pub consumed: bool, } pub struct Terminal { pub result: Result<(), Error>, pub committed: bool, } pub struct Write { pub result: Result<(), Error>, pub committed: bool, } /// # Safety /// Own real Lifecycle/Authority/Spawn process bags and exact nonclone Registry /// handles/Pins, with actual full PID/Principal and RW authority. Execution is /// pure actual CPU/I1/nonIRQ/current/bootstrap/trusted root/stack observation, /// and callbacks must full-gate BEFORE every owner access and full-post BEFORE /// later owner access. process includes real Authority usage and a kernel-issued /// quota. inventory is exact actual retained buffer frames/Pins for that full /// PID. acquire wholly zeros/verifies the page before publishing a Pin. It /// returns every actual partial owner on error/drift. release clears genuinely /// consumed tokens before postcheck, returns remaining owners, and reports /// consumed iff the complete page owner is gone, even on post-drift. write /// reports committed iff the exact byte changed, even on later drift; read/write /// validate real owner/full frame generation/Pin/RW rights. terminal stops only /// current via real Lifecycle, committed only after actual primary stop; no /// kernel park/callback loop, synthetic token/phase or destructor retirement. pub unsafe trait Backend { type Token; fn owning_cpu(&self) -> u16; fn execution(&self) -> Execution; fn process(&self) -> Result; fn inventory(&self, id: p::ProcessId) -> Result; fn acquire(&mut self, process: Process, capacity: usize) -> Acquisition; fn observe(&self, token: &Self::Token) -> Result; fn read(&self, token: &Self::Token, offset: usize) -> Result; fn write(&mut self, token: &mut Self::Token, offset: usize, byte: u8) -> Write; fn release(&mut self, token: Self::Token) -> Release; fn terminal(&mut self, code: i32) -> Terminal; } pub struct Buffer { namespace: u64, slot: usize, generation: u64, process: p::ProcessId, principal: a::Principal, capacity: usize, } impl Buffer { pub fn capacity(&self) -> usize { self.capacity } } pub struct FreeError { pub error: Error, pub buffer: Option, } struct Slot { generation: u64, token: Option, owned: Option, process: Option, length: usize, quarantined: bool, } pub struct Runtime { backend: Option, namespace: u64, cpu: u16, slots: [Slot; H], terminal: [Option; 16], faulted: bool, _local: PhantomData<*mut ()>, } #[cfg(not(target_os = "none"))] fn identity() -> Option { use core::sync::atomic::{AtomicU64, Ordering}; static NEXT: AtomicU64 = AtomicU64::new(0); NEXT.try_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1)) .ok() .map(|n| n + 1) } #[cfg(target_os = "none")] fn identity() -> Option { unsafe extern "C" { fn __os_k1220_runtime_identity() -> u64; } let n = unsafe { __os_k1220_runtime_identity() }; (n != 0).then_some(n) } impl Runtime { #[cfg(all(feature = "host-fixture", not(target_os = "none")))] pub fn fixture_backend(&mut self) -> &mut B { self.backend.as_mut().unwrap() } pub fn new(backend: B) -> Result { let e = backend.execution(); let cpu = backend.owning_cpu(); if backend.execution() != e { return Err((Error::Context, backend)); } if H == 0 || H > MAX_BUFFERS { return Err((Error::Configuration, backend)); } if !Self::valid(e, cpu) || e.process.current.is_some() || !e.process.kernel_bootstrap { return Err((Error::Context, backend)); } let Some(namespace) = identity() else { return Err((Error::Capacity, backend)); }; Ok(Self { backend: Some(backend), namespace, cpu, slots: array::from_fn(|_| Slot { generation: 0, token: None, owned: None, process: None, length: 0, quarantined: false, }), terminal: [None; 16], faulted: false, _local: PhantomData, }) } #[cfg(all(feature = "host-fixture", not(target_os = "none")))] pub fn fixture_lengths(&self) -> [usize; H] { array::from_fn(|i| self.slots[i].length) } #[cfg(all(feature = "host-fixture", not(target_os = "none")))] pub fn fixture_token_parts( &self, mut inert: impl FnMut(&B::Token) -> [bool; 2], ) -> [[bool; 2]; H] { array::from_fn(|i| { self.slots[i] .token .as_ref() .map(&mut inert) .unwrap_or([false; 2]) }) } fn backend(&self) -> &B { self.backend.as_ref().unwrap() } fn valid(e: Execution, cpu: u16) -> bool { task_ingress(e, cpu).is_ok() } fn gate(&self) -> Result { if self.faulted { return Err(Error::Quarantined); } let e = self.backend().execution(); if !Self::valid(e, self.cpu) { return Err(Error::Context); } Ok(e) } fn post(&mut self, before: Execution) -> Result<(), Error> { if self.backend().execution() != before { self.faulted = true; return Err(Error::Context); } Ok(()) } fn current(&mut self) -> Result { let before = self.gate()?; let id = before.process.current.ok_or(Error::Context)?; let observed = self.backend().process(); self.post(before)?; let process = observed?; if process.record.id != id || process.principal.process() != id || process.record.resources.process != id || !process.record.resources.published || process.record.resources.state != threads::State::Running || process.record.exit_code.is_some() || self.terminal.contains(&Some(id)) { return Err(Error::Resource); } Ok(process) } fn inventory(&mut self, id: p::ProcessId) -> Result { let before = self.gate()?; let r = self.backend().inventory(id); self.post(before)?; r } fn checked(&mut self, handle: &Buffer) -> Result { let current = self.current()?; if handle.namespace != self.namespace || handle.process != current.record.id || handle.principal != current.principal { return Err(Error::Foreign); } let slot = self.slots.get(handle.slot).ok_or(Error::Stale)?; if slot.generation != handle.generation || slot.token.is_none() { return Err(Error::Stale); } if slot.quarantined { return Err(Error::Quarantined); } let expected = slot.owned.ok_or(Error::Resource)?; let before = self.gate()?; let actual = self .backend() .observe(self.slots[handle.slot].token.as_ref().unwrap()); self.post(before)?; if actual != Ok(expected) { return Err(Error::Resource); } Ok(handle.slot) } pub fn allocate(&mut self, capacity: usize, alignment: usize) -> Result { if capacity == 0 || capacity > 4096 || !alignment.is_power_of_two() || alignment > 4096 { return Err(Error::Invalid); } let process = self.current()?; let slot = self .slots .iter() .position(|s| s.token.is_none() && !s.quarantined && s.generation != u64::MAX) .ok_or(Error::Capacity)?; let generation = self.slots[slot] .generation .checked_add(1) .ok_or(Error::Capacity)?; let before_inventory = self.inventory(process.record.id)?; if process .baseline .pages .checked_add(before_inventory.pages as u64) .and_then(|n| n.checked_add(1)) .is_none_or(|n| n > process.page_limit) { return Err(Error::NoMem); } let before = self.gate()?; let acquired = self.backend.as_mut().unwrap().acquire(process, capacity); self.slots[slot].token = acquired.actual; self.slots[slot].process = self.slots[slot].token.as_ref().map(|_| process.record.id); if self.post(before).is_err() { self.slots[slot].quarantined = true; return Err(Error::Context); } if let Err(e) = acquired.result { if self.slots[slot].token.is_some() { self.slots[slot].quarantined = true; } return Err(e); } let before = self.gate()?; let actual = match self.slots[slot].token.as_ref() { Some(t) => self.backend().observe(t), None => Err(Error::Resource), }; if self.post(before).is_err() { self.slots[slot].quarantined = true; return Err(Error::Context); } let owned = match actual { Ok(o) => o, Err(e) => { self.slots[slot].quarantined = true; return Err(e); } }; let after_inventory = match self.inventory(process.record.id) { Ok(i) => i, Err(e) => { self.slots[slot].quarantined = true; return Err(e); } }; if owned.process != process.record.id || owned.principal != process.principal || owned.frame.generation() == 0 || owned.frame.address() == 0 || owned.frame.address() & 4095 != 0 || owned.owner != process.record.resources.resource_owner || owned.pins != 1 || !owned.rw || owned.capacity != capacity || self .slots .iter() .enumerate() .any(|(i, s)| i != slot && s.owned.is_some_and(|prior| prior.frame == owned.frame)) || after_inventory.pages != before_inventory.pages + 1 || after_inventory.pins != before_inventory.pins + 1 { self.slots[slot].quarantined = true; return Err(Error::Resource); } self.slots[slot].owned = Some(owned); for offset in 0..4096 { let before = self.gate()?; let byte = self .backend() .read(self.slots[slot].token.as_ref().unwrap(), offset); if self.post(before).is_err() { self.slots[slot].quarantined = true; return Err(Error::Context); } if byte != Ok(0) { self.slots[slot].quarantined = true; return Err(Error::Resource); } } self.slots[slot].generation = generation; Ok(Buffer { namespace: self.namespace, slot, generation, process: process.record.id, principal: process.principal, capacity, }) } pub fn push(&mut self, buffer: &Buffer, byte: u8) -> Result<(), Error> { let slot = self.checked(buffer)?; let offset = self.slots[slot].length; if offset >= buffer.capacity { return Err(Error::Capacity); } let before = self.gate()?; let result = self.backend.as_mut().unwrap().write( self.slots[slot].token.as_mut().unwrap(), offset, byte, ); if result.committed { self.slots[slot].length += 1; } self.post(before)?; result.result?; if !result.committed { self.faulted = true; return Err(Error::Resource); } Ok(()) } pub fn read(&mut self, buffer: &Buffer, offset: usize) -> Result { let slot = self.checked(buffer)?; if offset >= self.slots[slot].length { return Err(Error::Invalid); } let before = self.gate()?; let result = self .backend() .read(self.slots[slot].token.as_ref().unwrap(), offset); self.post(before)?; result } fn free_slot(&mut self, slot: usize) -> Result { let before = self.gate()?; let token = self.slots[slot].token.take().ok_or(Error::Stale)?; let result = self.backend.as_mut().unwrap().release(token); self.slots[slot].token = result.actual; if result.consumed { self.slots[slot].owned = None; self.slots[slot].process = None; self.slots[slot].length = 0; } else { self.slots[slot].quarantined = true; } self.post(before)?; if result.consumed && self.slots[slot].token.is_some() { self.faulted = true; return Err(Error::Resource); } result.result?; if !result.consumed { self.faulted = true; return Err(Error::Resource); } Ok(true) } pub fn free(&mut self, buffer: Buffer) -> Result<(), FreeError> { let slot = match self.checked(&buffer) { Ok(i) => i, Err(error) => { return Err(FreeError { error, buffer: Some(buffer), }); } }; match self.free_slot(slot) { Ok(_) => Ok(()), Err(error) => Err(FreeError { error, buffer: self.slots[slot].token.is_some().then_some(buffer), }), } } pub fn exit(&mut self, code: i32) -> Result<(), Error> { let process = self.current()?; let terminal_slot = process.record.id.slot(); if terminal_slot >= self.terminal.len() { return Err(Error::Resource); } if self .slots .iter() .any(|s| s.process == Some(process.record.id) && s.quarantined) { return Err(Error::Quarantined); } for slot in 0..H { if self.slots[slot] .owned .is_some_and(|o| o.process == process.record.id) { self.free_slot(slot)?; } } let before = self.gate()?; let result = self.backend.as_mut().unwrap().terminal(code); if result.committed { self.terminal[terminal_slot] = Some(process.record.id); } let mut expected = before; if result.committed { expected.process.current = None; } self.post(expected)?; result.result?; if !result.committed { self.faulted = true; return Err(Error::Resource); } Ok(()) } pub fn panic(&mut self) -> Result<(), Error> { match self.exit(turkos_os_user_runtime_abi::PANIC_EXIT) { Err(Error::Context) => Ok(()), result => result, } } pub fn quarantined(&self) -> bool { self.faulted || self.slots.iter().any(|s| s.quarantined) } // Errors retain the entire actual owner graph without allocating a box. #[allow(clippy::result_large_err)] pub fn into_backend(mut self) -> Result { if let Err(e) = self.gate() { return Err((e, self)); } if self .slots .iter() .any(|s| s.token.is_some() || s.quarantined) { return Err((Error::Resource, self)); } Ok(self.backend.take().unwrap()) } } impl Drop for Runtime { fn drop(&mut self) { for slot in &mut self.slots { if let Some(t) = slot.token.take() { mem::forget(t); } } if let Some(b) = self.backend.take() { mem::forget(b); } } }