diff --git a/Cargo.lock b/Cargo.lock index 24d0c21b..7ab8ee9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -964,6 +964,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" @@ -1453,6 +1466,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" @@ -2160,6 +2183,20 @@ dependencies = [ "embedded-crc-macros", ] +[[package]] +name = "soc-manager-service" +version = "0.1.0" +dependencies = [ + "critical-section", + "defmt 0.3.100", + "embassy-sync", + "embedded-power-sequence", + "embedded-regulator", + "embedded-services", + "heapless", + "tokio", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 06f23b96..fc1cd248 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..ab0c27ca --- /dev/null +++ b/soc-manager-service/Cargo.toml @@ -0,0 +1,23 @@ +[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" } + +[dev-dependencies] +critical-section = { workspace = true, features = ["std"] } +tokio = { workspace = true, features = ["rt", "macros", "time"] } diff --git a/soc-manager-service/src/lib.rs b/soc-manager-service/src/lib.rs new file mode 100644 index 00000000..5a61953e --- /dev/null +++ b/soc-manager-service/src/lib.rs @@ -0,0 +1,240 @@ +//! 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 initialization. + /// + /// 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::ListenersNotAvailable)?, + }) + } + + /// 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 mut soc = self.soc.lock().await; + let cur_state = self.power_state.try_get().ok_or(Error::Other)?; + + 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(()) + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::*; + use embedded_power_sequence::{ErrorKind, ErrorType}; + + /// A mock SoC that always succeeds power transitions. + struct MockSoc; + + impl ErrorType for MockSoc { + type Error = ErrorKind; + } + + impl PowerSequence for MockSoc { + async fn power_on(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn pre_power_on(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn post_power_on(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn power_off(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn pre_power_off(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn post_power_off(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + #[tokio::test] + async fn test_state_transitions() { + let sm = SocManager::::new(MockSoc, PowerState::S5); + + // Verify that we can't directly transition to a sleeping state (S3) + assert!(matches!( + sm.set_power_state(PowerState::S3).await, + Err(Error::InvalidStateTransition) + )); + + // Verify state remains unchanged + assert!(sm.current_state().unwrap() == PowerState::S5); + + // Verify we can transition to S0 + assert!(sm.set_power_state(PowerState::S0).await.is_ok()); + + // Verify state has changed to S0 + assert!(sm.current_state().unwrap() == PowerState::S0); + + // Verify we can then transition to a sleeping state (S3) + assert!(sm.set_power_state(PowerState::S3).await.is_ok()); + + // Verify state has changed to S3 + assert!(sm.current_state().unwrap() == PowerState::S3); + } + + #[tokio::test] + async fn test_power_state_listener() { + let sm = SocManager::::new(MockSoc, PowerState::S5); + + // Create two listeners + let mut l1 = sm.new_pwr_listener().unwrap(); + let mut l2 = sm.new_pwr_listener().unwrap(); + + // Verify we can't create a third + assert!(matches!(sm.new_pwr_listener(), Err(Error::ListenersNotAvailable))); + + // Verify listeners can read initial state + assert_eq!(l1.current_state().unwrap(), PowerState::S5); + assert_eq!(l2.current_state().unwrap(), PowerState::S5); + + // Verify listeners can read updated state + sm.set_power_state(PowerState::S0).await.unwrap(); + assert_eq!(l1.current_state().unwrap(), PowerState::S0); + assert_eq!(l2.current_state().unwrap(), PowerState::S0); + + // Verify listeners can wait for state changes + sm.set_power_state(PowerState::S0ix).await.unwrap(); + assert_eq!(l1.wait_state_change().await, PowerState::S0ix); + assert_eq!(l2.wait_state_change().await, PowerState::S0ix); + + // Verify listeners can wait for specific state change + sm.set_power_state(PowerState::S0).await.unwrap(); + l1.wait_for_state(PowerState::S0).await; + l2.wait_for_state(PowerState::S0).await; + // If we got here then they successfully waited for S0 + } +} diff --git a/soc-manager-service/src/power_guard.rs b/soc-manager-service/src/power_guard.rs new file mode 100644 index 00000000..b66c4cb9 --- /dev/null +++ b/soc-manager-service/src/power_guard.rs @@ -0,0 +1,288 @@ +//! PowerGuard. +//! +//! This is intended to be used within `embedded-power-sequence` implementations for handling +//! rollback automatically while enabling/disabling power regulators. +//! +//! # Example +//! +//! ```rust,ignore +//! enum PowerSequenceError { +//! Timeout, +//! RegulatorFailure, +//! } +//! +//! impl PowerSequence for SoC { +//! async fn power_on(&mut self) -> Result<(), PowerSequenceError> { +//! 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.map_err(|_| PowerSequenceError::RegulatorFailure)?; +//! return Err(Error::Timeout); +//! } +//! +//! Ok(()) +//! } +//! } +//! ``` +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. + RegulatorFailure, + /// 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::RegulatorFailure`] if a regulator error occurred during rollback. + /// In this failure event, the PowerGuard may not be empty, and the failing regulator is NOT removed from the PowerGuard. + 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::RegulatorFailure`] if a regulator error occurred during rollback. + /// In this failure event, the failing regulator is NOT removed from the PowerGuard. + pub async fn rollback_once(&mut self) -> Result<(), Error> { + let res = match self.stk.last_mut() { + Some(Op::Enable(r)) => r.disable().await, + Some(Op::Disable(r)) => r.enable().await, + None => return Err(Error::Empty), + } + .map_err(|_| Error::RegulatorFailure); + + if res.is_ok() { + let _ = self.stk.pop(); + } + + res + } + + /// Execute an operation on a regulator and add it to the PowerGuard. + /// + /// # Errors + /// + /// Returns [`Error::Full`] if the PowerGuard is full. The PowerGuard is NOT rolled back in this case. + /// + /// Returns [`Error::RegulatorFailure`] if the regulator failed to perform the requested operation. + /// In this failure event, the PowerGuard is rolled back, and the failing regulator is NOT added to the PowerGuard. + /// During rollback a regulator may fail, in which case this error is also returned. + 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() { + // Note: This will always succeed since we checked the stack isn't full above + let _ = self.stk.push(cmd); + Ok(()) + } else { + self.rollback().await?; + Err(Error::RegulatorFailure) + } + } + + /// Pops the most recent regulator (if any) from the PowerGuard without attempting to roll back the operation. + pub fn pop(&mut self) -> Option<&mut R> { + match self.stk.pop() { + Some(Op::Enable(r)) | Some(Op::Disable(r)) => Some(r), + None => None, + } + } + + /// Clears the PowerGuard of all operations. + pub fn clear(&mut self) { + self.stk.clear(); + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::*; + use embedded_regulator::{ErrorKind, ErrorType}; + + /// A mock regulator that tracks enable/disable state. + struct MockReg { + enabled: bool, + always_fail: bool, + } + + impl MockReg { + fn new(always_fail: bool) -> Self { + Self { + enabled: false, + always_fail, + } + } + } + + impl ErrorType for MockReg { + type Error = ErrorKind; + } + + impl Regulator for MockReg { + async fn enable(&mut self) -> Result<(), Self::Error> { + if self.always_fail { + Err(ErrorKind::Other) + } else { + self.enabled = true; + Ok(()) + } + } + + async fn disable(&mut self) -> Result<(), Self::Error> { + if self.always_fail { + Err(ErrorKind::Other) + } else { + self.enabled = false; + Ok(()) + } + } + } + + #[tokio::test] + async fn test_execute() { + let mut r1 = MockReg::new(false); + let mut r2 = MockReg::new(false); + + { + let mut guard = PowerGuard::::new(); + guard.execute(Op::Enable(&mut r1)).await.unwrap(); + guard.execute(Op::Enable(&mut r2)).await.unwrap(); + } + + // Verify we can execute operations on the regulators + assert!(r1.enabled); + assert!(r2.enabled); + } + + #[tokio::test] + async fn test_execute_and_rollback() { + let mut r1 = MockReg::new(false); + let mut r2 = MockReg::new(false); + + { + let mut guard = PowerGuard::::new(); + guard.execute(Op::Enable(&mut r1)).await.unwrap(); + guard.execute(Op::Enable(&mut r2)).await.unwrap(); + guard.rollback().await.unwrap(); + } + + // Verify we can rollback operations on the regulators + assert!(!r1.enabled); + assert!(!r2.enabled); + } + + #[tokio::test] + async fn test_full() { + let mut r1 = MockReg::new(false); + let mut r2 = MockReg::new(false); + let mut r3 = MockReg::new(false); + + let mut guard = PowerGuard::::new(); + guard.execute(Op::Enable(&mut r1)).await.unwrap(); + guard.execute(Op::Enable(&mut r2)).await.unwrap(); + + // Verify we can't add a third regulator + assert!(matches!(guard.execute(Op::Enable(&mut r3)).await, Err(Error::Full))); + } + + #[tokio::test] + async fn test_execute_and_rollback_once() { + let mut r1 = MockReg::new(false); + let mut r2 = MockReg::new(false); + + { + let mut guard = PowerGuard::::new(); + guard.execute(Op::Enable(&mut r1)).await.unwrap(); + guard.execute(Op::Enable(&mut r2)).await.unwrap(); + guard.rollback_once().await.unwrap(); + } + + // Verify only r2 is rolled back (disabled) and r1 remains enabled + assert!(r1.enabled); + assert!(!r2.enabled); + } + + #[tokio::test] + async fn test_execute_failure() { + let mut r1 = MockReg::new(true); + + { + let mut guard = PowerGuard::::new(); + assert!(matches!( + guard.execute(Op::Enable(&mut r1)).await, + Err(Error::RegulatorFailure) + )); + } + + // Verify r1 remains disabled after operation failure + assert!(!r1.enabled); + } +}