Skip to content

Commit 93622b6

Browse files
committed
Implement soc-manager-service
1 parent ead4bc8 commit 93622b6

5 files changed

Lines changed: 581 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ members = [
2222
"debug-service",
2323
"debug-service-messages",
2424
"keyboard-service",
25+
"soc-manager-service",
2526
]
2627
exclude = ["examples/*"]
2728

soc-manager-service/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "soc-manager-service"
3+
version = "0.1.0"
4+
edition = "2024"
5+
description = "SoC manager embedded service implementation"
6+
repository = "https://github.com/OpenDevicePartnership/embedded-services"
7+
rust-version = "1.88"
8+
license = "MIT"
9+
10+
[lints]
11+
workspace = true
12+
13+
[dependencies]
14+
heapless = { workspace = true }
15+
defmt = { workspace = true, optional = true }
16+
embedded-services = { workspace = true }
17+
embassy-sync = { workspace = true }
18+
embedded-power-sequence = { git = "https://github.com/OpenDevicePartnership/embedded-power-sequence" }
19+
embedded-regulator = { git = "https://github.com/OpenDevicePartnership/embedded-regulator" }
20+
21+
[dev-dependencies]
22+
critical-section = { workspace = true, features = ["std"] }
23+
tokio = { workspace = true, features = ["rt", "macros", "time"] }

soc-manager-service/src/lib.rs

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
//! SoC manager service.
2+
#![no_std]
3+
4+
pub mod power_guard;
5+
6+
use embassy_sync::mutex::Mutex;
7+
use embassy_sync::watch::{Receiver, Watch};
8+
use embedded_power_sequence::PowerSequence;
9+
use embedded_services::GlobalRawMutex;
10+
11+
/// SoC manager service error.
12+
#[derive(Clone, Copy, Debug)]
13+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14+
pub enum Error {
15+
/// Unspecified error, likely some invariant was violated.
16+
Other,
17+
/// A power sequence error occurred.
18+
PowerSequence,
19+
/// An invalid power state transition was requested.
20+
InvalidStateTransition,
21+
/// No more power state listeners are available.
22+
ListenersNotAvailable,
23+
}
24+
25+
/// An ACPI power state.
26+
#[derive(Clone, Copy, Debug, PartialEq)]
27+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
28+
pub enum PowerState {
29+
/// Working state.
30+
S0,
31+
/// Modern standby state.
32+
S0ix,
33+
/// Sleep state.
34+
S3,
35+
/// Hibernate state.
36+
S4,
37+
/// Soft off state.
38+
S5,
39+
}
40+
41+
/// A power state listener struct.
42+
pub struct PowerStateListener<'a, const MAX_LISTENERS: usize> {
43+
rx: Receiver<'a, GlobalRawMutex, PowerState, MAX_LISTENERS>,
44+
}
45+
46+
impl<'a, const MAX_LISTENERS: usize> PowerStateListener<'a, MAX_LISTENERS> {
47+
/// Waits for any power state change, returning the new power state.
48+
pub fn wait_state_change(&mut self) -> impl Future<Output = PowerState> {
49+
self.rx.changed()
50+
}
51+
52+
/// Waits for a transition to a specific power state.
53+
pub async fn wait_for_state(&mut self, power_state: PowerState) {
54+
self.rx.changed_and(|p| *p == power_state).await;
55+
}
56+
57+
/// Returns the current power state.
58+
///
59+
/// # Errors
60+
///
61+
/// Returns [`Error::Other`] if the power state is uninitialized.
62+
pub fn current_state(&mut self) -> Result<PowerState, Error> {
63+
self.rx.try_get().ok_or(Error::Other)
64+
}
65+
}
66+
67+
/// SoC manager.
68+
pub struct SocManager<T: PowerSequence, const MAX_LISTENERS: usize> {
69+
soc: Mutex<GlobalRawMutex, T>,
70+
power_state: Watch<GlobalRawMutex, PowerState, MAX_LISTENERS>,
71+
}
72+
73+
impl<T: PowerSequence, const MAX_LISTENERS: usize> SocManager<T, MAX_LISTENERS> {
74+
/// Creates a new SoC manager instance.
75+
///
76+
/// The `initial_state` should capture the power state the SoC is ALREADY in, not the desired state
77+
/// to transition to on initialization.
78+
///
79+
/// This will usually be [`PowerState::S5`] (powered off) but not always.
80+
pub fn new(soc: T, initial_state: PowerState) -> Self {
81+
let soc_manager = Self {
82+
soc: Mutex::new(soc),
83+
power_state: Watch::new(),
84+
};
85+
86+
soc_manager.power_state.sender().send(initial_state);
87+
soc_manager
88+
}
89+
90+
/// Creates a new power state listener.
91+
///
92+
/// # Errors
93+
///
94+
/// Returns [`Error::ListenersNotAvailable`] if `MAX_LISTENERS` or greater are already in use.
95+
pub fn new_pwr_listener(&self) -> Result<PowerStateListener<'_, MAX_LISTENERS>, Error> {
96+
Ok(PowerStateListener {
97+
rx: self.power_state.receiver().ok_or(Error::ListenersNotAvailable)?,
98+
})
99+
}
100+
101+
/// Returns the current power state.
102+
///
103+
/// This method is also available on `PowerStateListener`.
104+
pub fn current_state(&self) -> Result<PowerState, Error> {
105+
self.power_state.try_get().ok_or(Error::Other)
106+
}
107+
108+
/// Sets the current power state.
109+
///
110+
/// # Errors
111+
///
112+
/// Returns [`Error::PowerSequence`] if an error is encountered while transitioning power state.
113+
///
114+
/// Returns [`Error::InvalidStateTransition`] if the requested state is not valid based on current state.
115+
pub async fn set_power_state(&self, state: PowerState) -> Result<(), Error> {
116+
// Revisit: Check with other services to see if we are too hot or don't have enough power for requested transition
117+
// Need to think more about how that will look though
118+
let mut soc = self.soc.lock().await;
119+
let cur_state = self.power_state.try_get().ok_or(Error::Other)?;
120+
121+
match (cur_state, state) {
122+
// Any sleeping state must first transition to S0 before we can transition to another state
123+
(PowerState::S0ix, PowerState::S0) => soc.wake_up().await,
124+
(PowerState::S3, PowerState::S0) => soc.resume().await,
125+
(PowerState::S4, PowerState::S0) => soc.activate().await,
126+
(PowerState::S5, PowerState::S0) => soc.power_on().await,
127+
128+
// S0 can then transition to any sleep state
129+
(PowerState::S0, PowerState::S0ix) => soc.idle().await,
130+
(PowerState::S0, PowerState::S3) => soc.suspend().await,
131+
(PowerState::S0, PowerState::S4) => soc.hibernate().await,
132+
(PowerState::S0, PowerState::S5) => soc.power_off().await,
133+
134+
// Anything else is an invalid transition
135+
_ => return Err(Error::InvalidStateTransition),
136+
}
137+
.map_err(|_| Error::PowerSequence)?;
138+
139+
self.power_state.sender().send(state);
140+
Ok(())
141+
}
142+
}
143+
144+
#[cfg(test)]
145+
#[allow(clippy::unwrap_used)]
146+
mod tests {
147+
use super::*;
148+
use embedded_power_sequence::{ErrorKind, ErrorType};
149+
150+
/// A mock SoC that always succeeds power transitions.
151+
struct MockSoc;
152+
153+
impl ErrorType for MockSoc {
154+
type Error = ErrorKind;
155+
}
156+
157+
impl PowerSequence for MockSoc {
158+
async fn power_on(&mut self) -> Result<(), Self::Error> {
159+
Ok(())
160+
}
161+
162+
async fn pre_power_on(&mut self) -> Result<(), Self::Error> {
163+
Ok(())
164+
}
165+
166+
async fn post_power_on(&mut self) -> Result<(), Self::Error> {
167+
Ok(())
168+
}
169+
170+
async fn power_off(&mut self) -> Result<(), Self::Error> {
171+
Ok(())
172+
}
173+
174+
async fn pre_power_off(&mut self) -> Result<(), Self::Error> {
175+
Ok(())
176+
}
177+
178+
async fn post_power_off(&mut self) -> Result<(), Self::Error> {
179+
Ok(())
180+
}
181+
}
182+
183+
#[tokio::test]
184+
async fn test_state_transitions() {
185+
let sm = SocManager::<MockSoc, 2>::new(MockSoc, PowerState::S5);
186+
187+
// Verify that we can't directly transition to a sleeping state (S3)
188+
assert!(matches!(
189+
sm.set_power_state(PowerState::S3).await,
190+
Err(Error::InvalidStateTransition)
191+
));
192+
193+
// Verify state remains unchanged
194+
assert!(sm.current_state().unwrap() == PowerState::S5);
195+
196+
// Verify we can transition to S0
197+
assert!(sm.set_power_state(PowerState::S0).await.is_ok());
198+
199+
// Verify state has changed to S0
200+
assert!(sm.current_state().unwrap() == PowerState::S0);
201+
202+
// Verify we can then transition to a sleeping state (S3)
203+
assert!(sm.set_power_state(PowerState::S3).await.is_ok());
204+
205+
// Verify state has changed to S3
206+
assert!(sm.current_state().unwrap() == PowerState::S3);
207+
}
208+
209+
#[tokio::test]
210+
async fn test_power_state_listener() {
211+
let sm = SocManager::<MockSoc, 2>::new(MockSoc, PowerState::S5);
212+
213+
// Create two listeners
214+
let mut l1 = sm.new_pwr_listener().unwrap();
215+
let mut l2 = sm.new_pwr_listener().unwrap();
216+
217+
// Verify we can't create a third
218+
assert!(matches!(sm.new_pwr_listener(), Err(Error::ListenersNotAvailable)));
219+
220+
// Verify listeners can read initial state
221+
assert_eq!(l1.current_state().unwrap(), PowerState::S5);
222+
assert_eq!(l2.current_state().unwrap(), PowerState::S5);
223+
224+
// Verify listeners can read updated state
225+
sm.set_power_state(PowerState::S0).await.unwrap();
226+
assert_eq!(l1.current_state().unwrap(), PowerState::S0);
227+
assert_eq!(l2.current_state().unwrap(), PowerState::S0);
228+
229+
// Verify listeners can wait for state changes
230+
sm.set_power_state(PowerState::S0ix).await.unwrap();
231+
assert_eq!(l1.wait_state_change().await, PowerState::S0ix);
232+
assert_eq!(l2.wait_state_change().await, PowerState::S0ix);
233+
234+
// Verify listeners can wait for specific state change
235+
sm.set_power_state(PowerState::S0).await.unwrap();
236+
l1.wait_for_state(PowerState::S0).await;
237+
l2.wait_for_state(PowerState::S0).await;
238+
// If we got here then they succesfully waited for S0
239+
}
240+
}

0 commit comments

Comments
 (0)