//! Single boot-CPU initialization and owned service publication. //! //! An initializer runs only after dependency/duplicate checks. A failing //! initializer transfers every partial acquisition back in `InitFailure`; //! the runtime keeps that owner unavailable until explicit rollback finishes. //! IRQ and DMA activity is read again after quiescence and before release. //! Platform adapters must report real activity and implement hardware drain; //! this policy module cannot infer DMA completion from a software boolean. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Profile { Product, Acceptance, Experiment, } pub const PROBE_ASSERTIONS: u32 = 1; pub const PROBE_INIT_FAILURE: u32 = 2; pub const PROBE_LIVE_DMA: u32 = 4; pub const KNOWN_PROBES: u32 = PROBE_ASSERTIONS | PROBE_INIT_FAILURE | PROBE_LIVE_DMA; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Manifest { pub profile: Profile, pub probes: u32, } /// Only `Manifest::validate` can produce the configuration accepted by Runtime. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ValidatedManifest(Manifest); impl Manifest { pub fn validate(self) -> Result { if self.probes & !KNOWN_PROBES != 0 { return Err(Error::UnknownProbe); } if self.profile == Profile::Product && self.probes != 0 { return Err(Error::ProductProbe); } Ok(ValidatedManifest(self)) } } impl ValidatedManifest { pub fn manifest(self) -> Manifest { self.0 } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Stage { Empty, Boot, Memory, Irq, Tasks, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ServiceId { Uart, Allocator, Irq, Scheduler, } impl ServiceId { const fn stage(self) -> Stage { match self { Self::Uart => Stage::Boot, Self::Allocator => Stage::Memory, Self::Irq => Stage::Irq, Self::Scheduler => Stage::Tasks, } } const fn prerequisite(self) -> Stage { match self { Self::Uart => Stage::Empty, Self::Allocator => Stage::Boot, Self::Irq => Stage::Memory, Self::Scheduler => Stage::Irq, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cause { Unavailable, Exhausted, Hardware, Injected, Busy, Corrupt, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Duplicate(ServiceId), Dependency { service: ServiceId, have: Stage }, RecoveryRequired, Init { service: ServiceId, cause: Cause }, NotFailed, Quiesce { service: ServiceId, cause: Cause }, LiveIrq(ServiceId), LiveDma(ServiceId), Release { service: ServiceId, cause: Cause }, ResourcesRemain(ServiceId), CountOverflow, UnknownProbe, ProductProbe, } impl Error { pub const fn tag(self) -> &'static str { match self { Self::Duplicate(_) => "duplicate-service", Self::Dependency { .. } => "dependency-not-ready", Self::RecoveryRequired => "recovery-required", Self::Init { .. } => "service-init-failed", Self::NotFailed => "no-failed-service", Self::Quiesce { .. } => "quiesce-failed", Self::LiveIrq(_) => "live-irq", Self::LiveDma(_) => "live-dma", Self::Release { .. } => "release-failed", Self::ResourcesRemain(_) => "resources-remain", Self::CountOverflow => "ownership-count-overflow", Self::UnknownProbe => "unknown-probe", Self::ProductProbe => "product-probe-contamination", } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Failure { pub service: ServiceId, pub cause: Cause, } /// Not Clone/Copy: the partial service is transferred to the runtime once. pub struct InitFailure { pub cause: Cause, pub acquired: Option, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Ownership { pub frames: u64, pub irq_bindings: u32, pub dma_buffers: u32, pub tasks: u32, } impl Ownership { fn checked_add(self, other: Self) -> Result { Ok(Self { frames: self .frames .checked_add(other.frames) .ok_or(Error::CountOverflow)?, irq_bindings: self .irq_bindings .checked_add(other.irq_bindings) .ok_or(Error::CountOverflow)?, dma_buffers: self .dma_buffers .checked_add(other.dma_buffers) .ok_or(Error::CountOverflow)?, tasks: self .tasks .checked_add(other.tasks) .ok_or(Error::CountOverflow)?, }) } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Activity { pub irq_callbacks: u32, pub dma_requests: u32, } /// Report resources this service acquired, excluding borrowed predecessor /// resources. Query hardware/driver state for activity when DMA/IRQ is used. pub trait Service { fn ownership(&self) -> Ownership; fn activity(&self) -> Activity; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { pub stage: Stage, pub failure: Option, /// Published owners only. Partial failed acquisitions are not registered. pub registered: [bool; 4], /// Includes unpublished acquisitions retained for safe recovery. pub ownership: Ownership, } /// Callback success never authorizes release by itself: activity and remaining /// ownership are checked separately. On refusal the partial owner is retained. /// Release implementations must update their inventory after each successful /// return, and never return a borrowed predecessor's resources. pub trait Rollback { fn quiesce_uart(&mut self, uart: &mut U) -> Result<(), Cause>; fn release_uart(&mut self, uart: &mut U) -> Result<(), Cause>; fn quiesce_memory(&mut self, memory: &mut M) -> Result<(), Cause>; fn release_memory(&mut self, memory: &mut M) -> Result<(), Cause>; fn quiesce_irq(&mut self, irq: &mut I) -> Result<(), Cause>; fn release_irq(&mut self, irq: &mut I, memory: &mut M) -> Result<(), Cause>; fn quiesce_tasks(&mut self, tasks: &mut T, irq: &mut I) -> Result<(), Cause>; fn release_tasks(&mut self, tasks: &mut T, memory: &mut M, irq: &mut I) -> Result<(), Cause>; } /// Construct exactly once for the boot CPU and keep it alive while services /// run. No globals, shared mutation, or dynamically allocated registry entries. /// Service fields and stage are private; only successful initializers publish. pub struct Runtime { manifest: ValidatedManifest, stage: Stage, failure: Option, uart: Option, memory: Option, irq: Option, tasks: Option, } impl Runtime { pub const fn new(manifest: ValidatedManifest) -> Self { Self { manifest, stage: Stage::Empty, failure: None, uart: None, memory: None, irq: None, tasks: None, } } pub fn manifest(&self) -> Manifest { self.manifest.manifest() } pub fn uart(&self) -> Option<&U> { (self.stage != Stage::Empty) .then_some(self.uart.as_ref()) .flatten() } pub fn memory(&self) -> Option<&M> { matches!(self.stage, Stage::Memory | Stage::Irq | Stage::Tasks) .then_some(self.memory.as_ref()) .flatten() } /// Access the installed allocator for MMU/table setup before IRQ and /// scheduler installation; the runtime keeps ownership of the service. pub fn memory_mut(&mut self) -> Result<&mut M, Error> { if self.failure.is_some() { return Err(Error::RecoveryRequired); } if !matches!(self.stage, Stage::Memory | Stage::Irq | Stage::Tasks) { return Err(Error::Dependency { service: ServiceId::Allocator, have: self.stage, }); } Ok(self.memory.as_mut().expect("memory owner")) } pub fn irq(&self) -> Option<&I> { matches!(self.stage, Stage::Irq | Stage::Tasks) .then_some(self.irq.as_ref()) .flatten() } fn check_start(&self, service: ServiceId, occupied: bool) -> Result<(), Error> { if self.failure.is_some() { return Err(Error::RecoveryRequired); } if false { return Err(Error::Duplicate(service)); } if self.stage != service.prerequisite() { return Err(Error::Dependency { service, have: self.stage, }); } Ok(()) } fn failed(&mut self, service: ServiceId, cause: Cause) -> Error { self.failure = Some(Failure { service, cause }); Error::Init { service, cause } } pub fn start_boot( &mut self, initialize: impl FnOnce() -> Result>, ) -> Result<(), Error> { self.check_start(ServiceId::Uart, self.uart.is_some())?; match initialize() { Ok(owner) => { self.uart = Some(owner); self.stage = Stage::Boot; Ok(()) } Err(failure) => { self.uart = failure.acquired; Err(self.failed(ServiceId::Uart, failure.cause)) } } } pub fn start_memory( &mut self, initialize: impl FnOnce(&U) -> Result>, ) -> Result<(), Error> { self.check_start(ServiceId::Allocator, self.memory.is_some())?; match initialize(self.uart.as_ref().expect("boot predecessor")) { Ok(owner) => { self.memory = Some(owner); self.stage = Stage::Memory; Ok(()) } Err(failure) => { self.memory = failure.acquired; Err(self.failed(ServiceId::Allocator, failure.cause)) } } } pub fn start_irq( &mut self, initialize: impl FnOnce(&mut M) -> Result>, ) -> Result<(), Error> { self.check_start(ServiceId::Irq, self.irq.is_some())?; match initialize(self.memory.as_mut().expect("memory predecessor")) { Ok(owner) => { self.irq = Some(owner); self.stage = Stage::Irq; Ok(()) } Err(failure) => { self.irq = failure.acquired; Err(self.failed(ServiceId::Irq, failure.cause)) } } } pub fn start_tasks( &mut self, initialize: impl FnOnce(&mut M, &mut I) -> Result>, ) -> Result<(), Error> { self.check_start(ServiceId::Scheduler, self.tasks.is_some())?; match initialize( self.memory.as_mut().expect("memory predecessor"), self.irq.as_mut().expect("irq predecessor"), ) { Ok(owner) => { self.tasks = Some(owner); self.stage = Stage::Tasks; Ok(()) } Err(failure) => { self.tasks = failure.acquired; Err(self.failed(ServiceId::Scheduler, failure.cause)) } } } pub fn snapshot(&self) -> Result { let mut ownership = Ownership::default(); let mut add = |value: &dyn Service| -> Result<(), Error> { ownership = ownership.checked_add(value.ownership())?; Ok(()) }; if let Some(value) = &self.uart { add(value)?; } if let Some(value) = &self.memory { add(value)?; } if let Some(value) = &self.irq { add(value)?; } if let Some(value) = &self.tasks { add(value)?; } Ok(Snapshot { stage: self.stage, failure: self.failure, registered: [ ServiceId::Uart, ServiceId::Allocator, ServiceId::Irq, ServiceId::Scheduler, ] .map(|id| id.stage() as u8 <= self.stage as u8), ownership, }) } /// Borrow the live owners only after every required service is published. /// A driver loop must retain this runtime instead of reconstructing owners. pub fn running(&mut self) -> Result<(&U, &mut M, &mut I, &mut T), Error> { if self.failure.is_some() { return Err(Error::RecoveryRequired); } if self.stage != Stage::Tasks { return Err(Error::Dependency { service: ServiceId::Scheduler, have: self.stage, }); } Ok(( self.uart.as_ref().expect("boot owner"), self.memory.as_mut().expect("memory owner"), self.irq.as_mut().expect("irq owner"), self.tasks.as_mut().expect("tasks owner"), )) } /// Roll back the failing initializer's acquisitions only; committed UART, /// allocator and earlier services retain their owner and progress state. pub fn rollback_failed(&mut self, driver: &mut impl Rollback) -> Result<(), Error> { let failure = self.failure.ok_or(Error::NotFailed)?; let id = failure.service; match id { ServiceId::Uart => { if let Some(owner) = self.uart.as_mut() { driver .quiesce_uart(owner) .map_err(|cause| Error::Quiesce { service: id, cause })?; quiescent(id, owner)?; driver .release_uart(owner) .map_err(|cause| Error::Release { service: id, cause })?; released(id, owner)?; self.uart = None; } } ServiceId::Allocator => { if let Some(owner) = self.memory.as_mut() { driver .quiesce_memory(owner) .map_err(|cause| Error::Quiesce { service: id, cause })?; quiescent(id, owner)?; driver .release_memory(owner) .map_err(|cause| Error::Release { service: id, cause })?; released(id, owner)?; self.memory = None; } } ServiceId::Irq => { if let Some(owner) = self.irq.as_mut() { driver .quiesce_irq(owner) .map_err(|cause| Error::Quiesce { service: id, cause })?; quiescent(id, owner)?; driver .release_irq(owner, self.memory.as_mut().expect("memory predecessor")) .map_err(|cause| Error::Release { service: id, cause })?; released(id, owner)?; self.irq = None; } } ServiceId::Scheduler => { if let Some(owner) = self.tasks.as_mut() { let irq = self.irq.as_mut().expect("irq predecessor"); driver .quiesce_tasks(owner, irq) .map_err(|cause| Error::Quiesce { service: id, cause })?; quiescent(id, owner)?; driver .release_tasks( owner, self.memory.as_mut().expect("memory predecessor"), irq, ) .map_err(|cause| Error::Release { service: id, cause })?; released(id, owner)?; self.tasks = None; } } } self.failure = None; Ok(()) } } fn quiescent(id: ServiceId, service: &impl Service) -> Result<(), Error> { let activity = service.activity(); if activity.irq_callbacks != 0 { return Err(Error::LiveIrq(id)); } if activity.dma_requests != 0 { return Err(Error::LiveDma(id)); } Ok(()) } fn released(id: ServiceId, service: &impl Service) -> Result<(), Error> { quiescent(id, service)?; if service.ownership() != Ownership::default() { return Err(Error::ResourcesRemain(id)); } Ok(()) }