use core::{array, marker::PhantomData, mem}; use turkos_os_address_spaces::spaces; use turkos_os_frame_metadata::metadata; use turkos_os_threads::threads; pub const MAX_PROCESSES: usize = 16; pub const MAX_SPACES: usize = 2; pub const MAX_HANDLES: usize = 8; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Configuration, Context, Foreign, Stale, Capacity, Exhausted, State, Resource, Preparation, Publication, Busy, NoChild, Quarantined, Backend, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ProcessId { table: u64, slot: usize, generation: u64, } impl ProcessId { pub fn table(self) -> u64 { self.table } pub fn slot(self) -> usize { self.slot } pub fn generation(self) -> u64 { self.generation } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Execution { pub cpu: u16, pub in_irq: bool, pub irq_masked: bool, pub current: Option, pub kernel_bootstrap: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Spawn { pub handles: u8, pub code_entry: u64, pub user_stack_top: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Boundary { Before, After, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Failure { pub error: Error, pub boundary: Boundary, } pub struct BuildFailure { pub failure: Failure, pub partial: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BagObservation { pub process: ProcessId, pub resource_owner: u64, pub thread: threads::ThreadId, pub state: threads::State, pub spaces: [Option; MAX_SPACES], pub handles: [Option; MAX_HANDLES], pub pages: u64, pub image_ready: bool, pub stack_ready: bool, pub published: bool, } /// # Safety /// Bag is a nonclone exclusive custodian of every partial resource. Observe /// checks actual frozen Table/Spaces/Registry owners, not caller metadata. /// Publish enqueues only a completely prepared real thread; exit makes that /// thread Dead before returning. A Before error never mutates an owner. /// Release proves inactive roots, no current SP/pin/IRQ user, erases and /// returns all backing. Failure returns the identical Bag. Backend and Bag /// Drop must not silently release resources. No mutable owner escapes. pub unsafe trait Backend { type Bag; fn execution(&self) -> Execution; fn prepare( &mut self, id: ProcessId, spawn: Spawn, ) -> Result>; fn observe(&self, bag: &Self::Bag) -> Result; fn publish(&mut self, bag: &mut Self::Bag) -> Result<(), Failure>; fn exit(&mut self, bag: &mut Self::Bag, code: i32) -> Result<(), Failure>; fn release(&mut self, bag: Self::Bag) -> Result<(), (Error, Self::Bag)>; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Phase { Vacant, Building, Prepared, Published, Zombie, Quarantined, } #[derive(Debug, PartialEq, Eq)] pub struct Reservation { table: u64, slot: usize, episode: u64, } impl Drop for Reservation { fn drop(&mut self) {} } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Record { pub id: ProcessId, pub parent: Option, pub resources: BagObservation, pub exit_code: Option, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Snapshot { pub published: usize, pub constructing: usize, pub zombies: usize, pub quarantined: usize, } struct Entry { generation: u64, episode: u64, phase: Phase, parent: Option, spawn: Option, bag: Option, baseline: Option, exit_code: Option, } pub struct Table { backend: Option, identity: u64, cpu: u16, next_episode: u64, entries: [Entry; N], _local: PhantomData<*mut ()>, } #[cfg(not(target_os = "none"))] fn identity() -> Result { use core::sync::atomic::{AtomicU64, Ordering}; static NEXT: AtomicU64 = AtomicU64::new(0); NEXT.try_update(Ordering::AcqRel, Ordering::Acquire, |n| n.checked_add(1)) .map(|n| n + 1) .map_err(|_| Error::Exhausted) } #[cfg(target_os = "none")] fn identity() -> Result { unsafe extern "C" { fn __os_k1120_process_identity() -> u64; } match unsafe { __os_k1120_process_identity() } { 0 => Err(Error::Exhausted), n => Ok(n), } } impl Table { #[cfg(all(feature = "host-fixture", not(target_os = "none")))] pub fn process_test_backend(&mut self) -> &mut B { self.backend.as_mut().expect("owned test backend") } #[cfg(all(feature = "host-fixture", not(target_os = "none")))] pub fn process_test_bag(&self, slot: usize) -> Option<&B::Bag> { self.entries.get(slot).and_then(|entry| entry.bag.as_ref()) } pub fn new(backend: B) -> Result { let e = backend.execution(); if N == 0 || N > MAX_PROCESSES { return Err((Error::Configuration, backend)); } if e.cpu == u16::MAX || e.in_irq || !e.irq_masked || e.current.is_some() { return Err((Error::Context, backend)); } let identity = match identity() { Ok(n) => n, Err(e) => return Err((e, backend)), }; Ok(Self { backend: Some(backend), identity, cpu: e.cpu, next_episode: 0, entries: array::from_fn(|_| Entry { generation: 0, episode: 0, phase: Phase::Vacant, parent: None, spawn: None, bag: None, baseline: None, exit_code: None, }), _local: PhantomData, }) } fn backend(&self) -> &B { self.backend.as_ref().expect("owned backend") } fn gate(&self) -> Result { let e = self.backend().execution(); if e.cpu != self.cpu || e.in_irq || !e.irq_masked { return Err(Error::Context); } Ok(e) } fn public_index(&self, id: ProcessId) -> Result { if id.table != self.identity { return Err(Error::Foreign); } let e = self.entries.get(id.slot).ok_or(Error::Stale)?; if e.generation != id.generation || id.generation == 0 { return Err(Error::Stale); } if !matches!(e.phase, Phase::Published | Phase::Zombie) { return Err(Error::Stale); } Ok(id.slot) } fn reservation_index(&self, r: &Reservation) -> Result { if r.table != self.identity { return Err(Error::Foreign); } let e = self.entries.get(r.slot).ok_or(Error::Stale)?; if e.episode != r.episode || !matches!(e.phase, Phase::Building | Phase::Prepared) { return Err(if e.phase == Phase::Quarantined { Error::Quarantined } else { Error::Stale }); } Ok(r.slot) } fn proposed(&self, i: usize) -> Result { Ok(ProcessId { table: self.identity, slot: i, generation: self.entries[i] .generation .checked_add(1) .ok_or(Error::Exhausted)?, }) } pub fn begin(&mut self, spawn: Spawn) -> Result { let e = self.gate()?; if spawn.handles == 0 || usize::from(spawn.handles) > MAX_HANDLES || spawn.code_entry == 0 || !spawn.code_entry.is_multiple_of(4) || spawn.user_stack_top == 0 || !spawn.user_stack_top.is_multiple_of(16) { return Err(Error::Configuration); } let parent = if let Some(parent) = e.current { let r = self.record(parent)?; if r.exit_code.is_some() || r.resources.state != threads::State::Running { return Err(Error::State); } Some(parent) } else { if !e.kernel_bootstrap { return Err(Error::Context); } if self .entries .iter() .any(|p| p.phase != Phase::Vacant && p.parent.is_none()) { return Err(Error::Busy); } None }; let i = self .entries .iter() .position(|p| p.phase == Phase::Vacant && p.generation != u64::MAX) .ok_or(Error::Capacity)?; let episode = self.next_episode.checked_add(1).ok_or(Error::Exhausted)?; self.entries[i].episode = episode; self.entries[i].phase = Phase::Building; self.entries[i].parent = parent; self.entries[i].spawn = Some(spawn); self.next_episode = episode; Ok(Reservation { table: self.identity, slot: i, episode, }) } fn owner_context(&self, i: usize, e: Execution) -> Result<(), Error> { if let Some(parent) = self.entries[i].parent { if e.current != Some(parent) { return Err(Error::Foreign); } let r = self.record(parent)?; if r.resources.state != threads::State::Running || r.exit_code.is_some() { return Err(Error::State); } } else if e.current.is_some() || !e.kernel_bootstrap { return Err(Error::Context); } Ok(()) } fn validate_bag(&self, i: usize, o: BagObservation, prepared: bool) -> Result<(), Error> { let pid = if matches!(self.entries[i].phase, Phase::Published | Phase::Zombie) { ProcessId { table: self.identity, slot: i, generation: self.entries[i].generation, } } else { self.proposed(i)? }; if o.process != pid || o.resource_owner == 0 || o.pages == 0 { return Err(Error::Resource); } let handles = usize::from(self.entries[i].spawn.ok_or(Error::State)?.handles); if prepared && (!o.image_ready || !o.stack_ready || o.spaces[0].is_none() || o.handles[..handles].iter().any(Option::is_none) || o.handles[handles..].iter().any(Option::is_some)) { return Err(Error::Preparation); } for (a, h) in o.spaces.iter().enumerate() { if let Some(h) = h && (h.owner() != o.resource_owner || h.generation() == 0 || o.spaces[..a].contains(&Some(*h))) { return Err(Error::Resource); } } for (a, h) in o.handles.iter().enumerate() { if let Some(h) = h && (h.generation() == 0 || o.handles[..a].contains(&Some(*h))) { return Err(Error::Resource); } } for (j, entry) in self.entries.iter().enumerate() { if j == i { continue; } if let Some(bag) = entry.bag.as_ref() { let other = self.backend().observe(bag)?; if other.thread == o.thread || other.resource_owner == o.resource_owner || o.spaces .iter() .flatten() .any(|h| other.spaces.contains(&Some(*h))) || o.handles .iter() .flatten() .any(|h| other.handles.contains(&Some(*h))) { return Err(Error::Resource); } } } Ok(()) } fn clear_hidden(&mut self, i: usize) { let e = &mut self.entries[i]; e.phase = Phase::Vacant; e.parent = None; e.spawn = None; e.baseline = None; e.exit_code = None; } fn release_owned(&mut self, i: usize) -> Result<(), Error> { let before = self.gate()?; if let Some(bag) = self.entries[i].bag.take() { match self.backend.as_mut().expect("owned backend").release(bag) { Ok(()) => (), Err((error, bag)) => { self.entries[i].bag = Some(bag); self.entries[i].phase = Phase::Quarantined; return Err(error); } } } if self.backend().execution() != before { self.entries[i].phase = Phase::Quarantined; return Err(Error::Context); } Ok(()) } pub fn prepare(&mut self, r: Reservation) -> Result { let perform = || -> Result { let e = self.gate()?; let i = self.reservation_index(&r)?; self.owner_context(i, e)?; if self.entries[i].phase != Phase::Building || self.entries[i].bag.is_some() { return Err(Error::State); } Ok(i) }; let i = match perform() { Ok(i) => i, Err(e) => return Err((e, r)), }; let pid = match self.proposed(i) { Ok(p) => p, Err(e) => return Err((e, r)), }; let spawn = self.entries[i].spawn.expect("reserved spawn"); let before = self.backend().execution(); match self .backend .as_mut() .expect("owned backend") .prepare(pid, spawn) { Ok(bag) => { self.entries[i].bag = Some(bag); let result = self .backend() .observe(self.entries[i].bag.as_ref().unwrap()) .and_then(|o| { self.validate_bag(i, o, true)?; if o.published || o.state != threads::State::Ready { return Err(Error::State); } Ok(o) }); match result { Ok(o) if self.backend().execution() == before => { self.entries[i].baseline = Some(o); self.entries[i].phase = Phase::Prepared; Ok(r) } Ok(_) => { self.entries[i].phase = Phase::Quarantined; Err((Error::Context, r)) } Err(e) => { self.entries[i].phase = Phase::Quarantined; Err((e, r)) } } } Err(f) => { self.entries[i].bag = f.partial; if f.failure.boundary == Boundary::After || self.backend().execution() != before { self.entries[i].phase = Phase::Quarantined; return Err((Error::Quarantined, r)); } if let Err(e) = self.release_owned(i) { return Err((e, r)); } Err((f.failure.error, r)) } } } pub fn abort(&mut self, r: Reservation) -> Result<(), (Error, Reservation)> { let result = || -> Result { let e = self.gate()?; let i = self.reservation_index(&r)?; self.owner_context(i, e)?; Ok(i) }; let i = match result() { Ok(i) => i, Err(e) => return Err((e, r)), }; if let Err(e) = self.release_owned(i) { return Err((e, r)); } self.clear_hidden(i); Ok(()) } pub fn publish(&mut self, r: Reservation) -> Result { let check = || -> Result<(usize, ProcessId, BagObservation), Error> { let e = self.gate()?; let i = self.reservation_index(&r)?; self.owner_context(i, e)?; if self.entries[i].phase != Phase::Prepared { return Err(Error::Preparation); } let o = self .backend() .observe(self.entries[i].bag.as_ref().ok_or(Error::Resource)?)?; self.validate_bag(i, o, true)?; if Some(o) != self.entries[i].baseline || o.published || o.state != threads::State::Ready { return Err(Error::Resource); } Ok((i, self.proposed(i)?, o)) }; let (i, pid, old) = match check() { Ok(v) => v, Err(e) => return Err((e, r)), }; let before = self.backend().execution(); let result = self .backend .as_mut() .expect("owned backend") .publish(self.entries[i].bag.as_mut().expect("prepared bag")); if let Err(f) = result { if f.boundary == Boundary::After || self.backend().execution() != before { self.entries[i].phase = Phase::Quarantined; } return Err((f.error, r)); } let observation = self .backend() .observe(self.entries[i].bag.as_ref().unwrap()); let mut expected = old; expected.published = true; if observation != Ok(expected) || self.backend().execution() != before { self.entries[i].phase = Phase::Quarantined; return Err((Error::Quarantined, r)); } self.entries[i].generation = pid.generation; self.entries[i].phase = Phase::Published; self.entries[i].baseline = Some(expected); Ok(pid) } pub fn record(&self, pid: ProcessId) -> Result { self.gate()?; let i = self.public_index(pid)?; let entry = &self.entries[i]; let o = self .backend() .observe(entry.bag.as_ref().ok_or(Error::Resource)?)?; self.validate_bag(i, o, true)?; let old = entry.baseline.ok_or(Error::Resource)?; let mut stable = o; stable.state = old.state; if stable != old || !o.published { return Err(Error::Resource); } if !matches!( o.state, threads::State::Ready | threads::State::Running | threads::State::Sleeping | threads::State::Dead ) { return Err(Error::State); } if (entry.phase == Phase::Zombie) != (entry.exit_code.is_some()) || entry.exit_code.is_some() && o.state != threads::State::Dead { return Err(Error::State); } Ok(Record { id: pid, parent: entry.parent, resources: o, exit_code: entry.exit_code, }) } fn finish_exit(&mut self, i: usize, code: i32) -> Result<(), Error> { let before = self.backend().execution(); let result = self .backend .as_mut() .expect("owned backend") .exit(self.entries[i].bag.as_mut().expect("published bag"), code); if let Err(f) = result { if f.boundary == Boundary::After || self.backend().execution() != before { self.entries[i].phase = Phase::Quarantined; } return Err(f.error); } let mut expected = self.entries[i].baseline.ok_or(Error::Resource)?; expected.state = threads::State::Dead; let mut expected_execution = before; if before.current == Some(expected.process) { expected_execution.current = None; } if self .backend() .observe(self.entries[i].bag.as_ref().unwrap()) != Ok(expected) || self.backend().execution() != expected_execution { self.entries[i].phase = Phase::Quarantined; return Err(Error::Quarantined); } self.entries[i].baseline = Some(expected); self.entries[i].exit_code = Some(code); self.entries[i].phase = Phase::Zombie; Ok(()) } pub fn exit_current(&mut self, code: i32) -> Result<(), Error> { let e = self.gate()?; let pid = e.current.ok_or(Error::Context)?; let r = self.record(pid)?; if r.resources.state != threads::State::Running || r.exit_code.is_some() { return Err(Error::State); } let i = self.public_index(pid)?; self.finish_exit(i, code) } pub fn terminate_child(&mut self, child: ProcessId, code: i32) -> Result<(), Error> { let e = self.gate()?; let i = self.public_index(child)?; self.owner_context(i, e)?; let r = self.record(child)?; if r.exit_code.is_some() || r.resources.state == threads::State::Running || e.current == Some(child) { return Err(Error::Busy); } self.finish_exit(i, code) } pub fn wait_child(&mut self, child: ProcessId) -> Result { let e = self.gate()?; let i = match self.public_index(child) { Ok(i) => i, Err(Error::Stale) => return Err(Error::NoChild), Err(e) => return Err(e), }; self.owner_context(i, e)?; let r = self.record(child)?; if e.current == Some(child) || r.resources.state != threads::State::Dead { return Err(Error::Busy); } let code = r.exit_code.ok_or(Error::Busy)?; if self .entries .iter() .any(|p| p.phase != Phase::Vacant && p.parent == Some(child)) { return Err(Error::Busy); } self.release_owned(i)?; self.clear_hidden(i); Ok(code) } pub fn snapshot(&self) -> Snapshot { let mut s = Snapshot::default(); for e in &self.entries { match e.phase { Phase::Vacant => (), Phase::Building | Phase::Prepared => s.constructing += 1, Phase::Published => s.published += 1, Phase::Zombie => { s.published += 1; s.zombies += 1 } Phase::Quarantined => s.quarantined += 1, } } s } pub fn published(&self) -> impl Iterator + '_ { self.entries .iter() .enumerate() .filter(|(_, e)| matches!(e.phase, Phase::Published | Phase::Zombie)) .map(|(slot, e)| ProcessId { table: self.identity, slot, generation: e.generation, }) } pub fn into_backend(mut self) -> Result { match self.gate() { Ok(e) if e.current.is_none() => (), Ok(_) => return Err((Error::Context, self)), Err(e) => return Err((e, self)), } if self .entries .iter() .any(|e| e.phase != Phase::Vacant || e.bag.is_some()) { return Err((Error::Busy, self)); } Ok(self.backend.take().expect("owned backend")) } } impl Drop for Table { fn drop(&mut self) { for entry in &mut self.entries { if let Some(bag) = entry.bag.take() { mem::forget(bag); } } if let Some(backend) = self.backend.take() { mem::forget(backend); } } } #[cfg(test)] mod private_tests { use super::*; struct Empty; unsafe impl Backend for Empty { type Bag = (); fn execution(&self) -> Execution { Execution { cpu: 0, in_irq: false, irq_masked: true, current: None, kernel_bootstrap: true, } } fn prepare(&mut self, _: ProcessId, _: Spawn) -> Result<(), BuildFailure<()>> { panic!("no acquisition expected") } fn observe(&self, _: &()) -> Result { panic!("no observation expected") } fn publish(&mut self, _: &mut ()) -> Result<(), Failure> { panic!("no publication expected") } fn exit(&mut self, _: &mut (), _: i32) -> Result<(), Failure> { panic!("no exit expected") } fn release(&mut self, _: ()) -> Result<(), (Error, ())> { panic!("no release expected") } } fn table() -> Table { Table::new(Empty).ok().unwrap() } fn spawn() -> Spawn { Spawn { handles: 1, code_entry: 0x10000, user_stack_top: 0x80000, } } #[test] fn generation_exhaustion_retires_vacant_slot_before_acquisition() { let mut t = table(); t.entries[0].generation = u64::MAX; assert_eq!(t.begin(spawn()), Err(Error::Capacity)); assert_eq!(t.entries[0].generation, u64::MAX); assert_eq!(t.snapshot(), Snapshot::default()); } #[test] fn episode_exhaustion_does_not_publish_or_reserve_any_slot() { let mut t = table(); t.next_episode = u64::MAX; assert_eq!(t.begin(spawn()), Err(Error::Exhausted)); assert_eq!(t.snapshot(), Snapshot::default()); assert_eq!(t.entries[0].episode, 0); } #[test] fn aborted_reservation_cannot_name_later_same_slot_generation_proposal() { let mut t = table(); let r = t.begin(spawn()).unwrap(); let old = Reservation { table: r.table, slot: r.slot, episode: r.episode, }; t.abort(r).unwrap(); let new = t.begin(spawn()).unwrap(); assert_ne!(old.episode, new.episode); let before = t.snapshot(); assert_eq!(t.abort(old).err().unwrap().0, Error::Stale); assert_eq!(t.snapshot(), before); t.abort(new).unwrap(); } #[test] fn invalid_spawn_bounds_do_not_reserve_or_change_generation() { for s in [ Spawn { handles: 0, ..spawn() }, Spawn { handles: 9, ..spawn() }, Spawn { code_entry: 0, ..spawn() }, Spawn { code_entry: 0x10002, ..spawn() }, Spawn { user_stack_top: 0, ..spawn() }, Spawn { user_stack_top: 0x80008, ..spawn() }, ] { let mut t = table(); assert_eq!(t.begin(s), Err(Error::Configuration)); assert_eq!(t.snapshot(), Snapshot::default()); assert_eq!(t.entries[0].generation, 0); } } }