Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"debug-service",
"debug-service-messages",
"keyboard-service",
"soc-manager-service",
]
exclude = ["examples/*"]

Expand Down
19 changes: 19 additions & 0 deletions soc-manager-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
141 changes: 141 additions & 0 deletions soc-manager-service/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Output = PowerState> {
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<PowerState, Error> {
self.rx.try_get().ok_or(Error::Other)
}
}

/// SoC manager.
pub struct SocManager<T: PowerSequence, const MAX_LISTENERS: usize> {
soc: Mutex<GlobalRawMutex, T>,
power_state: Watch<GlobalRawMutex, PowerState, MAX_LISTENERS>,
}

impl<T: PowerSequence, const MAX_LISTENERS: usize> SocManager<T, MAX_LISTENERS> {
/// 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<PowerStateListener<'_, MAX_LISTENERS>, 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<PowerState, Error> {
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(())
}
}
142 changes: 142 additions & 0 deletions soc-manager-service/src/power_guard.rs
Original file line number Diff line number Diff line change
@@ -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<R: Regulator, I: InputPin + Wait> PowerSequence for SoC<R, I> {
//! async fn power_on(&mut self) -> Result<(), Error> {
//! let mut guard = power_guard::PowerGuard::<R, 3>::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<Op<'a, R>, 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();
}
}