From 600100c775349e968df945a560a1c8afabd72a60 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Thu, 29 Jan 2026 22:31:44 -0800 Subject: [PATCH] Implement soc-manager-service --- Cargo.lock | 35 ++++++ Cargo.toml | 1 + soc-manager-service/Cargo.toml | 19 ++++ soc-manager-service/src/lib.rs | 141 ++++++++++++++++++++++++ soc-manager-service/src/power_guard.rs | 142 +++++++++++++++++++++++++ 5 files changed, 338 insertions(+) create mode 100644 soc-manager-service/Cargo.toml create mode 100644 soc-manager-service/src/lib.rs create mode 100644 soc-manager-service/src/power_guard.rs diff --git a/Cargo.lock b/Cargo.lock index dcab087b..ab6406d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -907,6 +907,19 @@ dependencies = [ "num_enum", ] +[[package]] +name = "embedded-power-sequence" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/embedded-power-sequence#e7ace1e0ac7c64e33440207f0b5c6e3e755af004" +dependencies = [ + "macros", +] + +[[package]] +name = "embedded-regulator" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/embedded-regulator#d4fb56f451e3e8703512665ac28b7b1c61454036" + [[package]] name = "embedded-sensors-hal" version = "0.1.0" @@ -1342,6 +1355,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "macros" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/embedded-power-sequence#e7ace1e0ac7c64e33440207f0b5c6e3e755af004" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "maitake-sync" version = "0.2.2" @@ -2020,6 +2043,18 @@ dependencies = [ "embedded-crc-macros", ] +[[package]] +name = "soc-manager-service" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embassy-sync", + "embedded-power-sequence", + "embedded-regulator", + "embedded-services", + "heapless", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index f9c4e650..9441aa24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "debug-service", "debug-service-messages", "keyboard-service", + "soc-manager-service", ] exclude = ["examples/*"] diff --git a/soc-manager-service/Cargo.toml b/soc-manager-service/Cargo.toml new file mode 100644 index 00000000..0cbd9d60 --- /dev/null +++ b/soc-manager-service/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "soc-manager-service" +version = "0.1.0" +edition = "2024" +description = "SoC manager embedded service implementation" +repository = "https://github.com/OpenDevicePartnership/embedded-services" +rust-version = "1.88" +license = "MIT" + +[lints] +workspace = true + +[dependencies] +heapless = { workspace = true } +defmt = { workspace = true, optional = true } +embedded-services = { workspace = true } +embassy-sync = { workspace = true } +embedded-power-sequence = { git = "https://github.com/OpenDevicePartnership/embedded-power-sequence" } +embedded-regulator = { git = "https://github.com/OpenDevicePartnership/embedded-regulator" } diff --git a/soc-manager-service/src/lib.rs b/soc-manager-service/src/lib.rs new file mode 100644 index 00000000..ccd69ca0 --- /dev/null +++ b/soc-manager-service/src/lib.rs @@ -0,0 +1,141 @@ +//! SoC manager service. +#![no_std] + +pub mod power_guard; + +use embassy_sync::mutex::Mutex; +use embassy_sync::watch::{Receiver, Watch}; +use embedded_power_sequence::PowerSequence; +use embedded_services::GlobalRawMutex; + +/// SoC manager service error. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Unspecified error, likely some invariant was violated. + Other, + /// A power sequence error occurred. + PowerSequence, + /// An invalid power state transition was requested. + InvalidStateTransition, + /// No more power state listeners are available. + ListenersNotAvailable, +} + +/// An ACPI power state. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PowerState { + /// Working state. + S0, + /// Modern standby state. + S0ix, + /// Sleep state. + S3, + /// Hibernate state. + S4, + /// Soft off state. + S5, +} + +/// A power state listener struct. +pub struct PowerStateListener<'a, const MAX_LISTENERS: usize> { + rx: Receiver<'a, GlobalRawMutex, PowerState, MAX_LISTENERS>, +} + +impl<'a, const MAX_LISTENERS: usize> PowerStateListener<'a, MAX_LISTENERS> { + /// Waits for any power state change, returning the new power state. + pub fn wait_state_change(&mut self) -> impl Future { + self.rx.changed() + } + + /// Waits for a transition to a specific power state. + pub async fn wait_for_state(&mut self, power_state: PowerState) { + self.rx.changed_and(|p| *p == power_state).await; + } + + /// Returns the current power state. + /// + /// # Errors + /// + /// Returns [`Error::Other`] if the power state is uninitialized. + pub fn current_state(&mut self) -> Result { + self.rx.try_get().ok_or(Error::Other) + } +} + +/// SoC manager. +pub struct SocManager { + soc: Mutex, + power_state: Watch, +} + +impl SocManager { + /// Creates a new SoC manager instance. + /// + /// The `initial_state` should capture the power state the SoC is ALREADY in, not the desired state + /// to transition to on initilization. + /// + /// This will usually be [`PowerState::S5`] (powered off) but not always. + pub fn new(soc: T, initial_state: PowerState) -> Self { + let soc_manager = Self { + soc: Mutex::new(soc), + power_state: Watch::new(), + }; + + soc_manager.power_state.sender().send(initial_state); + soc_manager + } + + /// Creates a new power state listener. + /// + /// # Errors + /// + /// Returns [`Error::ListenersNotAvailable`] if `MAX_LISTENERS` or greater are already in use. + pub fn new_pwr_listener(&self) -> Result, Error> { + Ok(PowerStateListener { + rx: self.power_state.receiver().ok_or(Error::InvalidStateTransition)?, + }) + } + + /// Returns the current power state. + /// + /// This method is also available on `PowerStateListener`. + pub fn current_state(&self) -> Result { + self.power_state.try_get().ok_or(Error::Other) + } + + /// Sets the current power state. + /// + /// # Errors + /// + /// Returns [`Error::PowerSequence`] if an error is encountered while transitioning power state. + /// + /// Returns [`Error::InvalidStateTransition`] if the requested state is not valid based on current state. + pub async fn set_power_state(&self, state: PowerState) -> Result<(), Error> { + // Revisit: Check with other services to see if we are too hot or don't have enough power for requested transition + // Need to think more about how that will look though + let cur_state = self.power_state.try_get().ok_or(Error::Other)?; + let mut soc = self.soc.lock().await; + match (cur_state, state) { + // Any sleeping state must first transition to S0 before we can transition to another state + (PowerState::S0ix, PowerState::S0) => soc.wake_up().await, + (PowerState::S3, PowerState::S0) => soc.resume().await, + (PowerState::S4, PowerState::S0) => soc.activate().await, + (PowerState::S5, PowerState::S0) => soc.power_on().await, + + // S0 can then transition to any sleep state + (PowerState::S0, PowerState::S0ix) => soc.idle().await, + (PowerState::S0, PowerState::S3) => soc.suspend().await, + (PowerState::S0, PowerState::S4) => soc.hibernate().await, + (PowerState::S0, PowerState::S5) => soc.power_off().await, + + // Anything else is an invalid transition + _ => return Err(Error::InvalidStateTransition), + } + .map_err(|_| Error::PowerSequence)?; + + self.power_state.sender().send(state); + Ok(()) + } +} diff --git a/soc-manager-service/src/power_guard.rs b/soc-manager-service/src/power_guard.rs new file mode 100644 index 00000000..67cd824d --- /dev/null +++ b/soc-manager-service/src/power_guard.rs @@ -0,0 +1,142 @@ +//! PowerGuard. +//! +//! This is intended to be used within `embedded-power-sequence` implementations for handling +//! rollback automatically while enabling/disabling power regulators. +//! +//! # Example +//! +//! ```rust,ignore +//! impl PowerSequence for SoC { +//! async fn power_on(&mut self) -> Result<(), Error> { +//! let mut guard = power_guard::PowerGuard::::new(); +//! +//! // If any of these fail, the PowerGuard will be implicitly rolled back +//! guard.execute(power_guard::Op::Enable(&mut self.regulator1)).await?; +//! guard.execute(power_guard::Op::Enable(&mut self.regulator2)).await?; +//! guard.execute(power_guard::Op::Enable(&mut self.regulator3)).await?; +//! +//! // Typically at some point during sequencing we might wait for a "power good" pin to go high, +//! // and if we timeout while waiting we can explicitly rollback the PowerGuard +//! if with_timeout(Duration::from_millis(1000), self.pwr_good.wait_for_high()).await.is_err() { +//! guard.rollback().await?; +//! } +//! } +//! } +//! ``` +use embedded_regulator::Regulator; +use heapless::Vec; + +/// PowerGuard error. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The PowerGuard is full and cannot accept more operations. + Full, + /// A regulator error occurred during rollback. + RollbackFailure, + /// A regulator error occurred while pushing an operation into the PowerGuard. + OpFailure, + /// The PowerGuard is empty. + Empty, +} + +/// PowerGuard operation. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Op<'a, R> { + /// Enable regulator. + Enable(&'a mut R), + /// Disable regulator. + Disable(&'a mut R), +} + +/// PowerGuard. +/// +/// This represents a stack of operations on power regulators. +/// As operations are pushed to the stack, they are executed. +/// +/// In the event of an error, operations are undone and removed from the PowerGuard in reverse order. +pub struct PowerGuard<'a, R: Regulator, const MAX_SIZE: usize> { + stk: Vec, MAX_SIZE>, +} + +impl<'a, R: Regulator, const MAX_SIZE: usize> Default for PowerGuard<'a, R, MAX_SIZE> { + fn default() -> Self { + Self { stk: Vec::new() } + } +} + +impl<'a, R: Regulator, const MAX_SIZE: usize> PowerGuard<'a, R, MAX_SIZE> { + /// Create a new PowerGuard instance. + pub fn new() -> Self { + Self::default() + } + + /// Rollback the PowerGuard. This will undo operations in reverse order of how they were entered. + /// If successful, the PowerGuard will be empty upon return. + /// + /// # Errors + /// + /// Returns [`Error::RollbackFailure`] if a regulator error occurred during rollback. + /// In this failure event, the PowerGuard may not be empty. + pub async fn rollback(&mut self) -> Result<(), Error> { + loop { + match self.rollback_once().await { + Ok(()) => continue, + Err(Error::Empty) => return Ok(()), + e @ Err(_) => return e, + } + } + } + + /// Rollback only the single most recent operation in the PowerGuard. + /// + /// # Errors + /// + /// Returns [`Error::Empty`] if the PowerGuard is empty. + /// + /// Returns [`Error::RollbackFailure`] if a regulator error occurred during rollback. + pub async fn rollback_once(&mut self) -> Result<(), Error> { + match self.stk.pop() { + Some(Op::Enable(r)) => r.disable().await, + Some(Op::Disable(r)) => r.enable().await, + None => return Err(Error::Empty), + } + .map_err(|_| Error::RollbackFailure) + } + + /// Execute an operation on a wrapped power regulator. + /// If the operation fails, the PowerGuard will be automatically rolled back. + /// + /// # Errors + /// + /// Returns [`Error::Full`] if the PowerGuard is full. The PowerGuard is NOT rolled back in this case. + /// + /// Returns [`Error::OpFailure`] if the operation failed but rollback was successful. + /// + /// Returns [`Error::RollbackFailure`] if the operation failed and rollback failed as well. + pub async fn execute(&mut self, mut cmd: Op<'a, R>) -> Result<(), Error> { + if self.stk.is_full() { + return Err(Error::Full); + } + + let res = match &mut cmd { + Op::Enable(r) => r.enable().await, + Op::Disable(r) => r.disable().await, + }; + + if res.is_ok() { + let _ = self.stk.push(cmd); + Ok(()) + } else { + self.rollback().await?; + Err(Error::OpFailure) + } + } + + /// Clears the PowerGuard of all operations. + /// Thus, they will not be included in future rollbacks. + pub fn clear(&mut self) { + self.stk.clear(); + } +}