From 1e61cdf7d0b3d9ce9e81ecabc265d36abfc74469 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 3 Nov 2025 10:05:39 -0800 Subject: [PATCH 01/22] embedded-service/power/policy: Convert to async calls --- .../src/power/policy/action/device.rs | 62 ++++++++---- .../src/power/policy/action/policy.rs | 99 ++++++++++--------- embedded-service/src/power/policy/device.rs | 71 +++++++------ embedded-service/src/power/policy/policy.rs | 49 ++++++--- 4 files changed, 176 insertions(+), 105 deletions(-) diff --git a/embedded-service/src/power/policy/action/device.rs b/embedded-service/src/power/policy/action/device.rs index 01d0d6c36..a8c192a69 100644 --- a/embedded-service/src/power/policy/action/device.rs +++ b/embedded-service/src/power/policy/action/device.rs @@ -1,27 +1,38 @@ //! Device state machine actions use super::*; +use crate::power::policy::device::DeviceTrait; use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device, policy}; +use crate::sync::Lockable; use crate::{info, trace}; /// Device state machine control -pub struct Device<'a, S: Kind> { - device: &'a device::Device, +pub struct Device<'a, C: Lockable, S: Kind> +where + C::Inner: DeviceTrait, +{ + device: &'a device::Device<'a, C>, _state: core::marker::PhantomData, } /// Enum to contain any state -pub enum AnyState<'a> { +pub enum AnyState<'a, C: Lockable> +where + C::Inner: DeviceTrait, +{ /// Detached - Detached(Device<'a, Detached>), + Detached(Device<'a, C, Detached>), /// Idle - Idle(Device<'a, Idle>), + Idle(Device<'a, C, Idle>), /// Connected Consumer - ConnectedConsumer(Device<'a, ConnectedConsumer>), + ConnectedConsumer(Device<'a, C, ConnectedConsumer>), /// Connected Provider - ConnectedProvider(Device<'a, ConnectedProvider>), + ConnectedProvider(Device<'a, C, ConnectedProvider>), } -impl AnyState<'_> { +impl AnyState<'_, C> +where + C::Inner: DeviceTrait, +{ /// Return the kind of the contained state pub fn kind(&self) -> StateKind { match self { @@ -33,9 +44,12 @@ impl AnyState<'_> { } } -impl<'a, S: Kind> Device<'a, S> { +impl<'a, C: Lockable, S: Kind> Device<'a, C, S> +where + C::Inner: DeviceTrait, +{ /// Create a new state machine - pub(crate) fn new(device: &'a device::Device) -> Self { + pub(crate) fn new(device: &'a device::Device<'a, C>) -> Self { Self { device, _state: core::marker::PhantomData, @@ -43,7 +57,7 @@ impl<'a, S: Kind> Device<'a, S> { } /// Detach the device - pub async fn detach(self) -> Result, Error> { + pub async fn detach(self) -> Result, Error> { info!("Received detach from device {}", self.device.id().0); self.device.set_state(device::State::Detached).await; self.device.update_consumer_capability(None).await; @@ -107,9 +121,12 @@ impl<'a, S: Kind> Device<'a, S> { } } -impl<'a> Device<'a, Detached> { +impl<'a, C: Lockable> Device<'a, C, Detached> +where + C::Inner: DeviceTrait, +{ /// Attach the device - pub async fn attach(self) -> Result, Error> { + pub async fn attach(self) -> Result, Error> { info!("Received attach from device {}", self.device.id().0); self.device.set_state(device::State::Idle).await; policy::send_request(self.device.id(), policy::RequestData::NotifyAttached) @@ -119,7 +136,10 @@ impl<'a> Device<'a, Detached> { } } -impl Device<'_, Idle> { +impl Device<'_, C, Idle> +where + C::Inner: DeviceTrait, +{ /// Notify the power policy service of an updated consumer power capability pub async fn notify_consumer_power_capability( &self, @@ -134,9 +154,12 @@ impl Device<'_, Idle> { } } -impl<'a> Device<'a, ConnectedConsumer> { +impl<'a, C: Lockable> Device<'a, C, ConnectedConsumer> +where + C::Inner: DeviceTrait, +{ /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await?; Ok(Device::new(self.device)) } @@ -150,9 +173,12 @@ impl<'a> Device<'a, ConnectedConsumer> { } } -impl<'a> Device<'a, ConnectedProvider> { +impl<'a, C: Lockable> Device<'a, C, ConnectedProvider> +where + C::Inner: DeviceTrait, +{ /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await?; Ok(Device::new(self.device)) } diff --git a/embedded-service/src/power/policy/action/policy.rs b/embedded-service/src/power/policy/action/policy.rs index 0559421fc..8b956444a 100644 --- a/embedded-service/src/power/policy/action/policy.rs +++ b/embedded-service/src/power/policy/action/policy.rs @@ -2,31 +2,42 @@ use embassy_time::{Duration, TimeoutError, with_timeout}; use super::*; +use crate::power::policy::device::DeviceTrait; use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device}; +use crate::sync::Lockable; use crate::{error, info}; /// Default timeout for device commands to prevent the policy from getting stuck const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); /// Policy state machine control -pub struct Policy<'a, S: Kind> { - device: &'a device::Device, +pub struct Policy<'a, C: Lockable, S: Kind> +where + C::Inner: DeviceTrait, +{ + device: &'a device::Device<'a, C>, _state: core::marker::PhantomData, } /// Enum to contain any state -pub enum AnyState<'a> { +pub enum AnyState<'a, C: Lockable> +where + C::Inner: DeviceTrait, +{ /// Detached - Detached(Policy<'a, Detached>), + Detached(Policy<'a, C, Detached>), /// Idle - Idle(Policy<'a, Idle>), + Idle(Policy<'a, C, Idle>), /// Connected Consumer - ConnectedConsumer(Policy<'a, ConnectedConsumer>), + ConnectedConsumer(Policy<'a, C, ConnectedConsumer>), /// Connected Provider - ConnectedProvider(Policy<'a, ConnectedProvider>), + ConnectedProvider(Policy<'a, C, ConnectedProvider>), } -impl AnyState<'_> { +impl AnyState<'_, C> +where + C::Inner: DeviceTrait, +{ /// Return the kind of the contained state pub fn kind(&self) -> StateKind { match self { @@ -38,9 +49,12 @@ impl AnyState<'_> { } } -impl<'a, S: Kind> Policy<'a, S> { +impl<'a, C: Lockable, S: Kind> Policy<'a, C, S> +where + C::Inner: DeviceTrait, +{ /// Create a new state machine - pub(crate) fn new(device: &'a device::Device) -> Self { + pub(crate) fn new(device: &'a device::Device<'a, C>) -> Self { Self { device, _state: core::marker::PhantomData, @@ -50,10 +64,7 @@ impl<'a, S: Kind> Policy<'a, S> { /// Common disconnect function used by multiple states async fn disconnect_internal_no_timeout(&self) -> Result<(), Error> { info!("Device {} got disconnect request", self.device.id().0); - self.device - .execute_device_command(device::CommandData::Disconnect) - .await? - .complete_or_err()?; + self.device.device.lock().await.disconnect().await?; self.device.set_state(device::State::Idle).await; Ok(()) } @@ -69,12 +80,7 @@ impl<'a, S: Kind> Policy<'a, S> { /// Common connect as provider function used by multiple states async fn connect_as_provider_internal_no_timeout(&self, capability: ProviderPowerCapability) -> Result<(), Error> { info!("Device {} connecting provider", self.device.id().0); - - self.device - .execute_device_command(device::CommandData::ConnectAsProvider(capability)) - .await? - .complete_or_err()?; - + self.device.device.lock().await.connect_provider(capability).await?; self.device .set_state(device::State::ConnectedProvider(capability)) .await; @@ -97,20 +103,20 @@ impl<'a, S: Kind> Policy<'a, S> { } // The policy can do nothing when no device is attached -impl Policy<'_, Detached> {} +impl Policy<'_, C, Detached> where C::Inner: DeviceTrait {} -impl<'a> Policy<'a, Idle> { +impl<'a, C: Lockable> Policy<'a, C, Idle> +where + C::Inner: DeviceTrait, +{ /// Connect this device as a consumer pub async fn connect_as_consumer_no_timeout( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { info!("Device {} connecting as consumer", self.device.id().0); - self.device - .execute_device_command(device::CommandData::ConnectAsConsumer(capability)) - .await? - .complete_or_err()?; + self.device.device.lock().await.connect_consumer(capability).await?; self.device .set_state(device::State::ConnectedConsumer(capability)) @@ -122,7 +128,7 @@ impl<'a> Policy<'a, Idle> { pub async fn connect_consumer( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -133,7 +139,7 @@ impl<'a> Policy<'a, Idle> { pub async fn connect_provider_no_timeout( self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_as_provider_internal_no_timeout(capability) .await .map(|_| Policy::new(self.device)) @@ -143,30 +149,36 @@ impl<'a> Policy<'a, Idle> { pub async fn connect_provider( self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_provider_internal(capability) .await .map(|_| Policy::new(self.device)) } } -impl<'a> Policy<'a, ConnectedConsumer> { +impl<'a, C: Lockable> Policy<'a, C, ConnectedConsumer> +where + C::Inner: DeviceTrait, +{ /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { + pub async fn disconnect_no_timeout(self) -> Result, Error> { self.disconnect_internal_no_timeout() .await .map(|_| Policy::new(self.device)) } /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await.map(|_| Policy::new(self.device)) } } -impl<'a> Policy<'a, ConnectedProvider> { +impl<'a, C: Lockable> Policy<'a, C, ConnectedProvider> +where + C::Inner: DeviceTrait, +{ /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { + pub async fn disconnect_no_timeout(self) -> Result, Error> { if let Err(e) = self.disconnect_internal_no_timeout().await { error!("Error disconnecting device {}: {:?}", self.device.id().0, e); return Err(e); @@ -175,7 +187,7 @@ impl<'a> Policy<'a, ConnectedProvider> { } /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.disconnect_no_timeout()).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -186,14 +198,9 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_as_consumer_no_timeout( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { info!("Device {} connecting as consumer", self.device.id().0); - - self.device - .execute_device_command(device::CommandData::ConnectAsConsumer(capability)) - .await? - .complete_or_err()?; - + self.device.device.lock().await.connect_consumer(capability).await?; self.device .set_state(device::State::ConnectedConsumer(capability)) .await; @@ -204,7 +211,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_consumer( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -215,7 +222,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_provider_no_timeout( &self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_as_provider_internal_no_timeout(capability) .await .map(|_| Policy::new(self.device)) @@ -225,7 +232,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_provider( &self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_provider_internal(capability) .await .map(|_| Policy::new(self.device)) diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 23a6cc052..b5332368e 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -4,8 +4,8 @@ use core::ops::DerefMut; use embassy_sync::mutex::Mutex; use super::{DeviceId, Error, action}; -use crate::ipc::deferred; use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; +use crate::sync::Lockable; use crate::{GlobalRawMutex, intrusive_list}; /// Most basic device states @@ -110,21 +110,37 @@ pub struct Response { pub data: ResponseData, } +/// Trait for devices that can be controlled by a power policy implementation +pub trait DeviceTrait { + /// Disconnect power from this device + fn disconnect(&mut self) -> impl Future>; + /// Connect this device to provide power to an external connection + fn connect_provider(&mut self, capability: ProviderPowerCapability) -> impl Future>; + /// Connect this device to consume power from an external connection + fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> impl Future>; +} + /// Device struct -pub struct Device { +pub struct Device<'a, C: Lockable> +where + C::Inner: DeviceTrait, +{ /// Intrusive list node node: intrusive_list::Node, /// Device ID id: DeviceId, /// Current state of the device state: Mutex, - /// Command channel - command: deferred::Channel, + /// Reference to hardware + pub(crate) device: &'a C, } -impl Device { +impl<'a, C: Lockable> Device<'a, C> +where + C::Inner: DeviceTrait, +{ /// Create a new device - pub fn new(id: DeviceId) -> Self { + pub fn new(id: DeviceId, device: &'a C) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -133,7 +149,7 @@ impl Device { consumer_capability: None, requested_provider_capability: None, }), - command: deferred::Channel::new(), + device, } } @@ -175,18 +191,6 @@ impl Device { self.state().await.kind() == StateKind::ConnectedProvider } - /// Execute a command on the device - pub(super) async fn execute_device_command(&self, command: CommandData) -> Result { - self.command.execute(command).await - } - - /// Create a handler for the command channel - /// - /// DROP SAFETY: Direct call to deferred channel primitive - pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, CommandData, InternalResponseData> { - self.command.receive().await - } - /// Internal function to set device state pub(super) async fn set_state(&self, new_state: State) { let mut lock = self.state.lock().await; @@ -209,7 +213,7 @@ impl Device { } /// Try to provide access to the device actions for the given state - pub async fn try_device_action(&self) -> Result, Error> { + pub async fn try_device_action(&self) -> Result, Error> { let state = self.state().await.kind(); if S::kind() != state { return Err(Error::InvalidState(S::kind(), state)); @@ -218,7 +222,7 @@ impl Device { } /// Provide access to the current device state - pub async fn device_action(&self) -> action::device::AnyState<'_> { + pub async fn device_action(&self) -> action::device::AnyState<'_, C> { match self.state().await.kind() { StateKind::Detached => action::device::AnyState::Detached(action::device::Device::new(self)), StateKind::Idle => action::device::AnyState::Idle(action::device::Device::new(self)), @@ -233,7 +237,7 @@ impl Device { /// Try to provide access to the policy actions for the given state /// Implemented here for lifetime reasons - pub(super) async fn try_policy_action(&self) -> Result, Error> { + pub(super) async fn try_policy_action(&self) -> Result, Error> { let state = self.state().await.kind(); if S::kind() != state { return Err(Error::InvalidState(S::kind(), state)); @@ -243,7 +247,7 @@ impl Device { /// Provide access to the current policy actions /// Implemented here for lifetime reasons - pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_> { + pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_, C> { match self.state().await.kind() { StateKind::Detached => action::policy::AnyState::Detached(action::policy::Policy::new(self)), StateKind::Idle => action::policy::AnyState::Idle(action::policy::Policy::new(self)), @@ -257,7 +261,7 @@ impl Device { } /// Detach the device, this action is available in all states - pub async fn detach(&self) -> Result, Error> { + pub async fn detach(&self) -> Result, Error> { match self.device_action().await { action::device::AnyState::Detached(state) => Ok(state), action::device::AnyState::Idle(state) => state.detach().await, @@ -267,20 +271,29 @@ impl Device { } } -impl intrusive_list::NodeContainer for Device { +impl intrusive_list::NodeContainer for Device<'static, C> +where + C::Inner: DeviceTrait, +{ fn get_node(&self) -> &crate::Node { &self.node } } /// Trait for any container that holds a device -pub trait DeviceContainer { +pub trait DeviceContainer +where + C::Inner: DeviceTrait, +{ /// Get the underlying device struct - fn get_power_policy_device(&self) -> &Device; + fn get_power_policy_device(&self) -> &Device<'_, C>; } -impl DeviceContainer for Device { - fn get_power_policy_device(&self) -> &Device { +impl DeviceContainer for Device<'_, C> +where + C::Inner: DeviceTrait, +{ + fn get_power_policy_device(&self) -> &Device<'_, C> { self } } diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 3e3d694bb..0d8a1ebeb 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -3,7 +3,9 @@ use core::sync::atomic::{AtomicBool, Ordering}; use crate::GlobalRawMutex; use crate::broadcaster::immediate as broadcaster; +use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; +use crate::sync::Lockable; use embassy_sync::channel::Channel; use super::charger::ChargerResponse; @@ -103,9 +105,14 @@ static CONTEXT: Context = Context::new(); pub fn init() {} /// Register a device with the power policy service -pub fn register_device(device: &'static impl device::DeviceContainer) -> Result<(), intrusive_list::Error> { +pub async fn register_device( + device: &'static impl device::DeviceContainer, +) -> Result<(), intrusive_list::Error> +where + C::Inner: DeviceTrait, +{ let device = device.get_power_policy_device(); - if get_device(device.id()).is_some() { + if get_device::(device.id()).await.is_some() { return Err(intrusive_list::Error::NodeAlreadyInList); } @@ -123,9 +130,12 @@ pub fn register_charger(device: &'static impl charger::ChargerContainer) -> Resu } /// Find a device by its ID -fn get_device(id: DeviceId) -> Option<&'static device::Device> { - for device in &CONTEXT.devices { - if let Some(data) = device.data::() { +async fn get_device(id: DeviceId) -> Option<&'static device::Device<'static, C>> +where + C::Inner: DeviceTrait, +{ + for device in &CONTEXT.get().await.devices { + if let Some(data) = device.data::>() { if data.id() == id { return Some(data); } @@ -244,8 +254,14 @@ impl ContextToken { } /// Get a device by its ID - pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device, Error> { - get_device(id).ok_or(Error::InvalidDevice) + pub async fn get_device( + &self, + id: DeviceId, + ) -> Result<&'static device::Device<'static, C>, Error> + where + C::Inner: DeviceTrait, + { + get_device(id).await.ok_or(Error::InvalidDevice) } /// Provides access to the device list @@ -264,16 +280,25 @@ impl ContextToken { } /// Try to provide access to the actions available to the policy for the given state and device - pub async fn try_policy_action( + pub async fn try_policy_action( &self, id: DeviceId, - ) -> Result, Error> { - self.get_device(id)?.try_policy_action().await + ) -> Result, Error> + where + C::Inner: DeviceTrait, + { + self.get_device(id).await?.try_policy_action().await } /// Provide access to current policy actions - pub async fn policy_action(&self, id: DeviceId) -> Result, Error> { - Ok(self.get_device(id)?.policy_action().await) + pub async fn policy_action( + &self, + id: DeviceId, + ) -> Result, Error> + where + C::Inner: DeviceTrait, + { + Ok(self.get_device(id).await?.policy_action().await) } /// Broadcast a power policy message to all subscribers From 593f0617792f99cdbb4d71d55692781967abeb9a Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 3 Nov 2025 10:35:05 -0800 Subject: [PATCH 02/22] Add event traits and remove device state machine --- .../src/power/policy/action/device.rs | 198 ------------------ .../src/power/policy/action/mod.rs | 1 - embedded-service/src/power/policy/device.rs | 47 ----- embedded-service/src/power/policy/policy.rs | 108 ++++++---- 4 files changed, 67 insertions(+), 287 deletions(-) delete mode 100644 embedded-service/src/power/policy/action/device.rs diff --git a/embedded-service/src/power/policy/action/device.rs b/embedded-service/src/power/policy/action/device.rs deleted file mode 100644 index a8c192a69..000000000 --- a/embedded-service/src/power/policy/action/device.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! Device state machine actions -use super::*; -use crate::power::policy::device::DeviceTrait; -use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device, policy}; -use crate::sync::Lockable; -use crate::{info, trace}; - -/// Device state machine control -pub struct Device<'a, C: Lockable, S: Kind> -where - C::Inner: DeviceTrait, -{ - device: &'a device::Device<'a, C>, - _state: core::marker::PhantomData, -} - -/// Enum to contain any state -pub enum AnyState<'a, C: Lockable> -where - C::Inner: DeviceTrait, -{ - /// Detached - Detached(Device<'a, C, Detached>), - /// Idle - Idle(Device<'a, C, Idle>), - /// Connected Consumer - ConnectedConsumer(Device<'a, C, ConnectedConsumer>), - /// Connected Provider - ConnectedProvider(Device<'a, C, ConnectedProvider>), -} - -impl AnyState<'_, C> -where - C::Inner: DeviceTrait, -{ - /// Return the kind of the contained state - pub fn kind(&self) -> StateKind { - match self { - AnyState::Detached(_) => StateKind::Detached, - AnyState::Idle(_) => StateKind::Idle, - AnyState::ConnectedConsumer(_) => StateKind::ConnectedConsumer, - AnyState::ConnectedProvider(_) => StateKind::ConnectedProvider, - } - } -} - -impl<'a, C: Lockable, S: Kind> Device<'a, C, S> -where - C::Inner: DeviceTrait, -{ - /// Create a new state machine - pub(crate) fn new(device: &'a device::Device<'a, C>) -> Self { - Self { - device, - _state: core::marker::PhantomData, - } - } - - /// Detach the device - pub async fn detach(self) -> Result, Error> { - info!("Received detach from device {}", self.device.id().0); - self.device.set_state(device::State::Detached).await; - self.device.update_consumer_capability(None).await; - self.device.update_requested_provider_capability(None).await; - policy::send_request(self.device.id(), policy::RequestData::NotifyDetached) - .await? - .complete_or_err()?; - Ok(Device::new(self.device)) - } - - /// Disconnect this device - async fn disconnect_internal(&self) -> Result<(), Error> { - info!("Device {} disconnecting", self.device.id().0); - self.device.update_consumer_capability(None).await; - self.device.update_requested_provider_capability(None).await; - self.device.set_state(device::State::Idle).await; - policy::send_request(self.device.id(), policy::RequestData::NotifyDisconnect) - .await? - .complete_or_err() - } - - /// Notify the power policy service of an updated consumer power capability - async fn notify_consumer_power_capability_internal( - &self, - capability: Option, - ) -> Result<(), Error> { - info!( - "Device {} consume capability updated: {:#?}", - self.device.id().0, - capability - ); - self.device.update_consumer_capability(capability).await; - policy::send_request( - self.device.id(), - policy::RequestData::NotifyConsumerCapability(capability), - ) - .await? - .complete_or_err() - } - - /// Request the given power from the power policy service - async fn request_provider_power_capability_internal( - &self, - capability: ProviderPowerCapability, - ) -> Result<(), Error> { - if self.device.provider_capability().await == Some(capability) { - // Already operating at this capability, power policy is already aware, don't need to do anything - trace!("Device {} already requested: {:#?}", self.device.id().0, capability); - return Ok(()); - } - - info!("Request provide from device {}, {:#?}", self.device.id().0, capability); - self.device.update_requested_provider_capability(Some(capability)).await; - policy::send_request( - self.device.id(), - policy::RequestData::RequestProviderCapability(capability), - ) - .await? - .complete_or_err()?; - Ok(()) - } -} - -impl<'a, C: Lockable> Device<'a, C, Detached> -where - C::Inner: DeviceTrait, -{ - /// Attach the device - pub async fn attach(self) -> Result, Error> { - info!("Received attach from device {}", self.device.id().0); - self.device.set_state(device::State::Idle).await; - policy::send_request(self.device.id(), policy::RequestData::NotifyAttached) - .await? - .complete_or_err()?; - Ok(Device::new(self.device)) - } -} - -impl Device<'_, C, Idle> -where - C::Inner: DeviceTrait, -{ - /// Notify the power policy service of an updated consumer power capability - pub async fn notify_consumer_power_capability( - &self, - capability: Option, - ) -> Result<(), Error> { - self.notify_consumer_power_capability_internal(capability).await - } - - /// Request the given power from the power policy service - pub async fn request_provider_power_capability(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - self.request_provider_power_capability_internal(capability).await - } -} - -impl<'a, C: Lockable> Device<'a, C, ConnectedConsumer> -where - C::Inner: DeviceTrait, -{ - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - self.disconnect_internal().await?; - Ok(Device::new(self.device)) - } - - /// Notify the power policy service of an updated consumer power capability - pub async fn notify_consumer_power_capability( - &self, - capability: Option, - ) -> Result<(), Error> { - self.notify_consumer_power_capability_internal(capability).await - } -} - -impl<'a, C: Lockable> Device<'a, C, ConnectedProvider> -where - C::Inner: DeviceTrait, -{ - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - self.disconnect_internal().await?; - Ok(Device::new(self.device)) - } - - /// Request the given power from the power policy service - pub async fn request_provider_power_capability(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - self.request_provider_power_capability_internal(capability).await - } - - /// Notify the power policy service of an updated consumer power capability - pub async fn notify_consumer_power_capability( - &self, - capability: Option, - ) -> Result<(), Error> { - self.notify_consumer_power_capability_internal(capability).await - } -} diff --git a/embedded-service/src/power/policy/action/mod.rs b/embedded-service/src/power/policy/action/mod.rs index 889dccf70..822c1c4ea 100644 --- a/embedded-service/src/power/policy/action/mod.rs +++ b/embedded-service/src/power/policy/action/mod.rs @@ -2,7 +2,6 @@ //! This modules contains wrapper structs that use type states to enforce the valid actions for each device state use super::device::StateKind; -pub mod device; pub mod policy; trait Sealed {} diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index b5332368e..d3b9dd0a8 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -198,43 +198,6 @@ where state.state = new_state; } - /// Internal function to set consumer capability - pub(super) async fn update_consumer_capability(&self, capability: Option) { - let mut lock = self.state.lock().await; - let state = lock.deref_mut(); - state.consumer_capability = capability; - } - - /// Internal function to set requested provider capability - pub(super) async fn update_requested_provider_capability(&self, capability: Option) { - let mut lock = self.state.lock().await; - let state = lock.deref_mut(); - state.requested_provider_capability = capability; - } - - /// Try to provide access to the device actions for the given state - pub async fn try_device_action(&self) -> Result, Error> { - let state = self.state().await.kind(); - if S::kind() != state { - return Err(Error::InvalidState(S::kind(), state)); - } - Ok(action::device::Device::new(self)) - } - - /// Provide access to the current device state - pub async fn device_action(&self) -> action::device::AnyState<'_, C> { - match self.state().await.kind() { - StateKind::Detached => action::device::AnyState::Detached(action::device::Device::new(self)), - StateKind::Idle => action::device::AnyState::Idle(action::device::Device::new(self)), - StateKind::ConnectedProvider => { - action::device::AnyState::ConnectedProvider(action::device::Device::new(self)) - } - StateKind::ConnectedConsumer => { - action::device::AnyState::ConnectedConsumer(action::device::Device::new(self)) - } - } - } - /// Try to provide access to the policy actions for the given state /// Implemented here for lifetime reasons pub(super) async fn try_policy_action(&self) -> Result, Error> { @@ -259,16 +222,6 @@ where } } } - - /// Detach the device, this action is available in all states - pub async fn detach(&self) -> Result, Error> { - match self.device_action().await { - action::device::AnyState::Detached(state) => Ok(state), - action::device::AnyState::Idle(state) => state.detach().await, - action::device::AnyState::ConnectedProvider(state) => state.detach().await, - action::device::AnyState::ConnectedConsumer(state) => state.detach().await, - } - } } impl intrusive_list::NodeContainer for Device<'static, C> diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 0d8a1ebeb..4e4e37381 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -1,12 +1,11 @@ //! Context for any power policy implementations use core::sync::atomic::{AtomicBool, Ordering}; -use crate::GlobalRawMutex; use crate::broadcaster::immediate as broadcaster; use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; -use embassy_sync::channel::Channel; +use embassy_sync::once_lock::OnceLock; use super::charger::ChargerResponse; use super::device::{self}; @@ -14,23 +13,20 @@ use super::{DeviceId, Error, action, charger}; use crate::power::policy::charger::ChargerResponseData::Ack; use crate::{error, intrusive_list}; -/// Number of slots for policy requests -const POLICY_CHANNEL_SIZE: usize = 1; - /// Data for a power policy request #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RequestData { /// Notify that a device has attached - NotifyAttached, + Attached, /// Notify that available power for consumption has changed - NotifyConsumerCapability(Option), + UpdatedConsumerCapability(Option), /// Request the given amount of power to provider - RequestProviderCapability(ProviderPowerCapability), + RequestedProviderCapability(ProviderPowerCapability), /// Notify that a device cannot consume or provide power anymore - NotifyDisconnect, + Disconnected, /// Notify that a device has detached - NotifyDetached, + Detached, } /// Request to the power policy service @@ -70,17 +66,71 @@ pub struct Response { pub data: ResponseData, } -/// Wrapper type to make code cleaner -type InternalResponseData = Result; +/// Trait used by devices to send events to a power policy implementation +pub trait EventSender { + /// Try to send an event + fn try_send(&mut self, event: RequestData) -> Option<()>; + /// Send an event + fn send(&mut self, event: RequestData) -> impl Future; + + /// Wrapper to simplify sending this event + fn on_attach(&mut self) -> impl Future { + self.send(RequestData::Attached) + } + + /// Wrapper to simplify attempting to send this event + fn try_on_update_consumer_capability(&mut self, cap: Option) -> Option<()> { + self.try_send(RequestData::UpdatedConsumerCapability(cap)) + } + + /// Wrapper to simplify sending this event + fn on_update_consumer_capability(&mut self, cap: Option) -> impl Future { + self.send(RequestData::UpdatedConsumerCapability(cap)) + } + + /// Wrapper to simplify attempting to send this event + fn try_on_request_provider_capability(&mut self, cap: ProviderPowerCapability) -> Option<()> { + self.try_send(RequestData::RequestedProviderCapability(cap)) + } + + /// Wrapper to simplify sending this event + fn on_request_provider_capability(&mut self, cap: ProviderPowerCapability) -> impl Future { + self.send(RequestData::RequestedProviderCapability(cap)) + } + + /// Wrapper to simplify attempting to send this event + fn try_on_disconnect(&mut self) -> Option<()> { + self.try_send(RequestData::Disconnected) + } + + /// Wrapper to simplify sending this event + fn on_disconnect(&mut self) -> impl Future { + self.send(RequestData::Disconnected) + } + + /// Wrapper to simplify attempting to send this event + fn try_on_detach(&mut self) -> Option<()> { + self.try_send(RequestData::Detached) + } + + /// Wrapper to simplify sending this event + fn on_detach(&mut self) -> impl Future { + self.send(RequestData::Detached) + } +} + +/// Receiver trait used by a policy implementation +pub trait EventReceiver { + /// Attempt to get a pending event + fn try_next(&mut self) -> Option; + /// Wait for the next event + fn wait_next(&mut self) -> impl Future; +} /// Power policy context struct Context { /// Registered devices devices: intrusive_list::IntrusiveList, - /// Policy request - policy_request: Channel, - /// Policy response - policy_response: Channel, /// Registered chargers chargers: intrusive_list::IntrusiveList, /// Message broadcaster @@ -92,9 +142,7 @@ impl Context { Self { devices: intrusive_list::IntrusiveList::new(), chargers: intrusive_list::IntrusiveList::new(), - policy_request: Channel::new(), - policy_response: Channel::new(), - broadcaster: broadcaster::Immediate::new(), + broadcaster: broadcaster::Immediate::default(), } } } @@ -175,18 +223,6 @@ fn get_charger(id: charger::ChargerId) -> Option<&'static charger::Device> { None } -/// Convenience function to send a request to the power policy service -pub(super) async fn send_request(from: DeviceId, request: RequestData) -> Result { - CONTEXT - .policy_request - .send(Request { - id: from, - data: request, - }) - .await; - CONTEXT.policy_response.receive().await -} - /// Initialize chargers in hardware pub async fn init_chargers() -> ChargerResponse { for charger in &CONTEXT.chargers { @@ -243,16 +279,6 @@ impl ContextToken { Ok(()) } - /// Wait for a power policy request - pub async fn wait_request(&self) -> Request { - CONTEXT.policy_request.receive().await - } - - /// Send a response to a power policy request - pub async fn send_response(&self, response: Result) { - CONTEXT.policy_response.send(response).await - } - /// Get a device by its ID pub async fn get_device( &self, From 46cdef585c082b910012a353e30e9f370ca6e21d Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 3 Nov 2025 10:43:08 -0800 Subject: [PATCH 03/22] Introduce receiver generic argument --- .../src/power/policy/action/policy.rs | 53 ++++++++++--------- embedded-service/src/power/policy/device.rs | 28 ++++++---- embedded-service/src/power/policy/policy.rs | 24 +++++---- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/embedded-service/src/power/policy/action/policy.rs b/embedded-service/src/power/policy/action/policy.rs index 8b956444a..7f1b26728 100644 --- a/embedded-service/src/power/policy/action/policy.rs +++ b/embedded-service/src/power/policy/action/policy.rs @@ -3,6 +3,7 @@ use embassy_time::{Duration, TimeoutError, with_timeout}; use super::*; use crate::power::policy::device::DeviceTrait; +use crate::power::policy::policy::EventReceiver; use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device}; use crate::sync::Lockable; use crate::{error, info}; @@ -11,30 +12,30 @@ use crate::{error, info}; const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); /// Policy state machine control -pub struct Policy<'a, C: Lockable, S: Kind> +pub struct Policy<'a, C: Lockable, R: EventReceiver, S: Kind> where C::Inner: DeviceTrait, { - device: &'a device::Device<'a, C>, + device: &'a device::Device<'a, C, R>, _state: core::marker::PhantomData, } /// Enum to contain any state -pub enum AnyState<'a, C: Lockable> +pub enum AnyState<'a, C: Lockable, R: EventReceiver> where C::Inner: DeviceTrait, { /// Detached - Detached(Policy<'a, C, Detached>), + Detached(Policy<'a, C, R, Detached>), /// Idle - Idle(Policy<'a, C, Idle>), + Idle(Policy<'a, C, R, Idle>), /// Connected Consumer - ConnectedConsumer(Policy<'a, C, ConnectedConsumer>), + ConnectedConsumer(Policy<'a, C, R, ConnectedConsumer>), /// Connected Provider - ConnectedProvider(Policy<'a, C, ConnectedProvider>), + ConnectedProvider(Policy<'a, C, R, ConnectedProvider>), } -impl AnyState<'_, C> +impl AnyState<'_, C, R> where C::Inner: DeviceTrait, { @@ -49,12 +50,12 @@ where } } -impl<'a, C: Lockable, S: Kind> Policy<'a, C, S> +impl<'a, C: Lockable, R: EventReceiver, S: Kind> Policy<'a, C, R, S> where C::Inner: DeviceTrait, { /// Create a new state machine - pub(crate) fn new(device: &'a device::Device<'a, C>) -> Self { + pub(crate) fn new(device: &'a device::Device<'a, C, R>) -> Self { Self { device, _state: core::marker::PhantomData, @@ -103,9 +104,9 @@ where } // The policy can do nothing when no device is attached -impl Policy<'_, C, Detached> where C::Inner: DeviceTrait {} +impl Policy<'_, C, R, Detached> where C::Inner: DeviceTrait {} -impl<'a, C: Lockable> Policy<'a, C, Idle> +impl<'a, C: Lockable, R: EventReceiver> Policy<'a, C, R, Idle> where C::Inner: DeviceTrait, { @@ -113,7 +114,7 @@ where pub async fn connect_as_consumer_no_timeout( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { info!("Device {} connecting as consumer", self.device.id().0); self.device.device.lock().await.connect_consumer(capability).await?; @@ -128,7 +129,7 @@ where pub async fn connect_consumer( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -139,7 +140,7 @@ where pub async fn connect_provider_no_timeout( self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_as_provider_internal_no_timeout(capability) .await .map(|_| Policy::new(self.device)) @@ -149,36 +150,36 @@ where pub async fn connect_provider( self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_provider_internal(capability) .await .map(|_| Policy::new(self.device)) } } -impl<'a, C: Lockable> Policy<'a, C, ConnectedConsumer> +impl<'a, C: Lockable, R: EventReceiver> Policy<'a, C, R, ConnectedConsumer> where C::Inner: DeviceTrait, { /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { + pub async fn disconnect_no_timeout(self) -> Result, Error> { self.disconnect_internal_no_timeout() .await .map(|_| Policy::new(self.device)) } /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await.map(|_| Policy::new(self.device)) } } -impl<'a, C: Lockable> Policy<'a, C, ConnectedProvider> +impl<'a, C: Lockable, R: EventReceiver> Policy<'a, C, R, ConnectedProvider> where C::Inner: DeviceTrait, { /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { + pub async fn disconnect_no_timeout(self) -> Result, Error> { if let Err(e) = self.disconnect_internal_no_timeout().await { error!("Error disconnecting device {}: {:?}", self.device.id().0, e); return Err(e); @@ -187,7 +188,7 @@ where } /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.disconnect_no_timeout()).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -198,7 +199,7 @@ where pub async fn connect_as_consumer_no_timeout( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { info!("Device {} connecting as consumer", self.device.id().0); self.device.device.lock().await.connect_consumer(capability).await?; self.device @@ -211,7 +212,7 @@ where pub async fn connect_consumer( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -222,7 +223,7 @@ where pub async fn connect_provider_no_timeout( &self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_as_provider_internal_no_timeout(capability) .await .map(|_| Policy::new(self.device)) @@ -232,7 +233,7 @@ where pub async fn connect_provider( &self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_provider_internal(capability) .await .map(|_| Policy::new(self.device)) diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index d3b9dd0a8..57eafd640 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -4,6 +4,7 @@ use core::ops::DerefMut; use embassy_sync::mutex::Mutex; use super::{DeviceId, Error, action}; +use crate::power::policy::policy::EventReceiver; use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; use crate::{GlobalRawMutex, intrusive_list}; @@ -121,7 +122,7 @@ pub trait DeviceTrait { } /// Device struct -pub struct Device<'a, C: Lockable> +pub struct Device<'a, C: Lockable, R: EventReceiver> where C::Inner: DeviceTrait, { @@ -132,15 +133,17 @@ where /// Current state of the device state: Mutex, /// Reference to hardware - pub(crate) device: &'a C, + pub device: &'a C, + /// Event receiver + pub receiver: &'a R, } -impl<'a, C: Lockable> Device<'a, C> +impl<'a, C: Lockable, R: EventReceiver> Device<'a, C, R> where C::Inner: DeviceTrait, { /// Create a new device - pub fn new(id: DeviceId, device: &'a C) -> Self { + pub fn new(id: DeviceId, device: &'a C, receiver: &'a R) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -150,6 +153,7 @@ where requested_provider_capability: None, }), device, + receiver, } } @@ -200,7 +204,9 @@ where /// Try to provide access to the policy actions for the given state /// Implemented here for lifetime reasons - pub(super) async fn try_policy_action(&self) -> Result, Error> { + pub(super) async fn try_policy_action( + &self, + ) -> Result, Error> { let state = self.state().await.kind(); if S::kind() != state { return Err(Error::InvalidState(S::kind(), state)); @@ -210,7 +216,7 @@ where /// Provide access to the current policy actions /// Implemented here for lifetime reasons - pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_, C> { + pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_, C, R> { match self.state().await.kind() { StateKind::Detached => action::policy::AnyState::Detached(action::policy::Policy::new(self)), StateKind::Idle => action::policy::AnyState::Idle(action::policy::Policy::new(self)), @@ -224,7 +230,7 @@ where } } -impl intrusive_list::NodeContainer for Device<'static, C> +impl intrusive_list::NodeContainer for Device<'static, C, R> where C::Inner: DeviceTrait, { @@ -234,19 +240,19 @@ where } /// Trait for any container that holds a device -pub trait DeviceContainer +pub trait DeviceContainer where C::Inner: DeviceTrait, { /// Get the underlying device struct - fn get_power_policy_device(&self) -> &Device<'_, C>; + fn get_power_policy_device(&self) -> &Device<'_, C, R>; } -impl DeviceContainer for Device<'_, C> +impl DeviceContainer for Device<'_, C, R> where C::Inner: DeviceTrait, { - fn get_power_policy_device(&self) -> &Device<'_, C> { + fn get_power_policy_device(&self) -> &Device<'_, C, R> { self } } diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 4e4e37381..3bc9640fa 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -153,14 +153,14 @@ static CONTEXT: Context = Context::new(); pub fn init() {} /// Register a device with the power policy service -pub async fn register_device( - device: &'static impl device::DeviceContainer, +pub async fn register_device( + device: &'static impl device::DeviceContainer, ) -> Result<(), intrusive_list::Error> where C::Inner: DeviceTrait, { let device = device.get_power_policy_device(); - if get_device::(device.id()).await.is_some() { + if get_device::(device.id()).await.is_some() { return Err(intrusive_list::Error::NodeAlreadyInList); } @@ -178,12 +178,14 @@ pub fn register_charger(device: &'static impl charger::ChargerContainer) -> Resu } /// Find a device by its ID -async fn get_device(id: DeviceId) -> Option<&'static device::Device<'static, C>> +async fn get_device( + id: DeviceId, +) -> Option<&'static device::Device<'static, C, R>> where C::Inner: DeviceTrait, { for device in &CONTEXT.get().await.devices { - if let Some(data) = device.data::>() { + if let Some(data) = device.data::>() { if data.id() == id { return Some(data); } @@ -280,10 +282,10 @@ impl ContextToken { } /// Get a device by its ID - pub async fn get_device( + pub async fn get_device( &self, id: DeviceId, - ) -> Result<&'static device::Device<'static, C>, Error> + ) -> Result<&'static device::Device<'static, C, R>, Error> where C::Inner: DeviceTrait, { @@ -306,10 +308,10 @@ impl ContextToken { } /// Try to provide access to the actions available to the policy for the given state and device - pub async fn try_policy_action( + pub async fn try_policy_action( &self, id: DeviceId, - ) -> Result, Error> + ) -> Result, Error> where C::Inner: DeviceTrait, { @@ -317,10 +319,10 @@ impl ContextToken { } /// Provide access to current policy actions - pub async fn policy_action( + pub async fn policy_action( &self, id: DeviceId, - ) -> Result, Error> + ) -> Result, Error> where C::Inner: DeviceTrait, { From 34701dd0acb1ff1f84f96c5df872398d932c2493 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 3 Nov 2025 11:19:13 -0800 Subject: [PATCH 04/22] Add device and receiver arguments to context token --- embedded-service/src/power/policy/policy.rs | 38 +++++++++------------ 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 3bc9640fa..03398de24 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -1,4 +1,5 @@ //! Context for any power policy implementations +use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; use crate::broadcaster::immediate as broadcaster; @@ -257,9 +258,17 @@ pub fn register_message_receiver( } /// Singleton struct to give access to the power policy context -pub struct ContextToken(()); +pub struct ContextToken +where + D::Inner: DeviceTrait, +{ + _phantom: PhantomData<(D, R)>, +} -impl ContextToken { +impl ContextToken +where + D::Inner: DeviceTrait, +{ /// Create a new context token, returning None if this function has been called before pub fn create() -> Option { static INIT: AtomicBool = AtomicBool::new(false); @@ -268,7 +277,7 @@ impl ContextToken { } INIT.store(true, Ordering::SeqCst); - Some(ContextToken(())) + Some(ContextToken { _phantom: PhantomData }) } /// Initialize Policy charger devices @@ -282,13 +291,7 @@ impl ContextToken { } /// Get a device by its ID - pub async fn get_device( - &self, - id: DeviceId, - ) -> Result<&'static device::Device<'static, C, R>, Error> - where - C::Inner: DeviceTrait, - { + pub async fn get_device(&self, id: DeviceId) -> Result<&'static device::Device<'static, D, R>, Error> { get_device(id).await.ok_or(Error::InvalidDevice) } @@ -308,24 +311,15 @@ impl ContextToken { } /// Try to provide access to the actions available to the policy for the given state and device - pub async fn try_policy_action( + pub async fn try_policy_action( &self, id: DeviceId, - ) -> Result, Error> - where - C::Inner: DeviceTrait, - { + ) -> Result, Error> { self.get_device(id).await?.try_policy_action().await } /// Provide access to current policy actions - pub async fn policy_action( - &self, - id: DeviceId, - ) -> Result, Error> - where - C::Inner: DeviceTrait, - { + pub async fn policy_action(&self, id: DeviceId) -> Result, Error> { Ok(self.get_device(id).await?.policy_action().await) } From 8f15e4799a22cb2151cd6fa91f323644f3524143 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 3 Nov 2025 13:09:16 -0800 Subject: [PATCH 05/22] Power policy refactor --- power-policy-service/src/consumer.rs | 13 +++++---- power-policy-service/src/lib.rs | 41 ++++++++++++++++------------ power-policy-service/src/provider.rs | 7 +++-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index 0600193fc..5f0b68b83 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -31,14 +31,17 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl PowerPolicy { +impl PowerPolicy +where + D::Inner: DeviceTrait, +{ /// Iterate over all devices to determine what is best power port provides the highest power async fn find_best_consumer(&self, state: &InternalState) -> Result, Error> { let mut best_consumer = None; let current_consumer_id = state.current_consumer_state.map(|f| f.device_id); - for node in self.context.devices() { - let device = node.data::().ok_or(Error::InvalidDevice)?; + for node in self.context.devices().await { + let device = node.data::>().ok_or(Error::InvalidDevice)?; let consumer_capability = device.consumer_capability().await; // Don't consider consumers below minimum threshold @@ -91,8 +94,8 @@ impl PowerPolicy { async fn update_unconstrained_state(&self, state: &mut InternalState) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for node in self.context.devices() { - let device = node.data::().ok_or(Error::InvalidDevice)?; + for node in self.context.devices().await { + let device = node.data::>().ok_or(Error::InvalidDevice)?; if let Some(capability) = device.consumer_capability().await { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index c43719f81..c1c11c66e 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -2,8 +2,10 @@ use core::ops::DerefMut; use embassy_sync::mutex::Mutex; use embedded_services::GlobalRawMutex; -use embedded_services::power::policy::device::Device; +use embedded_services::power::policy::device::{Device, DeviceTrait}; +use embedded_services::power::policy::policy::EventReceiver; use embedded_services::power::policy::{action, policy, *}; +use embedded_services::sync::Lockable; use embedded_services::{comms, error, info}; pub mod config; @@ -29,9 +31,12 @@ struct InternalState { } /// Power policy state -pub struct PowerPolicy { +pub struct PowerPolicy +where + D::Inner: DeviceTrait, +{ /// Power policy context - context: policy::ContextToken, + context: policy::ContextToken, /// State state: Mutex, /// Comms endpoint @@ -40,7 +45,10 @@ pub struct PowerPolicy { config: config::Config, } -impl PowerPolicy { +impl PowerPolicy +where + D::Inner: DeviceTrait, +{ /// Create a new power policy pub fn create(config: config::Config) -> Option { Some(Self { @@ -52,31 +60,25 @@ impl PowerPolicy { } async fn process_notify_attach(&self) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; Ok(()) } - async fn process_notify_detach(&self, device: &device::Device) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; - self.remove_connected_provider(device.id()).await; + async fn process_notify_detach(&self) -> Result<(), Error> { self.update_current_consumer().await?; Ok(()) } async fn process_notify_consumer_power_capability(&self) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; self.update_current_consumer().await?; Ok(()) } async fn process_request_provider_power_capabilities(&self, device: DeviceId) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; self.connect_provider(device).await; Ok(()) } - async fn process_notify_disconnect(&self, device: &device::Device) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; + async fn process_notify_disconnect(&self) -> Result<(), Error> { if let Some(consumer) = self.state.lock().await.current_consumer_state.take() { info!("Device{}: Connected consumer disconnected", consumer.device_id.0); self.disconnect_chargers().await?; @@ -124,15 +126,15 @@ impl PowerPolicy { let device = self.context.get_device(request.id)?; match request.data { - policy::RequestData::NotifyAttached => { + policy::RequestData::Attached => { info!("Received notify attached from device {}", device.id().0); self.process_notify_attach().await } - policy::RequestData::NotifyDetached => { + policy::RequestData::Detached => { info!("Received notify detached from device {}", device.id().0); self.process_notify_detach(device).await } - policy::RequestData::NotifyConsumerCapability(capability) => { + policy::RequestData::UpdatedConsumerCapability(capability) => { info!( "Device{}: Received notify consumer capability: {:#?}", device.id().0, @@ -140,7 +142,7 @@ impl PowerPolicy { ); self.process_notify_consumer_power_capability().await } - policy::RequestData::RequestProviderCapability(capability) => { + policy::RequestData::RequestedProviderCapability(capability) => { info!( "Device{}: Received request provider capability: {:#?}", device.id().0, @@ -148,7 +150,7 @@ impl PowerPolicy { ); self.process_request_provider_power_capabilities(device.id()).await } - policy::RequestData::NotifyDisconnect => { + policy::RequestData::Disconnected => { info!("Received notify disconnect from device {}", device.id().0); self.process_notify_disconnect(device).await } @@ -162,4 +164,7 @@ impl PowerPolicy { } } -impl comms::MailboxDelegate for PowerPolicy {} +impl comms::MailboxDelegate for PowerPolicy where + D::Inner: DeviceTrait +{ +} diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index 830509576..e588e4d02 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -25,7 +25,10 @@ pub(super) struct State { state: PowerState, } -impl PowerPolicy { +impl PowerPolicy +where + D::Inner: DeviceTrait, +{ /// Attempt to connect the requester as a provider pub(super) async fn connect_provider(&self, requester_id: DeviceId) { trace!("Device{}: Attempting to connect as provider", requester_id.0); @@ -48,7 +51,7 @@ impl PowerPolicy { let mut total_power_mw = 0; // Determine total requested power draw - for device in self.context.devices().iter_only::() { + for device in self.context.devices().await.iter_only::>() { let target_provider_cap = if device.id() == requester_id { // Use the requester's requested power capability // this handles both new connections and upgrade requests From bddad8aac18458ffc310ab376f10dc5e92e7d9ac Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 3 Nov 2025 14:39:49 -0800 Subject: [PATCH 06/22] Remove policy type state machine, power policy now builds --- embedded-service/Cargo.toml | 2 +- .../src/power/policy/action/mod.rs | 50 ---- .../src/power/policy/action/policy.rs | 246 ------------------ embedded-service/src/power/policy/device.rs | 42 +-- embedded-service/src/power/policy/mod.rs | 1 - embedded-service/src/power/policy/policy.rs | 48 ++-- examples/rt633/Cargo.lock | 1 + examples/rt685s-evk/Cargo.lock | 1 + examples/std/Cargo.lock | 1 + power-policy-service/src/consumer.rs | 48 ++-- power-policy-service/src/lib.rs | 42 ++- power-policy-service/src/provider.rs | 48 +--- 12 files changed, 100 insertions(+), 430 deletions(-) delete mode 100644 embedded-service/src/power/policy/action/mod.rs delete mode 100644 embedded-service/src/power/policy/action/policy.rs diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index 2570775c5..4199d9ec8 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -21,6 +21,7 @@ defmt = { workspace = true, optional = true } document-features.workspace = true embassy-sync.workspace = true embassy-time.workspace = true +embassy-futures.workspace = true embedded-batteries-async.workspace = true embedded-cfu-protocol.workspace = true embedded-hal-async.workspace = true @@ -49,7 +50,6 @@ cortex-m.workspace = true [dev-dependencies] critical-section = { workspace = true, features = ["std"] } -embassy-futures.workspace = true embassy-sync = { workspace = true, features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } embassy-time-driver = { workspace = true } diff --git a/embedded-service/src/power/policy/action/mod.rs b/embedded-service/src/power/policy/action/mod.rs deleted file mode 100644 index 822c1c4ea..000000000 --- a/embedded-service/src/power/policy/action/mod.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Power policy actions -//! This modules contains wrapper structs that use type states to enforce the valid actions for each device state -use super::device::StateKind; - -pub mod policy; - -trait Sealed {} - -/// Trait to provide the kind of a state type -#[allow(private_bounds)] -pub trait Kind: Sealed { - /// Return the kind of a state type - fn kind() -> StateKind; -} - -/// State type for a detached device -pub struct Detached; -impl Sealed for Detached {} -impl Kind for Detached { - fn kind() -> StateKind { - StateKind::Detached - } -} - -/// State type for an attached device -pub struct Idle; -impl Sealed for Idle {} -impl Kind for Idle { - fn kind() -> StateKind { - StateKind::Idle - } -} - -/// State type for a device that is providing power -pub struct ConnectedProvider; -impl Sealed for ConnectedProvider {} -impl Kind for ConnectedProvider { - fn kind() -> StateKind { - StateKind::ConnectedProvider - } -} - -/// State type for a device that is consuming power -pub struct ConnectedConsumer; -impl Sealed for ConnectedConsumer {} -impl Kind for ConnectedConsumer { - fn kind() -> StateKind { - StateKind::ConnectedConsumer - } -} diff --git a/embedded-service/src/power/policy/action/policy.rs b/embedded-service/src/power/policy/action/policy.rs deleted file mode 100644 index 7f1b26728..000000000 --- a/embedded-service/src/power/policy/action/policy.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Policy state machine -use embassy_time::{Duration, TimeoutError, with_timeout}; - -use super::*; -use crate::power::policy::device::DeviceTrait; -use crate::power::policy::policy::EventReceiver; -use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device}; -use crate::sync::Lockable; -use crate::{error, info}; - -/// Default timeout for device commands to prevent the policy from getting stuck -const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); - -/// Policy state machine control -pub struct Policy<'a, C: Lockable, R: EventReceiver, S: Kind> -where - C::Inner: DeviceTrait, -{ - device: &'a device::Device<'a, C, R>, - _state: core::marker::PhantomData, -} - -/// Enum to contain any state -pub enum AnyState<'a, C: Lockable, R: EventReceiver> -where - C::Inner: DeviceTrait, -{ - /// Detached - Detached(Policy<'a, C, R, Detached>), - /// Idle - Idle(Policy<'a, C, R, Idle>), - /// Connected Consumer - ConnectedConsumer(Policy<'a, C, R, ConnectedConsumer>), - /// Connected Provider - ConnectedProvider(Policy<'a, C, R, ConnectedProvider>), -} - -impl AnyState<'_, C, R> -where - C::Inner: DeviceTrait, -{ - /// Return the kind of the contained state - pub fn kind(&self) -> StateKind { - match self { - AnyState::Detached(_) => StateKind::Detached, - AnyState::Idle(_) => StateKind::Idle, - AnyState::ConnectedConsumer(_) => StateKind::ConnectedConsumer, - AnyState::ConnectedProvider(_) => StateKind::ConnectedProvider, - } - } -} - -impl<'a, C: Lockable, R: EventReceiver, S: Kind> Policy<'a, C, R, S> -where - C::Inner: DeviceTrait, -{ - /// Create a new state machine - pub(crate) fn new(device: &'a device::Device<'a, C, R>) -> Self { - Self { - device, - _state: core::marker::PhantomData, - } - } - - /// Common disconnect function used by multiple states - async fn disconnect_internal_no_timeout(&self) -> Result<(), Error> { - info!("Device {} got disconnect request", self.device.id().0); - self.device.device.lock().await.disconnect().await?; - self.device.set_state(device::State::Idle).await; - Ok(()) - } - - /// Common disconnect function used by multiple states - async fn disconnect_internal(&self) -> Result<(), Error> { - match with_timeout(DEFAULT_TIMEOUT, self.disconnect_internal_no_timeout()).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Common connect as provider function used by multiple states - async fn connect_as_provider_internal_no_timeout(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - info!("Device {} connecting provider", self.device.id().0); - self.device.device.lock().await.connect_provider(capability).await?; - self.device - .set_state(device::State::ConnectedProvider(capability)) - .await; - - Ok(()) - } - - /// Common connect provider function used by multiple states - async fn connect_provider_internal(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - match with_timeout( - DEFAULT_TIMEOUT, - self.connect_as_provider_internal_no_timeout(capability), - ) - .await - { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } -} - -// The policy can do nothing when no device is attached -impl Policy<'_, C, R, Detached> where C::Inner: DeviceTrait {} - -impl<'a, C: Lockable, R: EventReceiver> Policy<'a, C, R, Idle> -where - C::Inner: DeviceTrait, -{ - /// Connect this device as a consumer - pub async fn connect_as_consumer_no_timeout( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - info!("Device {} connecting as consumer", self.device.id().0); - - self.device.device.lock().await.connect_consumer(capability).await?; - - self.device - .set_state(device::State::ConnectedConsumer(capability)) - .await; - Ok(Policy::new(self.device)) - } - - /// Connect this device as a consumer - pub async fn connect_consumer( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Connect this device as a provider - pub async fn connect_provider_no_timeout( - self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_as_provider_internal_no_timeout(capability) - .await - .map(|_| Policy::new(self.device)) - } - - /// Connect this device as a provider - pub async fn connect_provider( - self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_provider_internal(capability) - .await - .map(|_| Policy::new(self.device)) - } -} - -impl<'a, C: Lockable, R: EventReceiver> Policy<'a, C, R, ConnectedConsumer> -where - C::Inner: DeviceTrait, -{ - /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { - self.disconnect_internal_no_timeout() - .await - .map(|_| Policy::new(self.device)) - } - - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - self.disconnect_internal().await.map(|_| Policy::new(self.device)) - } -} - -impl<'a, C: Lockable, R: EventReceiver> Policy<'a, C, R, ConnectedProvider> -where - C::Inner: DeviceTrait, -{ - /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { - if let Err(e) = self.disconnect_internal_no_timeout().await { - error!("Error disconnecting device {}: {:?}", self.device.id().0, e); - return Err(e); - } - Ok(Policy::new(self.device)) - } - - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - match with_timeout(DEFAULT_TIMEOUT, self.disconnect_no_timeout()).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Connect this device as a consumer - pub async fn connect_as_consumer_no_timeout( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - info!("Device {} connecting as consumer", self.device.id().0); - self.device.device.lock().await.connect_consumer(capability).await?; - self.device - .set_state(device::State::ConnectedConsumer(capability)) - .await; - Ok(Policy::new(self.device)) - } - - /// Connect this device as a consumer - pub async fn connect_consumer( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Connect this device as a provider - pub async fn connect_provider_no_timeout( - &self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_as_provider_internal_no_timeout(capability) - .await - .map(|_| Policy::new(self.device)) - } - - /// Connect this device as a provider - pub async fn connect_provider( - &self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_provider_internal(capability) - .await - .map(|_| Policy::new(self.device)) - } - - /// Get the provider power capability of this device - pub async fn power_capability(&self) -> Option { - self.device.provider_capability().await - } -} diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 57eafd640..42f221cb9 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -1,9 +1,7 @@ //! Device struct and methods -use core::ops::DerefMut; - use embassy_sync::mutex::Mutex; -use super::{DeviceId, Error, action}; +use super::{DeviceId, Error}; use crate::power::policy::policy::EventReceiver; use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; @@ -52,7 +50,7 @@ impl State { /// Internal device state for power policy #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct InternalState { +pub struct InternalState { /// Current state of the device pub state: State, /// Current consumer capability @@ -131,7 +129,7 @@ where /// Device ID id: DeviceId, /// Current state of the device - state: Mutex, + pub state: Mutex, /// Reference to hardware pub device: &'a C, /// Event receiver @@ -194,40 +192,6 @@ where pub async fn is_provider(&self) -> bool { self.state().await.kind() == StateKind::ConnectedProvider } - - /// Internal function to set device state - pub(super) async fn set_state(&self, new_state: State) { - let mut lock = self.state.lock().await; - let state = lock.deref_mut(); - state.state = new_state; - } - - /// Try to provide access to the policy actions for the given state - /// Implemented here for lifetime reasons - pub(super) async fn try_policy_action( - &self, - ) -> Result, Error> { - let state = self.state().await.kind(); - if S::kind() != state { - return Err(Error::InvalidState(S::kind(), state)); - } - Ok(action::policy::Policy::new(self)) - } - - /// Provide access to the current policy actions - /// Implemented here for lifetime reasons - pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_, C, R> { - match self.state().await.kind() { - StateKind::Detached => action::policy::AnyState::Detached(action::policy::Policy::new(self)), - StateKind::Idle => action::policy::AnyState::Idle(action::policy::Policy::new(self)), - StateKind::ConnectedProvider => { - action::policy::AnyState::ConnectedProvider(action::policy::Policy::new(self)) - } - StateKind::ConnectedConsumer => { - action::policy::AnyState::ConnectedConsumer(action::policy::Policy::new(self)) - } - } - } } impl intrusive_list::NodeContainer for Device<'static, C, R> diff --git a/embedded-service/src/power/policy/mod.rs b/embedded-service/src/power/policy/mod.rs index b925f860b..2ab5bbeb1 100644 --- a/embedded-service/src/power/policy/mod.rs +++ b/embedded-service/src/power/policy/mod.rs @@ -1,5 +1,4 @@ //! Power policy related data structures and messages -pub mod action; pub mod charger; pub mod device; pub mod flags; diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 03398de24..bb9bfd5eb 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -1,16 +1,18 @@ //! Context for any power policy implementations use core::marker::PhantomData; +use core::pin::pin; use core::sync::atomic::{AtomicBool, Ordering}; use crate::broadcaster::immediate as broadcaster; use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; +use embassy_futures::select::select_slice; use embassy_sync::once_lock::OnceLock; use super::charger::ChargerResponse; use super::device::{self}; -use super::{DeviceId, Error, action, charger}; +use super::{DeviceId, Error, charger}; use crate::power::policy::charger::ChargerResponseData::Ack; use crate::{error, intrusive_list}; @@ -23,7 +25,7 @@ pub enum RequestData { /// Notify that available power for consumption has changed UpdatedConsumerCapability(Option), /// Request the given amount of power to provider - RequestedProviderCapability(ProviderPowerCapability), + RequestedProviderCapability(Option), /// Notify that a device cannot consume or provide power anymore Disconnected, /// Notify that a device has detached @@ -90,12 +92,12 @@ pub trait EventSender { } /// Wrapper to simplify attempting to send this event - fn try_on_request_provider_capability(&mut self, cap: ProviderPowerCapability) -> Option<()> { + fn try_on_request_provider_capability(&mut self, cap: Option) -> Option<()> { self.try_send(RequestData::RequestedProviderCapability(cap)) } /// Wrapper to simplify sending this event - fn on_request_provider_capability(&mut self, cap: ProviderPowerCapability) -> impl Future { + fn on_request_provider_capability(&mut self, cap: Option) -> impl Future { self.send(RequestData::RequestedProviderCapability(cap)) } @@ -123,9 +125,9 @@ pub trait EventSender { /// Receiver trait used by a policy implementation pub trait EventReceiver { /// Attempt to get a pending event - fn try_next(&mut self) -> Option; + fn try_next(&self) -> Option; /// Wait for the next event - fn wait_next(&mut self) -> impl Future; + fn wait_next(&self) -> impl Future; } /// Power policy context @@ -310,21 +312,29 @@ where &CONTEXT.chargers } - /// Try to provide access to the actions available to the policy for the given state and device - pub async fn try_policy_action( - &self, - id: DeviceId, - ) -> Result, Error> { - self.get_device(id).await?.try_policy_action().await - } - - /// Provide access to current policy actions - pub async fn policy_action(&self, id: DeviceId) -> Result, Error> { - Ok(self.get_device(id).await?.policy_action().await) - } - /// Broadcast a power policy message to all subscribers pub async fn broadcast_message(&self, message: CommsMessage) { CONTEXT.broadcaster.broadcast(message).await; } + + /// Get the next pending device event + pub async fn wait_request(&self) -> Request { + let mut futures = heapless::Vec::<_, 16>::new(); + for device in self.devices().await.iter_only::>() { + // TODO: check this at compile time + let _ = futures.push(async { device.receiver.wait_next().await }); + } + + let (event, index) = select_slice(pin!(&mut futures)).await; + let device = self + .devices() + .await + .iter_only::>() + .nth(index) + .unwrap(); + Request { + id: device.id(), + data: event, + } + } } diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index c1215bae5..f93d05d0c 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -736,6 +736,7 @@ dependencies = [ "critical-section", "defmt 0.3.100", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 52cc130ee..a6ee8f96e 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -706,6 +706,7 @@ dependencies = [ "critical-section", "defmt 0.3.100", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index d714101dc..f7141241a 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -747,6 +747,7 @@ dependencies = [ "cortex-m-rt", "critical-section", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async", diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index 5f0b68b83..c9e81c68b 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -200,19 +200,11 @@ where } state.current_consumer_state = None; + let consumer_device = self.context.get_device(current_consumer.device_id).await?; // Disconnect the current consumer if needed - if let Ok(consumer) = self - .context - .try_policy_action::(current_consumer.device_id) - .await - { - info!( - "Device {}, disconnecting current consumer", - current_consumer.device_id.0 - ); - // disconnect current consumer and set idle - consumer.disconnect().await?; - } + info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); + // disconnect current consumer and set idle + consumer_device.device.lock().await.disconnect().await?; // If no chargers are registered, they won't receive the new power capability. // Also, if chargers return UnpoweredAck, that means the charger isn't powered. @@ -229,28 +221,26 @@ where } info!("Device {}, connecting new consumer", new_consumer.device_id.0); - if let Ok(idle) = self - .context - .try_policy_action::(new_consumer.device_id) - .await - { - idle.connect_consumer(new_consumer.consumer_power_capability).await?; - self.post_consumer_connected(state, new_consumer).await?; - } else if let Ok(provider) = self - .context - .try_policy_action::(new_consumer.device_id) - .await - { - provider + let device = self.context.get_device(new_consumer.device_id).await?; + let device_state = device.state().await; + + if matches!(device_state, device::State::Idle | device::State::ConnectedConsumer(_)) { + device + .device + .lock() + .await .connect_consumer(new_consumer.consumer_power_capability) .await?; - state.current_consumer_state = Some(new_consumer); self.post_consumer_connected(state, new_consumer).await?; + Ok(()) } else { - error!("Error obtaining device in idle state"); + error!( + "Device{}: Not ready to connect consumer, state: {:#?}", + device.id().0, + device_state + ); + Err(Error::InvalidState(device::StateKind::Idle, device_state.kind())) } - - Ok(()) } /// Determines and connects the best external power diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index c1c11c66e..e6d2f0060 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -2,9 +2,9 @@ use core::ops::DerefMut; use embassy_sync::mutex::Mutex; use embedded_services::GlobalRawMutex; -use embedded_services::power::policy::device::{Device, DeviceTrait}; +use embedded_services::power::policy::device::{Device, DeviceTrait, State}; use embedded_services::power::policy::policy::EventReceiver; -use embedded_services::power::policy::{action, policy, *}; +use embedded_services::power::policy::{policy, *}; use embedded_services::sync::Lockable; use embedded_services::{comms, error, info}; @@ -68,14 +68,34 @@ where Ok(()) } - async fn process_notify_consumer_power_capability(&self) -> Result<(), Error> { - self.update_current_consumer().await?; - Ok(()) + async fn process_notify_consumer_power_capability( + &self, + device: DeviceId, + capability: Option, + ) -> Result<(), Error> { + let device = self.context.get_device(device).await?; + match device.state().await { + State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => { + device.state.lock().await.consumer_capability = capability; + self.update_current_consumer().await + } + State::Detached => device.device.lock().await.disconnect().await, + } } - async fn process_request_provider_power_capabilities(&self, device: DeviceId) -> Result<(), Error> { - self.connect_provider(device).await; - Ok(()) + async fn process_request_provider_power_capabilities( + &self, + device: DeviceId, + capability: Option, + ) -> Result<(), Error> { + let device = self.context.get_device(device).await?; + match device.state().await { + State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => { + device.state.lock().await.requested_provider_capability = capability; + self.connect_provider(device.id()).await + } + State::Detached => device.device.lock().await.disconnect().await, + } } async fn process_notify_disconnect(&self) -> Result<(), Error> { @@ -140,7 +160,8 @@ where device.id().0, capability, ); - self.process_notify_consumer_power_capability().await + self.process_notify_consumer_power_capability(device.id(), capability) + .await } policy::RequestData::RequestedProviderCapability(capability) => { info!( @@ -148,7 +169,8 @@ where device.id().0, capability, ); - self.process_request_provider_power_capabilities(device.id()).await + self.process_request_provider_power_capabilities(device.id(), capability) + .await } policy::RequestData::Disconnected => { info!("Received notify disconnect from device {}", device.id().0); diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index e588e4d02..f09057a57 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -30,21 +30,15 @@ where D::Inner: DeviceTrait, { /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&self, requester_id: DeviceId) { + pub(super) async fn connect_provider(&self, requester_id: DeviceId) -> Result<(), Error> { trace!("Device{}: Attempting to connect as provider", requester_id.0); - let requester = match self.context.get_device(requester_id) { - Ok(device) => device, - Err(_) => { - error!("Device{}: Invalid device", requester_id.0); - return; - } - }; + let requester = self.context.get_device(requester_id).await?; let requested_power_capability = match requester.requested_provider_capability().await { Some(cap) => cap, // Requester is no longer requesting power _ => { - info!("Device{}: No-longer requesting power", requester.id().0); - return; + error!("Device{}: No-longer requesting power", requester.id().0); + return Err(Error::CannotProvide(None)); } }; let mut state = self.state.lock().await; @@ -90,36 +84,20 @@ where } }; - let connected = if let Ok(action) = self.context.try_policy_action::(requester.id()).await { - if let Err(e) = action.connect_provider(target_power).await { - error!("Device{}: Failed to connect as provider, {:#?}", requester.id().0, e); - } else { - self.post_provider_connected(&mut state, requester.id(), target_power) - .await; - } - Ok(()) - } else if let Ok(action) = self - .context - .try_policy_action::(requester.id()) - .await - { - if let Err(e) = action.connect_provider(target_power).await { - error!("Device{}: Failed to connect as provider, {:#?}", requester.id().0, e); - } else { - self.post_provider_connected(&mut state, requester.id(), target_power) - .await; - } - Ok(()) + let device = self.context.get_device(requester_id).await?; + let state = device.state().await; + if matches!(state, device::State::Idle | device::State::ConnectedProvider(_)) { + device.device.lock().await.connect_provider(target_power).await } else { + error!( + "Device{}: Cannot provide, device is in state {:#?}", + device.id().0, + state + ); Err(Error::InvalidState( device::StateKind::Idle, requester.state().await.kind(), )) - }; - - // Don't need to do anything special, the device is responsible for attempting to reconnect - if let Err(e) = connected { - error!("Device{}: Failed to connect as provider, {:#?}", requester.id().0, e); } } From 81de3e83be89f64fef18eea6b1729bc228b45d53 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Tue, 4 Nov 2025 15:12:16 -0800 Subject: [PATCH 07/22] Initial power-policy integration tests --- embedded-service/src/lib.rs | 1 + embedded-service/src/power/policy/device.rs | 21 ++-- embedded-service/src/power/policy/policy.rs | 26 ++-- power-policy-service/Cargo.toml | 8 ++ power-policy-service/src/consumer.rs | 14 ++- power-policy-service/src/lib.rs | 107 ++++++++++++----- power-policy-service/src/provider.rs | 4 +- power-policy-service/tests/common/mock.rs | 82 +++++++++++++ power-policy-service/tests/common/mod.rs | 124 ++++++++++++++++++++ power-policy-service/tests/consumer.rs | 90 ++++++++++++++ 10 files changed, 412 insertions(+), 65 deletions(-) create mode 100644 power-policy-service/tests/common/mock.rs create mode 100644 power-policy-service/tests/common/mod.rs create mode 100644 power-policy-service/tests/consumer.rs diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 7facbbe0d..c38659a8a 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -17,6 +17,7 @@ pub mod buffer; pub mod cfu; pub mod comms; pub mod ec_type; +pub mod event; pub mod fmt; pub mod hid; pub mod init; diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 42f221cb9..44a0481ca 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -2,7 +2,8 @@ use embassy_sync::mutex::Mutex; use super::{DeviceId, Error}; -use crate::power::policy::policy::EventReceiver; +use crate::event::Receiver; +use crate::power::policy::policy::RequestData; use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; use crate::{GlobalRawMutex, intrusive_list}; @@ -117,10 +118,12 @@ pub trait DeviceTrait { fn connect_provider(&mut self, capability: ProviderPowerCapability) -> impl Future>; /// Connect this device to consume power from an external connection fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> impl Future>; + /// Device is out of sync with the policy, reset and renotify power policy + fn reset(&mut self) -> impl Future>; } /// Device struct -pub struct Device<'a, C: Lockable, R: EventReceiver> +pub struct Device<'a, C: Lockable, R: Receiver> where C::Inner: DeviceTrait, { @@ -133,15 +136,15 @@ where /// Reference to hardware pub device: &'a C, /// Event receiver - pub receiver: &'a R, + pub receiver: Mutex, } -impl<'a, C: Lockable, R: EventReceiver> Device<'a, C, R> +impl<'a, C: Lockable, R: Receiver> Device<'a, C, R> where C::Inner: DeviceTrait, { /// Create a new device - pub fn new(id: DeviceId, device: &'a C, receiver: &'a R) -> Self { + pub fn new(id: DeviceId, device: &'a C, receiver: R) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -151,7 +154,7 @@ where requested_provider_capability: None, }), device, - receiver, + receiver: Mutex::new(receiver), } } @@ -194,7 +197,7 @@ where } } -impl intrusive_list::NodeContainer for Device<'static, C, R> +impl + 'static> intrusive_list::NodeContainer for Device<'static, C, R> where C::Inner: DeviceTrait, { @@ -204,7 +207,7 @@ where } /// Trait for any container that holds a device -pub trait DeviceContainer +pub trait DeviceContainer> where C::Inner: DeviceTrait, { @@ -212,7 +215,7 @@ where fn get_power_policy_device(&self) -> &Device<'_, C, R>; } -impl DeviceContainer for Device<'_, C, R> +impl> DeviceContainer for Device<'_, C, R> where C::Inner: DeviceTrait, { diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index bb9bfd5eb..a61ebf080 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -4,6 +4,7 @@ use core::pin::pin; use core::sync::atomic::{AtomicBool, Ordering}; use crate::broadcaster::immediate as broadcaster; +use crate::event::{self, Receiver}; use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; @@ -70,12 +71,7 @@ pub struct Response { } /// Trait used by devices to send events to a power policy implementation -pub trait EventSender { - /// Try to send an event - fn try_send(&mut self, event: RequestData) -> Option<()>; - /// Send an event - fn send(&mut self, event: RequestData) -> impl Future; - +pub trait Sender: event::Sender { /// Wrapper to simplify sending this event fn on_attach(&mut self) -> impl Future { self.send(RequestData::Attached) @@ -122,13 +118,7 @@ pub trait EventSender { } } -/// Receiver trait used by a policy implementation -pub trait EventReceiver { - /// Attempt to get a pending event - fn try_next(&self) -> Option; - /// Wait for the next event - fn wait_next(&self) -> impl Future; -} +impl Sender for T where T: event::Sender {} /// Power policy context struct Context { @@ -156,7 +146,7 @@ static CONTEXT: Context = Context::new(); pub fn init() {} /// Register a device with the power policy service -pub async fn register_device( +pub async fn register_device + 'static>( device: &'static impl device::DeviceContainer, ) -> Result<(), intrusive_list::Error> where @@ -181,7 +171,7 @@ pub fn register_charger(device: &'static impl charger::ChargerContainer) -> Resu } /// Find a device by its ID -async fn get_device( +async fn get_device + 'static>( id: DeviceId, ) -> Option<&'static device::Device<'static, C, R>> where @@ -260,14 +250,14 @@ pub fn register_message_receiver( } /// Singleton struct to give access to the power policy context -pub struct ContextToken +pub struct ContextToken> where D::Inner: DeviceTrait, { _phantom: PhantomData<(D, R)>, } -impl ContextToken +impl + 'static> ContextToken where D::Inner: DeviceTrait, { @@ -322,7 +312,7 @@ where let mut futures = heapless::Vec::<_, 16>::new(); for device in self.devices().await.iter_only::>() { // TODO: check this at compile time - let _ = futures.push(async { device.receiver.wait_next().await }); + let _ = futures.push(async { device.receiver.lock().await.wait_next().await }); } let (event, index) = select_slice(pin!(&mut futures)).await; diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 8e661bb0d..986ab18b7 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -19,6 +19,14 @@ embedded-services.workspace = true log = { workspace = true, optional = true } heapless.workspace = true +[dev-dependencies] +static_cell.workspace = true +critical-section = { workspace = true, features = ["std"] } +embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } +tokio = { workspace = true, features = ["rt", "macros", "time"] } +env_logger = "0.9.0" +log = { workspace = true } + [features] default = [] defmt = [ diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index c9e81c68b..b5057bfb3 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -31,7 +31,7 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl PowerPolicy +impl + 'static> PowerPolicy where D::Inner: DeviceTrait, { @@ -201,10 +201,13 @@ where state.current_consumer_state = None; let consumer_device = self.context.get_device(current_consumer.device_id).await?; - // Disconnect the current consumer if needed - info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); - // disconnect current consumer and set idle - consumer_device.device.lock().await.disconnect().await?; + if matches!(consumer_device.state().await, State::ConnectedConsumer(_)) { + // Disconnect the current consumer if needed + info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); + // disconnect current consumer and set idle + consumer_device.device.lock().await.disconnect().await?; + consumer_device.state.lock().await.state = State::Idle; + } // If no chargers are registered, they won't receive the new power capability. // Also, if chargers return UnpoweredAck, that means the charger isn't powered. @@ -231,6 +234,7 @@ where .await .connect_consumer(new_consumer.consumer_power_capability) .await?; + device.state.lock().await.state = State::ConnectedConsumer(new_consumer.consumer_power_capability); self.post_consumer_connected(state, new_consumer).await?; Ok(()) } else { diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index e6d2f0060..e734eddbf 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -2,8 +2,9 @@ use core::ops::DerefMut; use embassy_sync::mutex::Mutex; use embedded_services::GlobalRawMutex; +use embedded_services::event::Receiver; use embedded_services::power::policy::device::{Device, DeviceTrait, State}; -use embedded_services::power::policy::policy::EventReceiver; +use embedded_services::power::policy::policy::RequestData; use embedded_services::power::policy::{policy, *}; use embedded_services::sync::Lockable; use embedded_services::{comms, error, info}; @@ -31,7 +32,7 @@ struct InternalState { } /// Power policy state -pub struct PowerPolicy +pub struct PowerPolicy> where D::Inner: DeviceTrait, { @@ -45,7 +46,7 @@ where config: config::Config, } -impl PowerPolicy +impl + 'static> PowerPolicy where D::Inner: DeviceTrait, { @@ -59,52 +60,97 @@ where }) } - async fn process_notify_attach(&self) -> Result<(), Error> { - Ok(()) + async fn process_notify_attach(&self, device: &Device<'_, D, R>) -> Result<(), Error> { + let state = device.state.lock().await.state; + if state != State::Detached { + error!("Device{}: Invalid state for attach: {:#?}", device.id().0, state); + device.state.lock().await.state = State::Detached; + device.device.lock().await.reset().await + } else { + device.state.lock().await.state = State::Idle; + Ok(()) + } } - async fn process_notify_detach(&self) -> Result<(), Error> { + async fn process_notify_detach(&self, device: &Device<'_, D, R>) -> Result<(), Error> { + // Detach is valid in any state + { + let state = &mut device.state.lock().await; + state.state = State::Detached; + state.consumer_capability = None; + state.requested_provider_capability = None; + } self.update_current_consumer().await?; Ok(()) } async fn process_notify_consumer_power_capability( &self, - device: DeviceId, + device: &Device<'_, D, R>, capability: Option, ) -> Result<(), Error> { - let device = self.context.get_device(device).await?; - match device.state().await { - State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => { - device.state.lock().await.consumer_capability = capability; - self.update_current_consumer().await - } - State::Detached => device.device.lock().await.disconnect().await, + let state = device.state.lock().await.state; + if matches!( + device.state.lock().await.state, + State::Idle | State::ConnectedConsumer(_) + ) { + device.state.lock().await.consumer_capability = capability; + self.update_current_consumer().await + } else { + error!( + "Device{}: Invalid state for notify consumer capability: {:#?}", + device.id().0, + state, + ); + device.state.lock().await.state = State::Detached; + device.device.lock().await.reset().await } } async fn process_request_provider_power_capabilities( &self, - device: DeviceId, + device: &Device<'_, D, R>, capability: Option, ) -> Result<(), Error> { - let device = self.context.get_device(device).await?; - match device.state().await { - State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => { - device.state.lock().await.requested_provider_capability = capability; - self.connect_provider(device.id()).await - } - State::Detached => device.device.lock().await.disconnect().await, + let state = device.state.lock().await.state; + if matches!(state, State::Idle | State::ConnectedProvider(_)) { + device.state.lock().await.requested_provider_capability = capability; + self.connect_provider(device.id()).await + } else { + error!( + "Device{}: Invalid state for request provider capability: {:#?}", + device.id().0, + state, + ); + device.state.lock().await.state = State::Detached; + device.device.lock().await.reset().await } } - async fn process_notify_disconnect(&self) -> Result<(), Error> { - if let Some(consumer) = self.state.lock().await.current_consumer_state.take() { - info!("Device{}: Connected consumer disconnected", consumer.device_id.0); + async fn process_notify_disconnect(&self, device: &Device<'_, D, R>) -> Result<(), Error> { + let state = device.state.lock().await.state; + if matches!(state, State::ConnectedConsumer(_) | State::ConnectedProvider(_)) { + device.state.lock().await.state = State::Idle; + } else { + error!("Device{}: Invalid state for disconnect: {:#?}", device.id().0, state); + device.state.lock().await.state = State::Detached; + if let Err(e) = device.device.lock().await.reset().await { + error!("Device{}: Failed to reset device: {:#?}", device.id().0, e); + } + } + + if self + .state + .lock() + .await + .current_consumer_state + .is_some_and(|current| current.device_id == device.id()) + { + info!("Device{}: Connected consumer disconnected", device.id().0); self.disconnect_chargers().await?; self.comms_notify(CommsMessage { - data: CommsData::ConsumerDisconnected(consumer.device_id), + data: CommsData::ConsumerDisconnected(device.id()), }) .await; } @@ -148,7 +194,7 @@ where match request.data { policy::RequestData::Attached => { info!("Received notify attached from device {}", device.id().0); - self.process_notify_attach().await + self.process_notify_attach(device).await } policy::RequestData::Detached => { info!("Received notify detached from device {}", device.id().0); @@ -160,8 +206,7 @@ where device.id().0, capability, ); - self.process_notify_consumer_power_capability(device.id(), capability) - .await + self.process_notify_consumer_power_capability(device, capability).await } policy::RequestData::RequestedProviderCapability(capability) => { info!( @@ -169,7 +214,7 @@ where device.id().0, capability, ); - self.process_request_provider_power_capabilities(device.id(), capability) + self.process_request_provider_power_capabilities(device, capability) .await } policy::RequestData::Disconnected => { @@ -186,7 +231,7 @@ where } } -impl comms::MailboxDelegate for PowerPolicy where +impl + 'static> comms::MailboxDelegate for PowerPolicy where D::Inner: DeviceTrait { } diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index f09057a57..d581caa4a 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -3,7 +3,7 @@ //! the system is in unlimited power state. In this mode up to [provider_unlimited](super::Config::provider_unlimited) //! is provided to each device. Above this threshold, the system is in limited power state. //! In this mode [provider_limited](super::Config::provider_limited) is provided to each device -use embedded_services::{debug, trace}; +use embedded_services::{debug, event::Receiver, power::policy::policy::RequestData, trace}; use super::*; @@ -25,7 +25,7 @@ pub(super) struct State { state: PowerState, } -impl PowerPolicy +impl + 'static> PowerPolicy where D::Inner: DeviceTrait, { diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs new file mode 100644 index 000000000..1956e54df --- /dev/null +++ b/power-policy-service/tests/common/mock.rs @@ -0,0 +1,82 @@ +use embedded_services::info; +use embedded_services::power::policy::device::DeviceTrait; +use embedded_services::power::policy::flags::Consumer; +use embedded_services::power::policy::policy::Sender; +use embedded_services::power::policy::{ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability}; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +pub enum FnCall { + ConnectConsumer(ConsumerPowerCapability), + ConnectProvider(ProviderPowerCapability), + Disconnect, + Reset, +} + +pub struct Mock { + sender: S, + // Number of function calls made to the mock. + pub num_fn_calls: usize, + // Last function call made. + pub last_fn_call: Option, +} + +impl Mock { + pub fn new(sender: S) -> Self { + Self { + sender, + num_fn_calls: 0, + last_fn_call: None, + } + } + + pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { + self.sender.on_attach().await; + self.sender + .on_update_consumer_capability(Some(ConsumerPowerCapability { + capability, + flags: Consumer::none(), + })) + .await; + } + + #[allow(dead_code)] + pub async fn simulate_detach(&mut self) { + self.sender.on_detach().await; + } + + pub fn reset_mock(&mut self) { + self.num_fn_calls = 0; + self.last_fn_call = None; + } +} + +impl DeviceTrait for Mock { + async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { + info!("Connect consumer {:#?}", capability); + self.num_fn_calls += 1; + self.last_fn_call = Some(FnCall::ConnectConsumer(capability)); + Ok(()) + } + + async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + info!("Connect provider: {:#?}", capability); + self.num_fn_calls += 1; + self.last_fn_call = Some(FnCall::ConnectProvider(capability)); + Ok(()) + } + + async fn disconnect(&mut self) -> Result<(), Error> { + info!("Disconnect"); + self.num_fn_calls += 1; + self.last_fn_call = Some(FnCall::Disconnect); + Ok(()) + } + + async fn reset(&mut self) -> Result<(), Error> { + info!("Reset"); + self.num_fn_calls += 1; + self.last_fn_call = Some(FnCall::Reset); + Ok(()) + } +} diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs new file mode 100644 index 000000000..205fa3b29 --- /dev/null +++ b/power-policy-service/tests/common/mod.rs @@ -0,0 +1,124 @@ +use embassy_futures::{ + join::join, + select::{Either, select}, +}; +use embassy_sync::{ + channel::{Channel, DynamicReceiver, DynamicSender}, + mutex::Mutex, + signal::Signal, +}; +use embassy_time::{Duration, with_timeout}; +use embedded_services::{ + GlobalRawMutex, + power::policy::{self, DeviceId, PowerCapability, device, policy::RequestData}, +}; +use power_policy_service::PowerPolicy; + +pub mod mock; + +use mock::Mock; +use static_cell::StaticCell; + +pub const LOW_POWER: PowerCapability = PowerCapability { + voltage_mv: 5000, + current_ma: 1500, +}; + +#[allow(dead_code)] +pub const HIGH_POWER: PowerCapability = PowerCapability { + voltage_mv: 5000, + current_ma: 3000, +}; + +pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); + +const EVENT_CHANNEL_SIZE: usize = 4; + +async fn power_policy_task( + completion_signal: &'static Signal, + power_policy: &'static PowerPolicy< + Mutex>>, + DynamicReceiver<'static, RequestData>, + >, +) { + loop { + match select(power_policy.process(), completion_signal.wait()).await { + Either::First(result) => result.unwrap(), + Either::Second(_) => { + break; + } + } + } +} + +pub async fn run_test>( + timeout: Duration, + test: impl FnOnce( + &'static Mutex>>, + &'static Mutex>>, + ) -> F, +) { + env_logger::builder().filter_level(log::LevelFilter::Trace).init(); + embedded_services::init().await; + + static DEVICE0_EVENT_CHANNEL: StaticCell> = + StaticCell::new(); + let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); + let device0_sender = device0_event_channel.dyn_sender(); + let device0_receiver = device0_event_channel.dyn_receiver(); + + static DEVICE0: StaticCell>>> = StaticCell::new(); + let device0 = DEVICE0.init(Mutex::new(Mock::new(device0_sender))); + + static DEVICE0_REGISTRATION: StaticCell< + device::Device< + 'static, + Mutex>>, + DynamicReceiver<'static, RequestData>, + >, + > = StaticCell::new(); + let device0_registration = DEVICE0_REGISTRATION.init(device::Device::new(DeviceId(0), device0, device0_receiver)); + + policy::register_device(device0_registration).await.unwrap(); + + static DEVICE1_EVENT_CHANNEL: StaticCell> = + StaticCell::new(); + let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); + let device1_sender = device1_event_channel.dyn_sender(); + let device1_receiver = device1_event_channel.dyn_receiver(); + + static DEVICE1: StaticCell>>> = StaticCell::new(); + let device1 = DEVICE1.init(Mutex::new(Mock::new(device1_sender))); + + static DEVICE1_REGISTRATION: StaticCell< + device::Device< + 'static, + Mutex>>, + DynamicReceiver<'static, RequestData>, + >, + > = StaticCell::new(); + let device1_registration = DEVICE1_REGISTRATION.init(device::Device::new(DeviceId(1), device1, device1_receiver)); + + policy::register_device(device1_registration).await.unwrap(); + + static POWER_POLICY: StaticCell< + PowerPolicy< + Mutex>>, + DynamicReceiver<'static, RequestData>, + >, + > = StaticCell::new(); + let power_policy = POWER_POLICY.init(power_policy_service::PowerPolicy::create(Default::default()).unwrap()); + + static COMPLETION_SIGNAL: StaticCell> = StaticCell::new(); + let completion_signal = COMPLETION_SIGNAL.init(Signal::new()); + + with_timeout( + timeout, + join(power_policy_task(completion_signal, power_policy), async { + test(device0, device1).await; + completion_signal.signal(()); + }), + ) + .await + .unwrap(); +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs new file mode 100644 index 000000000..d075c432c --- /dev/null +++ b/power-policy-service/tests/consumer.rs @@ -0,0 +1,90 @@ +use embassy_time::Timer; +use embedded_services::power::policy::{ConsumerPowerCapability, flags::Consumer}; + +mod common; + +use common::LOW_POWER; + +use crate::common::{DEFAULT_TIMEOUT, HIGH_POWER, run_test}; + +/// Test the basic consumer flow with a single device. +/*#[tokio::test] +async fn test_single() { + run_test(DEFAULT_TIMEOUT, |device0, _| async { + // Test initial connection + { + device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + Timer::after_millis(500).await; + + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!( + dev0.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + })) + ); + dev0.reset_mock(); + } + // Test detach + { + device0.lock().await.simulate_detach().await; + Timer::after_millis(500).await; + + let mut dev0 = device0.lock().await; + // Power policy shouldn't do any function calls + assert_eq!(dev0.num_fn_calls, 0); + assert_eq!(dev0.last_fn_call, None,); + dev0.reset_mock(); + } + }) + .await; +}*/ + +/// Test swapping to a higher powered device. +#[tokio::test] +async fn test_swap_higher() { + run_test(DEFAULT_TIMEOUT, |device0, device1| async { + // Device0 connection at low power + { + device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + Timer::after_millis(500).await; + + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!( + dev0.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + })) + ); + dev0.reset_mock(); + } + // Device1 connection at high power + { + device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; + Timer::after_millis(500).await; + + // Check that device0 was disconnected + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!(dev0.last_fn_call, Some(common::mock::FnCall::Disconnect)); + dev0.reset_mock(); + + // Check that device1 was connected + let mut dev1 = device1.lock().await; + assert_eq!(dev1.num_fn_calls, 1); + assert_eq!( + dev1.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: Consumer::none(), + })) + ); + dev1.reset_mock(); + } + }) + .await; +} From dedc4b83eefc8fec49ec2d10a8c13f3dcd74493b Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Tue, 4 Nov 2025 16:03:50 -0800 Subject: [PATCH 08/22] Combine power policy integration tests for now --- embedded-service/src/event.rs | 43 ++++++ power-policy-service/tests/consumer.rs | 179 +++++++++++++++---------- 2 files changed, 150 insertions(+), 72 deletions(-) create mode 100644 embedded-service/src/event.rs diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs new file mode 100644 index 000000000..20675ae68 --- /dev/null +++ b/embedded-service/src/event.rs @@ -0,0 +1,43 @@ +//! Common traits for event senders and receivers + +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; + +/// Common event sender trait +pub trait Sender { + /// Attempt to send an event + /// + /// Return none if the event cannot currently be sent + fn try_send(&mut self, event: E) -> Option<()>; + /// Send an event + fn send(&mut self, event: E) -> impl Future; +} + +/// Common event receiver trait +pub trait Receiver { + /// Attempt to receive an event + /// + /// Return none if there are no pending events + fn try_next(&mut self) -> Option; + /// Receiver an event + fn wait_next(&mut self) -> impl Future; +} + +impl Sender for DynamicSender<'_, E> { + fn try_send(&mut self, event: E) -> Option<()> { + DynamicSender::try_send(self, event).ok() + } + + fn send(&mut self, event: E) -> impl Future { + DynamicSender::send(self, event) + } +} + +impl Receiver for DynamicReceiver<'_, E> { + fn try_next(&mut self) -> Option { + self.try_receive().ok() + } + + fn wait_next(&mut self) -> impl Future { + self.receive() + } +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index d075c432c..0c0ed8283 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,90 +1,125 @@ +use embassy_sync::{channel::DynamicSender, mutex::Mutex}; use embassy_time::Timer; -use embedded_services::power::policy::{ConsumerPowerCapability, flags::Consumer}; +use embedded_services::{ + GlobalRawMutex, + power::policy::{ConsumerPowerCapability, flags::Consumer, policy::RequestData}, +}; mod common; use common::LOW_POWER; -use crate::common::{DEFAULT_TIMEOUT, HIGH_POWER, run_test}; +use crate::common::{DEFAULT_TIMEOUT, HIGH_POWER, mock::Mock, run_test}; /// Test the basic consumer flow with a single device. -/*#[tokio::test] -async fn test_single() { - run_test(DEFAULT_TIMEOUT, |device0, _| async { - // Test initial connection - { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; - Timer::after_millis(500).await; +async fn test_single(device0: &'static Mutex>>) { + // Test initial connection + { + device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + Timer::after_millis(1000).await; - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); - assert_eq!( - dev0.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: LOW_POWER, - flags: Consumer::none(), - })) - ); - dev0.reset_mock(); - } - // Test detach - { - device0.lock().await.simulate_detach().await; - Timer::after_millis(500).await; + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!( + dev0.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + })) + ); + dev0.reset_mock(); + } + // Test detach + { + device0.lock().await.simulate_detach().await; + Timer::after_millis(1000).await; - let mut dev0 = device0.lock().await; - // Power policy shouldn't do any function calls - assert_eq!(dev0.num_fn_calls, 0); - assert_eq!(dev0.last_fn_call, None,); - dev0.reset_mock(); - } - }) - .await; -}*/ + let mut dev0 = device0.lock().await; + // Power policy shouldn't do any function calls + assert_eq!(dev0.num_fn_calls, 0); + assert_eq!(dev0.last_fn_call, None); + dev0.reset_mock(); + } +} /// Test swapping to a higher powered device. -#[tokio::test] -async fn test_swap_higher() { - run_test(DEFAULT_TIMEOUT, |device0, device1| async { - // Device0 connection at low power - { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; - Timer::after_millis(500).await; +async fn test_swap_higher( + device0: &'static Mutex>>, + device1: &'static Mutex>>, +) { + // Device0 connection at low power + { + device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + Timer::after_millis(1000).await; + + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!( + dev0.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + })) + ); + dev0.reset_mock(); + } + // Device1 connection at high power + { + device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; + Timer::after_millis(1000).await; - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); - assert_eq!( - dev0.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: LOW_POWER, - flags: Consumer::none(), - })) - ); - dev0.reset_mock(); - } - // Device1 connection at high power - { - device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; - Timer::after_millis(500).await; + // Check that device0 was disconnected + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!(dev0.last_fn_call, Some(common::mock::FnCall::Disconnect)); + dev0.reset_mock(); - // Check that device0 was disconnected - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); - assert_eq!(dev0.last_fn_call, Some(common::mock::FnCall::Disconnect)); - dev0.reset_mock(); + // Check that device1 was connected + let mut dev1 = device1.lock().await; + assert_eq!(dev1.num_fn_calls, 1); + assert_eq!( + dev1.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: Consumer::none(), + })) + ); + dev1.reset_mock(); + } + // Test detach device1, should reconnect device0 + { + device1.lock().await.simulate_detach().await; + Timer::after_millis(1000).await; + + let mut dev1 = device1.lock().await; + // Power policy shouldn't do any function calls + assert_eq!(dev1.num_fn_calls, 0); + assert_eq!(dev1.last_fn_call, None); + dev1.reset_mock(); + + // Check that device0 was reconnected + let mut dev0 = device0.lock().await; + assert_eq!(dev0.num_fn_calls, 1); + assert_eq!( + dev0.last_fn_call, + Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + })) + ); + dev0.reset_mock(); + } +} + +/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. +#[tokio::test] +async fn run_all_tests() { + run_test(DEFAULT_TIMEOUT, |device0, device1| async move { + test_single(device0).await; - // Check that device1 was connected - let mut dev1 = device1.lock().await; - assert_eq!(dev1.num_fn_calls, 1); - assert_eq!( - dev1.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: HIGH_POWER, - flags: Consumer::none(), - })) - ); - dev1.reset_mock(); - } + device0.lock().await.reset_mock(); + device1.lock().await.reset_mock(); + test_swap_higher(device0, device1).await; }) .await; } From a10cc639e29ef9c8d73ce10dc78394da8f8cd143 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 5 Nov 2025 15:31:30 -0800 Subject: [PATCH 09/22] WIP: Migrate type-C service over --- Cargo.lock | 83 ++++++++++ embedded-service/src/power/policy/device.rs | 175 ++++++++++++++++++-- embedded-service/src/power/policy/mod.rs | 2 +- embedded-service/src/power/policy/policy.rs | 52 +----- power-policy-service/src/consumer.rs | 28 +++- power-policy-service/src/lib.rs | 77 ++++----- power-policy-service/src/provider.rs | 14 +- power-policy-service/tests/common/mock.rs | 75 +++++---- power-policy-service/tests/common/mod.rs | 16 +- power-policy-service/tests/consumer.rs | 143 ++++++++-------- type-c-service/src/wrapper/backing.rs | 98 ++++++++--- type-c-service/src/wrapper/cfu.rs | 6 +- type-c-service/src/wrapper/dp.rs | 5 +- type-c-service/src/wrapper/mod.rs | 41 +++-- type-c-service/src/wrapper/pd.rs | 31 +--- type-c-service/src/wrapper/power.rs | 11 +- type-c-service/src/wrapper/vdm.rs | 5 +- 17 files changed, 549 insertions(+), 313 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee0606d4b..6aeaef8c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,17 @@ dependencies = [ "winnow 0.7.13", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -914,6 +925,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1069,6 +1093,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hid-service" version = "0.1.0" @@ -1082,6 +1115,12 @@ dependencies = [ "log", ] +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "include_dir" version = "0.7.4" @@ -1619,13 +1658,17 @@ dependencies = [ name = "power-policy-service" version = "0.1.0" dependencies = [ + "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", "embedded-services", + "env_logger", "heapless", "log", + "static_cell", + "tokio", ] [[package]] @@ -1988,6 +2031,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thermal-service" version = "0.1.0" @@ -2350,6 +2402,37 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.61.3" diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 44a0481ca..545d868c0 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -48,16 +48,164 @@ impl State { } } -/// Internal device state for power policy +/// Per-device state for power policy implementation +/// +/// This struct implements the state machine outlined in the docs directory. +/// The various state transition functions always succeed in the sense that +/// the desired state is always entered, but some still return a result. +/// This is because a the device that is driving this state machine is the +/// ultimate source of truth and the recovery procedure would ultimately +/// end up catching up to this state anyway. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct InternalState { /// Current state of the device - pub state: State, + state: State, /// Current consumer capability - pub consumer_capability: Option, + consumer_capability: Option, /// Current requested provider capability - pub requested_provider_capability: Option, + requested_provider_capability: Option, +} + +impl Default for InternalState { + fn default() -> Self { + Self { + state: State::Detached, + consumer_capability: None, + requested_provider_capability: None, + } + } +} + +impl InternalState { + /// Attach the device + pub fn attach(&mut self) -> Result<(), Error> { + let result = if self.state == State::Detached { + Ok(()) + } else { + Err(Error::InvalidState(&[StateKind::Detached], self.state.kind())) + }; + self.state = State::Idle; + result + } + + /// Detach the device + /// + /// Detach is always a valid transition + pub fn detach(&mut self) { + self.state = State::Detached; + self.consumer_capability = None; + self.requested_provider_capability = None; + } + + /// Disconnect this device + pub fn disconnect(&mut self, clear_caps: bool) -> Result<(), Error> { + let result = if matches!(self.state, State::ConnectedConsumer(_) | State::ConnectedProvider(_)) { + Ok(()) + } else { + Err(Error::InvalidState( + &[StateKind::ConnectedConsumer, StateKind::ConnectedProvider], + self.state.kind(), + )) + }; + self.state = State::Idle; + if clear_caps { + self.consumer_capability = None; + self.requested_provider_capability = None; + } + result + } + + /// Update the available consumer capability + pub fn update_consumer_power_capability( + &mut self, + capability: Option, + ) -> Result<(), Error> { + let result = if matches!( + self.state, + State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) + ) { + Ok(()) + } else { + Err(Error::InvalidState( + &[ + StateKind::Idle, + StateKind::ConnectedConsumer, + StateKind::ConnectedProvider, + ], + self.state.kind(), + )) + }; + self.consumer_capability = capability; + result + } + + /// Updated the requested provider capability + pub fn update_requested_provider_power_capability( + &mut self, + capability: Option, + ) -> Result<(), Error> { + if self.requested_provider_capability == capability { + // Already operating at this capability, power policy is already aware, don't need to do anything + return Ok(()); + } + + let result = if matches!( + self.state, + State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) + ) { + Ok(()) + } else { + Err(Error::InvalidState( + &[ + StateKind::Idle, + StateKind::ConnectedConsumer, + StateKind::ConnectedProvider, + ], + self.state.kind(), + )) + }; + + self.requested_provider_capability = capability; + result + } + + /// Handle a request to connect as a consumer from the policy + pub fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { + let result = if self.state == State::Idle { + Ok(()) + } else { + Err(Error::InvalidState(&[StateKind::Idle], self.state.kind())) + }; + self.state = State::ConnectedConsumer(capability); + result + } + + /// Handle a request to connect as a provider from the policy + pub fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + let result = if self.state == State::Idle { + Ok(()) + } else { + Err(Error::InvalidState(&[StateKind::Idle], self.state.kind())) + }; + self.state = State::ConnectedProvider(capability); + result + } + + /// Returns the current state machine state + pub fn state(&self) -> State { + self.state + } + + /// Returns the current consumer capability + pub fn consumer_capability(&self) -> Option { + self.consumer_capability + } + + /// Returns the requested provider capability + pub fn requested_provider_capability(&self) -> Option { + self.requested_provider_capability + } } /// Data for a device request @@ -118,14 +266,12 @@ pub trait DeviceTrait { fn connect_provider(&mut self, capability: ProviderPowerCapability) -> impl Future>; /// Connect this device to consume power from an external connection fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> impl Future>; - /// Device is out of sync with the policy, reset and renotify power policy - fn reset(&mut self) -> impl Future>; } /// Device struct -pub struct Device<'a, C: Lockable, R: Receiver> +pub struct Device<'a, D: Lockable, R: Receiver> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { /// Intrusive list node node: intrusive_list::Node, @@ -134,7 +280,7 @@ where /// Current state of the device pub state: Mutex, /// Reference to hardware - pub device: &'a C, + pub device: &'a D, /// Event receiver pub receiver: Mutex, } @@ -163,11 +309,6 @@ where self.id } - /// Returns the current state of the device - pub async fn state(&self) -> State { - self.state.lock().await.state - } - /// Returns the current consumer capability of the device pub async fn consumer_capability(&self) -> Option { self.state.lock().await.consumer_capability @@ -175,12 +316,12 @@ where /// Returns true if the device is currently consuming power pub async fn is_consumer(&self) -> bool { - self.state().await.kind() == StateKind::ConnectedConsumer + self.state.lock().await.state.kind() == StateKind::ConnectedConsumer } /// Returns current provider power capability pub async fn provider_capability(&self) -> Option { - match self.state().await { + match self.state.lock().await.state { State::ConnectedProvider(capability) => Some(capability), _ => None, } @@ -193,7 +334,7 @@ where /// Returns true if the device is currently providing power pub async fn is_provider(&self) -> bool { - self.state().await.kind() == StateKind::ConnectedProvider + self.state.lock().await.state.kind() == StateKind::ConnectedProvider } } diff --git a/embedded-service/src/power/policy/mod.rs b/embedded-service/src/power/policy/mod.rs index 2ab5bbeb1..d33efc63f 100644 --- a/embedded-service/src/power/policy/mod.rs +++ b/embedded-service/src/power/policy/mod.rs @@ -19,7 +19,7 @@ pub enum Error { /// The consume request was denied, contains maximum available power CannotConsume(Option), /// The device is not in the correct state (expected, actual) - InvalidState(device::StateKind, device::StateKind), + InvalidState(&'static [device::StateKind], device::StateKind), /// Invalid response InvalidResponse, /// Busy, the device cannot respond to the request at this time diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index a61ebf080..9dd33f044 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -4,7 +4,7 @@ use core::pin::pin; use core::sync::atomic::{AtomicBool, Ordering}; use crate::broadcaster::immediate as broadcaster; -use crate::event::{self, Receiver}; +use crate::event::Receiver; use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; @@ -70,56 +70,6 @@ pub struct Response { pub data: ResponseData, } -/// Trait used by devices to send events to a power policy implementation -pub trait Sender: event::Sender { - /// Wrapper to simplify sending this event - fn on_attach(&mut self) -> impl Future { - self.send(RequestData::Attached) - } - - /// Wrapper to simplify attempting to send this event - fn try_on_update_consumer_capability(&mut self, cap: Option) -> Option<()> { - self.try_send(RequestData::UpdatedConsumerCapability(cap)) - } - - /// Wrapper to simplify sending this event - fn on_update_consumer_capability(&mut self, cap: Option) -> impl Future { - self.send(RequestData::UpdatedConsumerCapability(cap)) - } - - /// Wrapper to simplify attempting to send this event - fn try_on_request_provider_capability(&mut self, cap: Option) -> Option<()> { - self.try_send(RequestData::RequestedProviderCapability(cap)) - } - - /// Wrapper to simplify sending this event - fn on_request_provider_capability(&mut self, cap: Option) -> impl Future { - self.send(RequestData::RequestedProviderCapability(cap)) - } - - /// Wrapper to simplify attempting to send this event - fn try_on_disconnect(&mut self) -> Option<()> { - self.try_send(RequestData::Disconnected) - } - - /// Wrapper to simplify sending this event - fn on_disconnect(&mut self) -> impl Future { - self.send(RequestData::Disconnected) - } - - /// Wrapper to simplify attempting to send this event - fn try_on_detach(&mut self) -> Option<()> { - self.try_send(RequestData::Detached) - } - - /// Wrapper to simplify sending this event - fn on_detach(&mut self) -> impl Future { - self.send(RequestData::Detached) - } -} - -impl Sender for T where T: event::Sender {} - /// Power policy context struct Context { /// Registered devices diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index b5057bfb3..c886e2965 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -2,6 +2,7 @@ use core::cmp::Ordering; use embedded_services::debug; use embedded_services::power::policy::charger::Device as ChargerDevice; use embedded_services::power::policy::charger::PolicyEvent; +use embedded_services::power::policy::device::StateKind; use embedded_services::power::policy::policy::check_chargers_ready; use embedded_services::power::policy::policy::init_chargers; @@ -201,12 +202,18 @@ where state.current_consumer_state = None; let consumer_device = self.context.get_device(current_consumer.device_id).await?; - if matches!(consumer_device.state().await, State::ConnectedConsumer(_)) { + if matches!(consumer_device.state.lock().await.state(), State::ConnectedConsumer(_)) { // Disconnect the current consumer if needed info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); // disconnect current consumer and set idle consumer_device.device.lock().await.disconnect().await?; - consumer_device.state.lock().await.state = State::Idle; + if let Err(e) = consumer_device.state.lock().await.disconnect(false) { + // This should never happen because we check the state above, log an error instead of a panic + error!( + "Device{}: Disconnect transition failed: {:#?}", + current_consumer.device_id.0, e + ); + } } // If no chargers are registered, they won't receive the new power capability. @@ -225,7 +232,7 @@ where info!("Device {}, connecting new consumer", new_consumer.device_id.0); let device = self.context.get_device(new_consumer.device_id).await?; - let device_state = device.state().await; + let device_state = device.state.lock().await.state(); if matches!(device_state, device::State::Idle | device::State::ConnectedConsumer(_)) { device @@ -234,7 +241,18 @@ where .await .connect_consumer(new_consumer.consumer_power_capability) .await?; - device.state.lock().await.state = State::ConnectedConsumer(new_consumer.consumer_power_capability); + if let Err(e) = device + .state + .lock() + .await + .connect_consumer(new_consumer.consumer_power_capability) + { + // Should never happen because we checked the state above, log an error instead of a panic + error!( + "Device{}: Connect state transition failed: {:#?}", + new_consumer.device_id.0, e + ); + } self.post_consumer_connected(state, new_consumer).await?; Ok(()) } else { @@ -243,7 +261,7 @@ where device.id().0, device_state ); - Err(Error::InvalidState(device::StateKind::Idle, device_state.kind())) + Err(Error::InvalidState(&[StateKind::Idle], device_state.kind())) } } diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index e734eddbf..1f49cd899 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -60,28 +60,15 @@ where }) } - async fn process_notify_attach(&self, device: &Device<'_, D, R>) -> Result<(), Error> { - let state = device.state.lock().await.state; - if state != State::Detached { - error!("Device{}: Invalid state for attach: {:#?}", device.id().0, state); - device.state.lock().await.state = State::Detached; - device.device.lock().await.reset().await - } else { - device.state.lock().await.state = State::Idle; - Ok(()) + async fn process_notify_attach(&self, device: &Device<'_, D, R>) { + if let Err(e) = device.state.lock().await.attach() { + error!("Device{}: Invalid state for attach: {:#?}", device.id().0, e); } } async fn process_notify_detach(&self, device: &Device<'_, D, R>) -> Result<(), Error> { - // Detach is valid in any state - { - let state = &mut device.state.lock().await; - state.state = State::Detached; - state.consumer_capability = None; - state.requested_provider_capability = None; - } - self.update_current_consumer().await?; - Ok(()) + device.state.lock().await.detach(); + self.update_current_consumer().await } async fn process_notify_consumer_power_capability( @@ -89,22 +76,15 @@ where device: &Device<'_, D, R>, capability: Option, ) -> Result<(), Error> { - let state = device.state.lock().await.state; - if matches!( - device.state.lock().await.state, - State::Idle | State::ConnectedConsumer(_) - ) { - device.state.lock().await.consumer_capability = capability; - self.update_current_consumer().await - } else { + if let Err(e) = device.state.lock().await.update_consumer_power_capability(capability) { error!( - "Device{}: Invalid state for notify consumer capability: {:#?}", + "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", device.id().0, - state, + e, ); - device.state.lock().await.state = State::Detached; - device.device.lock().await.reset().await } + + self.update_current_consumer().await } async fn process_request_provider_power_capabilities( @@ -112,31 +92,29 @@ where device: &Device<'_, D, R>, capability: Option, ) -> Result<(), Error> { - let state = device.state.lock().await.state; - if matches!(state, State::Idle | State::ConnectedProvider(_)) { - device.state.lock().await.requested_provider_capability = capability; - self.connect_provider(device.id()).await - } else { + if let Err(e) = device + .state + .lock() + .await + .update_requested_provider_power_capability(capability) + { error!( - "Device{}: Invalid state for request provider capability: {:#?}", + "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", device.id().0, - state, + e, ); - device.state.lock().await.state = State::Detached; - device.device.lock().await.reset().await } + + self.connect_provider(device.id()).await } async fn process_notify_disconnect(&self, device: &Device<'_, D, R>) -> Result<(), Error> { - let state = device.state.lock().await.state; - if matches!(state, State::ConnectedConsumer(_) | State::ConnectedProvider(_)) { - device.state.lock().await.state = State::Idle; - } else { - error!("Device{}: Invalid state for disconnect: {:#?}", device.id().0, state); - device.state.lock().await.state = State::Detached; - if let Err(e) = device.device.lock().await.reset().await { - error!("Device{}: Failed to reset device: {:#?}", device.id().0, e); - } + if let Err(e) = device.state.lock().await.disconnect(true) { + error!( + "Device{}: Invalid state for notify disconnect, catching up: {:#?}", + device.id().0, + e, + ); } if self @@ -194,7 +172,8 @@ where match request.data { policy::RequestData::Attached => { info!("Received notify attached from device {}", device.id().0); - self.process_notify_attach(device).await + self.process_notify_attach(device).await; + Ok(()) } policy::RequestData::Detached => { info!("Received notify detached from device {}", device.id().0); diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index d581caa4a..e4bc107f1 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -3,7 +3,12 @@ //! the system is in unlimited power state. In this mode up to [provider_unlimited](super::Config::provider_unlimited) //! is provided to each device. Above this threshold, the system is in limited power state. //! In this mode [provider_limited](super::Config::provider_limited) is provided to each device -use embedded_services::{debug, event::Receiver, power::policy::policy::RequestData, trace}; +use embedded_services::{ + debug, + event::Receiver, + power::policy::{device::StateKind, policy::RequestData}, + trace, +}; use super::*; @@ -85,7 +90,7 @@ where }; let device = self.context.get_device(requester_id).await?; - let state = device.state().await; + let state = device.state.lock().await.state(); if matches!(state, device::State::Idle | device::State::ConnectedProvider(_)) { device.device.lock().await.connect_provider(target_power).await } else { @@ -94,10 +99,7 @@ where device.id().0, state ); - Err(Error::InvalidState( - device::StateKind::Idle, - requester.state().await.kind(), - )) + Err(Error::InvalidState(&[StateKind::Idle], state.kind())) } } diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index 1956e54df..d87226387 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,8 +1,9 @@ -use embedded_services::info; -use embedded_services::power::policy::device::DeviceTrait; +use embassy_sync::signal::Signal; +use embedded_services::power::policy::device::{DeviceTrait, InternalState}; use embedded_services::power::policy::flags::Consumer; -use embedded_services::power::policy::policy::Sender; +use embedded_services::power::policy::policy::RequestData; use embedded_services::power::policy::{ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability}; +use embedded_services::{GlobalRawMutex, event, info}; #[derive(Debug, Clone, PartialEq, Eq)] #[allow(dead_code)] @@ -13,70 +14,68 @@ pub enum FnCall { Reset, } -pub struct Mock { +pub struct Mock<'a, S: event::Sender> { sender: S, - // Number of function calls made to the mock. - pub num_fn_calls: usize, - // Last function call made. - pub last_fn_call: Option, + fn_call: &'a Signal, + // Internal state + pub state: InternalState, } -impl Mock { - pub fn new(sender: S) -> Self { +impl<'a, S: event::Sender> Mock<'a, S> { + pub fn new(sender: S, fn_call: &'a Signal) -> Self { Self { sender, - num_fn_calls: 0, - last_fn_call: None, + fn_call, + state: Default::default(), } } + fn record_fn_call(&mut self, fn_call: FnCall) { + let num_fn_calls = self + .fn_call + .try_take() + .map(|(num_fn_calls, _)| num_fn_calls) + .unwrap_or(1); + self.fn_call.signal((num_fn_calls, fn_call)); + } + pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { - self.sender.on_attach().await; + self.state.attach().unwrap(); + + self.sender.send(RequestData::Attached).await; + + let capability = Some(ConsumerPowerCapability { + capability, + flags: Consumer::none(), + }); + self.state.update_consumer_power_capability(capability).unwrap(); self.sender - .on_update_consumer_capability(Some(ConsumerPowerCapability { - capability, - flags: Consumer::none(), - })) + .send(RequestData::UpdatedConsumerCapability(capability)) .await; } - #[allow(dead_code)] pub async fn simulate_detach(&mut self) { - self.sender.on_detach().await; - } - - pub fn reset_mock(&mut self) { - self.num_fn_calls = 0; - self.last_fn_call = None; + self.state.detach(); + self.sender.send(RequestData::Detached).await; } } -impl DeviceTrait for Mock { +impl<'a, S: event::Sender> DeviceTrait for Mock<'a, S> { async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { info!("Connect consumer {:#?}", capability); - self.num_fn_calls += 1; - self.last_fn_call = Some(FnCall::ConnectConsumer(capability)); + self.record_fn_call(FnCall::ConnectConsumer(capability)); Ok(()) } async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { info!("Connect provider: {:#?}", capability); - self.num_fn_calls += 1; - self.last_fn_call = Some(FnCall::ConnectProvider(capability)); + self.record_fn_call(FnCall::ConnectProvider(capability)); Ok(()) } async fn disconnect(&mut self) -> Result<(), Error> { info!("Disconnect"); - self.num_fn_calls += 1; - self.last_fn_call = Some(FnCall::Disconnect); - Ok(()) - } - - async fn reset(&mut self) -> Result<(), Error> { - info!("Reset"); - self.num_fn_calls += 1; - self.last_fn_call = Some(FnCall::Reset); + self.record_fn_call(FnCall::Disconnect); Ok(()) } } diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 205fa3b29..33885699c 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -19,6 +19,8 @@ pub mod mock; use mock::Mock; use static_cell::StaticCell; +use crate::common::mock::FnCall; + pub const LOW_POWER: PowerCapability = PowerCapability { voltage_mv: 5000, current_ma: 1500, @@ -37,7 +39,7 @@ const EVENT_CHANNEL_SIZE: usize = 4; async fn power_policy_task( completion_signal: &'static Signal, power_policy: &'static PowerPolicy< - Mutex>>, + Mutex>>, DynamicReceiver<'static, RequestData>, >, ) { @@ -55,7 +57,9 @@ pub async fn run_test>( timeout: Duration, test: impl FnOnce( &'static Mutex>>, + &'static Signal, &'static Mutex>>, + &'static Signal, ) -> F, ) { env_logger::builder().filter_level(log::LevelFilter::Trace).init(); @@ -67,8 +71,10 @@ pub async fn run_test>( let device0_sender = device0_event_channel.dyn_sender(); let device0_receiver = device0_event_channel.dyn_receiver(); + static DEVICE0_SIGNAL: StaticCell> = StaticCell::new(); + let device0_signal = DEVICE0_SIGNAL.init(Signal::new()); static DEVICE0: StaticCell>>> = StaticCell::new(); - let device0 = DEVICE0.init(Mutex::new(Mock::new(device0_sender))); + let device0 = DEVICE0.init(Mutex::new(Mock::new(device0_sender, device0_signal))); static DEVICE0_REGISTRATION: StaticCell< device::Device< @@ -87,8 +93,10 @@ pub async fn run_test>( let device1_sender = device1_event_channel.dyn_sender(); let device1_receiver = device1_event_channel.dyn_receiver(); + static DEVICE1_SIGNAL: StaticCell> = StaticCell::new(); + let device1_signal = DEVICE1_SIGNAL.init(Signal::new()); static DEVICE1: StaticCell>>> = StaticCell::new(); - let device1 = DEVICE1.init(Mutex::new(Mock::new(device1_sender))); + let device1 = DEVICE1.init(Mutex::new(Mock::new(device1_sender, device1_signal))); static DEVICE1_REGISTRATION: StaticCell< device::Device< @@ -115,7 +123,7 @@ pub async fn run_test>( with_timeout( timeout, join(power_policy_task(completion_signal, power_policy), async { - test(device0, device1).await; + test(device0, device0_signal, device1, device1_signal).await; completion_signal.signal(()); }), ) diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 0c0ed8283..c201e5278 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,5 +1,5 @@ -use embassy_sync::{channel::DynamicSender, mutex::Mutex}; -use embassy_time::Timer; +use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; +use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::{ GlobalRawMutex, power::policy::{ConsumerPowerCapability, flags::Consumer, policy::RequestData}, @@ -9,117 +9,126 @@ mod common; use common::LOW_POWER; -use crate::common::{DEFAULT_TIMEOUT, HIGH_POWER, mock::Mock, run_test}; +use crate::common::{ + DEFAULT_TIMEOUT, HIGH_POWER, + mock::{FnCall, Mock}, + run_test, +}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic consumer flow with a single device. -async fn test_single(device0: &'static Mutex>>) { +async fn test_single( + device0: &'static Mutex>>, + device0_signal: &'static Signal, +) { // Test initial connection { device0.lock().await.simulate_consumer_connection(LOW_POWER).await; - Timer::after_millis(1000).await; - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); assert_eq!( - dev0.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: LOW_POWER, - flags: Consumer::none(), - })) + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + }) + ) ); - dev0.reset_mock(); + device0_signal.reset(); } // Test detach { device0.lock().await.simulate_detach().await; - Timer::after_millis(1000).await; - let mut dev0 = device0.lock().await; - // Power policy shouldn't do any function calls - assert_eq!(dev0.num_fn_calls, 0); - assert_eq!(dev0.last_fn_call, None); - dev0.reset_mock(); + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); } } /// Test swapping to a higher powered device. async fn test_swap_higher( - device0: &'static Mutex>>, - device1: &'static Mutex>>, + device0: &'static Mutex>>, + device0_signal: &'static Signal, + device1: &'static Mutex>>, + device1_signal: &'static Signal, ) { // Device0 connection at low power { device0.lock().await.simulate_consumer_connection(LOW_POWER).await; - Timer::after_millis(1000).await; - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); assert_eq!( - dev0.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: LOW_POWER, - flags: Consumer::none(), - })) + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + }) + ) ); - dev0.reset_mock(); + device0_signal.reset(); } // Device1 connection at high power { device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; - Timer::after_millis(1000).await; - // Check that device0 was disconnected - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); - assert_eq!(dev0.last_fn_call, Some(common::mock::FnCall::Disconnect)); - dev0.reset_mock(); + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); - // Check that device1 was connected - let mut dev1 = device1.lock().await; - assert_eq!(dev1.num_fn_calls, 1); assert_eq!( - dev1.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: HIGH_POWER, - flags: Consumer::none(), - })) + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: Consumer::none(), + }) + ) ); - dev1.reset_mock(); + device1_signal.reset(); } // Test detach device1, should reconnect device0 { device1.lock().await.simulate_detach().await; - Timer::after_millis(1000).await; - let mut dev1 = device1.lock().await; - // Power policy shouldn't do any function calls - assert_eq!(dev1.num_fn_calls, 0); - assert_eq!(dev1.last_fn_call, None); - dev1.reset_mock(); + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); - // Check that device0 was reconnected - let mut dev0 = device0.lock().await; - assert_eq!(dev0.num_fn_calls, 1); assert_eq!( - dev0.last_fn_call, - Some(common::mock::FnCall::ConnectConsumer(ConsumerPowerCapability { - capability: LOW_POWER, - flags: Consumer::none(), - })) + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + }) + ) ); - dev0.reset_mock(); + device0_signal.reset(); } } /// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. #[tokio::test] async fn run_all_tests() { - run_test(DEFAULT_TIMEOUT, |device0, device1| async move { - test_single(device0).await; - - device0.lock().await.reset_mock(); - device1.lock().await.reset_mock(); - test_swap_higher(device0, device1).await; - }) + run_test( + DEFAULT_TIMEOUT, + |device0, device0_signal, device1, device1_signal| async move { + test_single(device0, device0_signal).await; + test_swap_higher(device0, device0_signal, device1, device1_signal).await; + }, + ) .await; } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 711b5a02e..c1eb05a9a 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -43,12 +43,14 @@ use core::cell::{RefCell, RefMut}; use embassy_sync::{ blocking_mutex::raw::RawMutex, + mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}, }; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::{ - power, + event, + power::{self, policy::policy}, type_c::{ ControllerId, controller::PortStatus, @@ -93,6 +95,28 @@ impl Default for ControllerState { } } +pub struct PortProxy; + +impl power::policy::device::DeviceTrait for PortProxy { + async fn disconnect(&mut self) -> Result<(), power::policy::Error> { + Ok(()) + } + + async fn connect_provider( + &mut self, + _capability: power::policy::ProviderPowerCapability, + ) -> Result<(), power::policy::Error> { + Ok(()) + } + + async fn connect_consumer( + &mut self, + _capability: power::policy::ConsumerPowerCapability, + ) -> Result<(), power::policy::Error> { + Ok(()) + } +} + /// Internal state containing all per-port and per-controller state struct InternalState<'a, const N: usize> { controller_state: ControllerState, @@ -158,14 +182,13 @@ pub trait DynPortState<'a> { } /// Service registration objects -pub struct Registration<'a> { - pub context: &'a embedded_services::type_c::controller::Context, +pub struct Registration<'a, M: RawMutex, R: event::Receiver> { pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, pub cfu_device: &'a embedded_services::cfu::component::CfuDevice, - pub power_devices: &'a [embedded_services::power::policy::device::Device], + pub power_devices: &'a [embedded_services::power::policy::device::Device<'a, Mutex, R>], } -impl<'a> Registration<'a> { +impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, M, R> { pub fn num_ports(&self) -> usize { self.power_devices.len() } @@ -174,38 +197,51 @@ impl<'a> Registration<'a> { /// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. const MAX_BUFFERED_PD_ALERTS: usize = 4; +struct PortPower> { + pub sender: S, + pub state: power::policy::device::InternalState, +} + /// Base storage -pub struct Storage<'a, const N: usize, M: RawMutex> { +pub struct Storage<'a, const N: usize, M: RawMutex, S: event::Sender> { // Registration-related context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: embedded_services::cfu::component::CfuDevice, - power_devices: [embedded_services::power::policy::device::Device; N], + power_state: Mutex; N]>, + port_proxies: [Mutex; N], // State-related pd_alerts: [PubSubChannel; N], } -impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { +impl<'a, const N: usize, M: RawMutex, S: event::Sender> Storage { pub fn new( context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, cfu_id: ComponentId, - ports: [(GlobalPortId, power::policy::DeviceId); N], + pd_ports: [GlobalPortId; N], + power_policy_senders: [S; N], ) -> Self { Self { context, controller_id, - pd_ports: ports.map(|(port, _)| port), + pd_ports, cfu_device: embedded_services::cfu::component::CfuDevice::new(cfu_id), - power_devices: ports.map(|(_, device)| embedded_services::power::policy::device::Device::new(device)), + power_state: Mutex::new(power_policy_senders.map(|sender| PortPower { + sender, + state: Default::default(), + })), + port_proxies: from_fn(|_| Mutex::new(PortProxy)), pd_alerts: [const { PubSubChannel::new() }; N], } } - /// Create referenced storage from this storage - pub fn create_referenced(&self) -> Option> { + pub fn create_referenced>( + &self, + policy_receivers: [(power::policy::DeviceId, R); N], + ) -> Option> { ReferencedStorage::try_from_storage(self) } } @@ -214,15 +250,28 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { /// /// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state /// completely separately. -pub struct ReferencedStorage<'a, const N: usize, M: RawMutex> { - storage: &'a Storage<'a, N, M>, +pub struct ReferencedStorage< + 'a, + const N: usize, + M: RawMutex, + S: event::Sender, + R: event::Receiver, +> { + storage: &'a Storage, state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, + power_devices: [power::policy::device::Device<'a, Mutex, R>; N], } -impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { +impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: event::Receiver> + ReferencedStorage<'a, N, M, S, R> +{ /// Create a new referenced storage from the given storage and controller ID - fn try_from_storage(storage: &'a Storage) -> Option { + fn try_from_storage( + storage: &'a Storage, + policy_receivers: [(power::policy::DeviceId, R); N], + ) -> Option { + let mut policy_iter = policy_receivers.into_iter(); Some(Self { storage, state: RefCell::new(InternalState::try_new(storage)?), @@ -230,20 +279,25 @@ impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { storage.controller_id, storage.pd_ports.as_slice(), ), + power_devices: from_fn(|i| { + // Always safe because both arrays have length N + let (id, receiver) = policy_iter.next().unwrap(); + power::policy::device::Device::new(id, &storage.port_proxies[i], receiver) + }), }) } /// Creates the backing, returns `None` if a backing has already been created - pub fn create_backing<'b>(&'b self) -> Option> + pub fn create_backing<'b>(&'b self) -> Option> where 'b: 'a, { - self.state.try_borrow_mut().ok().map(|state| Backing { + self.state.try_borrow_mut().ok().map(|state| Backing:: { registration: Registration { context: self.storage.context, pd_controller: &self.pd_controller, cfu_device: &self.storage.cfu_device, - power_devices: &self.storage.power_devices, + power_devices: &self.power_devices, }, state, }) @@ -251,7 +305,7 @@ impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { } /// Wrapper around registration and type-erased state -pub struct Backing<'a> { - pub(crate) registration: Registration<'a>, +pub struct Backing<'a, M: RawMutex, R: event::Receiver> { + pub(crate) registration: Registration<'a, M, R>, pub(crate) state: RefMut<'a, dyn DynPortState<'a>>, } diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index d1b7f7246..7f468cb0b 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -4,6 +4,7 @@ use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_services::cfu::component::{InternalResponseData, RequestData}; use embedded_services::power; +use embedded_services::power::policy::policy; use embedded_services::type_c::controller::Controller; use embedded_services::{debug, error}; @@ -29,7 +30,8 @@ impl FwUpdateState { } } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> + ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { @@ -142,7 +144,7 @@ where // Detach from the power policy so it doesn't attempt to do anything while we are updating let controller_id = self.registration.pd_controller.id(); let mut detached_all = true; - for power in self.registration.power_devices { + for power in self.registration.power_event_senders { info!("Controller{}: checking power device", controller_id.0); if power.state().await != power::policy::device::State::Detached { info!("Controller{}: Detaching power device", controller_id.0); diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 8bae8560f..dd51129c1 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,10 +1,11 @@ use super::{ControllerWrapper, FwOfferValidator}; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_services::{sync::Lockable, trace, type_c::controller::Controller}; +use embedded_services::{event, power::policy::policy, sync::Lockable, trace, type_c::controller::Controller}; use embedded_usb_pd::{Error, LocalPortId}; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> + ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index fb2853c0c..6b9bc4146 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -28,11 +28,11 @@ use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::power::policy::device::StateKind; -use embedded_services::power::policy::{self, action}; +use embedded_services::power::policy::policy; use embedded_services::sync::Lockable; use embedded_services::type_c::controller::{self, Controller, PortStatus}; use embedded_services::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; -use embedded_services::{GlobalRawMutex, intrusive_list}; +use embedded_services::{GlobalRawMutex, event, intrusive_list}; use embedded_services::{debug, error, info, trace, warn}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; @@ -68,8 +68,13 @@ pub trait FwOfferValidator { pub const MAX_SUPPORTED_PORTS: usize = 2; /// Common functionality implemented on top of [`embedded_services::type_c::controller::Controller`] -pub struct ControllerWrapper<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> -where +pub struct ControllerWrapper< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + V: FwOfferValidator, +> where ::Inner: Controller, { controller: &'device C, @@ -78,7 +83,7 @@ where /// FW update ticker used to check for timeouts and recovery attempts fw_update_ticker: Mutex, /// Registration information for services - registration: backing::Registration<'device>, + registration: backing::Registration<'device, S>, /// State state: Mutex>>, /// SW port status event signal @@ -87,7 +92,8 @@ where config: config::Config, } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> + ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { @@ -95,7 +101,7 @@ where pub fn try_new( controller: &'device C, config: config::Config, - storage: &'device backing::ReferencedStorage<'device, N, M>, + storage: &'device backing::ReferencedStorage<'device, N, M, S>, fw_version_validator: V, ) -> Option { const { @@ -117,8 +123,8 @@ where } /// Get the power policy devices for this controller. - pub fn power_policy_devices(&self) -> &[policy::device::Device] { - self.registration.power_devices + pub fn power_policy_senders(&self) -> &[S] { + self.registration.power_event_senders } /// Get the cached port status, returns None if the port is invalid @@ -180,7 +186,7 @@ where async fn process_plug_event( &self, _controller: &mut C::Inner, - power: &policy::device::Device, + power: &mut S, port: LocalPortId, status: &PortStatus, ) -> Result<(), Error<::BusError>> { @@ -227,6 +233,7 @@ where async fn process_port_status_changed<'b>( &self, controller: &mut C::Inner, + power: &mut S, state: &mut dyn DynPortState<'_>, local_port_id: LocalPortId, status_event: PortStatusChanged, @@ -239,10 +246,6 @@ where let status = controller.get_port_status(local_port_id).await?; trace!("Port{} status: {:#?}", global_port_id.0, status); - - let power = self - .get_power_device(local_port_id) - .ok_or(Error::Pd(PdError::InvalidPort))?; trace!("Port{} status events: {:#?}", global_port_id.0, status_event); if status_event.plug_inserted_or_removed() { self.process_plug_event(controller, power, local_port_id, &status) @@ -607,13 +610,9 @@ where } /// Register all devices with their respective services - pub async fn register( - &'static self, - controllers: &intrusive_list::IntrusiveList, - ) -> Result<(), Error<::BusError>> { - // TODO: Unify these devices? - for device in self.registration.power_devices { - policy::register_device(device).map_err(|_| { + pub async fn register(&'static self, controllers: &intrusive_list::IntrusiveList,) -> Result<(), Error<::BusError>> { + for device in self.registration.power_event_senders { + policy::register_device(device).await.map_err(|_| { error!( "Controller{}: Failed to register power device {}", self.registration.pd_controller.id().0, diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index f55bd8bbf..f36b29261 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -2,6 +2,7 @@ use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; use embedded_services::debug; +use embedded_services::power::policy::device::State; use embedded_services::type_c::Cached; use embedded_services::type_c::controller::{InternalResponseData, Response}; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; @@ -9,7 +10,8 @@ use embedded_usb_pd::ucsi::{self, lpm}; use super::*; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> + ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { @@ -115,42 +117,27 @@ where LocalPortId(port_index as u8) } - /// Set the maximum sink voltage for a port - pub async fn set_max_sink_voltage(&self, local_port: LocalPortId, voltage_mv: Option) -> Result<(), PdError> { - let mut controller = self.controller.lock().await; - let _ = self - .process_set_max_sink_voltage(&mut controller, local_port, voltage_mv) - .await?; - Ok(()) - } - /// Process a request to set the maximum sink voltage for a port async fn process_set_max_sink_voltage( &self, controller: &mut C::Inner, + power_state: &mut embedded_services::power::policy::device::InternalState, + power_sender: &mut S, local_port: LocalPortId, voltage_mv: Option, ) -> Result { - let power_device = self.get_power_device(local_port).ok_or(PdError::InvalidPort)?; - - let state = power_device.state().await; + let state = power_state.state(); debug!("Port{}: Current state: {:#?}", local_port.0, state); - if let Ok(connected_consumer) = power_device.try_device_action::().await { + if matches!(state, State::ConnectedConsumer(_)) { debug!("Port{}: Set max sink voltage, connected consumer found", local_port.0); - if voltage_mv.is_some() - && voltage_mv - < power_device - .consumer_capability() - .await - .map(|c| c.capability.voltage_mv) - { + if voltage_mv.is_some() && voltage_mv < power_state.consumer_capability().map(|c| c.capability.voltage_mv) { // New max voltage is lower than current consumer capability which will trigger a renegociation // So disconnect first debug!( "Port{}: Disconnecting consumer before setting max sink voltage", local_port.0 ); - let _ = connected_consumer.disconnect().await; + power_sender.send(policy::RequestData::Disconnected).await; } } diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 2d3682a1d..4c4b035d6 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -15,19 +15,20 @@ use crate::wrapper::config::UnconstrainedSink; use super::*; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> + ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { /// Return the power device for the given port - pub fn get_power_device(&self, port: LocalPortId) -> Option<&policy::device::Device> { - self.registration.power_devices.get(port.0 as usize) + pub fn get_power_device(&self, port: LocalPortId) -> Option<&S> { + self.registration.power_event_senders.get(port.0 as usize) } /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, - power: &policy::device::Device, + power: &mut S, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); @@ -186,7 +187,7 @@ where deferred::Request<'_, GlobalRawMutex, CommandData, InternalResponseData>, ) { let futures: [_; MAX_SUPPORTED_PORTS] = from_fn(|i| async move { - if let Some(device) = self.registration.power_devices.get(i) { + if let Some(device) = self.registration.power_event_senders.get(i) { device.receive().await } else { future::pending().await diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 029b7f7d1..9d61d9013 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -1,5 +1,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{ + event, + power::policy::policy, sync::Lockable, trace, type_c::{ @@ -13,7 +15,8 @@ use crate::wrapper::{DynPortState, message::vdm::OutputKind}; use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> + ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { From 41b14c144afbb3318f884837efcbf00085d4c79d Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 5 Nov 2025 16:23:25 -0800 Subject: [PATCH 10/22] WIP: More type-c refactoring --- type-c-service/src/wrapper/backing.rs | 77 +++++++------ type-c-service/src/wrapper/cfu.rs | 57 +++------ type-c-service/src/wrapper/dp.rs | 10 +- type-c-service/src/wrapper/mod.rs | 80 ++++++------- type-c-service/src/wrapper/pd.rs | 39 ++++--- type-c-service/src/wrapper/power.rs | 159 +++++++++----------------- type-c-service/src/wrapper/vdm.rs | 16 ++- 7 files changed, 192 insertions(+), 246 deletions(-) diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index c1eb05a9a..2fd78ef27 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -118,37 +118,35 @@ impl power::policy::device::DeviceTrait for PortProxy { } /// Internal state containing all per-port and per-controller state -struct InternalState<'a, const N: usize> { +struct InternalState<'a, const N: usize, S: event::Sender> { controller_state: ControllerState, port_states: [PortState<'a>; N], + port_power: [PortPower; N], } -impl<'a, const N: usize> InternalState<'a, N> { - fn try_new(storage: &'a Storage) -> Option { - let port_states = storage.pd_alerts.each_ref().map(|pd_alert| { - Some(PortState { +impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { + fn try_new(storage: &'a Storage, power_policy_senders: [S; N]) -> Option { + Some(Self { + controller_state: ControllerState::default(), + port_states: from_fn(|i| PortState { status: PortStatus::new(), sw_status_event: PortStatusChanged::none(), sink_ready_deadline: None, pending_events: PortEvent::none(), - pd_alerts: (pd_alert.dyn_immediate_publisher(), pd_alert.dyn_subscriber().ok()?), - }) - }); - - if port_states.iter().any(|s| s.is_none()) { - return None; - } - - Some(Self { - controller_state: ControllerState::default(), - // Panic safety: All array elements checked above - #[allow(clippy::unwrap_used)] - port_states: port_states.map(|s| s.unwrap()), + pd_alerts: ( + storage.pd_alerts[i].dyn_immediate_publisher(), + storage.pd_alerts[i].dyn_subscriber()?, + ), + }), + port_power: power_policy_senders.map(|sender| PortPower { + sender, + state: Default::default(), + }), }) } } -impl<'a, const N: usize> DynPortState<'a> for InternalState<'a, N> { +impl<'a, const N: usize, S: event::Sender> DynPortState<'a, S> for InternalState<'a, N, S> { fn num_ports(&self) -> usize { self.port_states.len() } @@ -168,10 +166,18 @@ impl<'a, const N: usize> DynPortState<'a> for InternalState<'a, N> { fn controller_state_mut(&mut self) -> &mut ControllerState { &mut self.controller_state } + + fn port_power(&self) -> &[PortPower] { + &self.port_power + } + + fn port_power_mut(&mut self) -> &mut [PortPower] { + &mut self.port_power + } } /// Trait to erase the generic port count argument -pub trait DynPortState<'a> { +pub trait DynPortState<'a, S: event::Sender> { fn num_ports(&self) -> usize; fn port_states(&self) -> &[PortState<'a>]; @@ -179,6 +185,9 @@ pub trait DynPortState<'a> { fn controller_state(&self) -> &ControllerState; fn controller_state_mut(&mut self) -> &mut ControllerState; + + fn port_power(&self) -> &[PortPower]; + fn port_power_mut(&mut self) -> &mut [PortPower]; } /// Service registration objects @@ -197,7 +206,7 @@ impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, /// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. const MAX_BUFFERED_PD_ALERTS: usize = 4; -struct PortPower> { +pub struct PortPower> { pub sender: S, pub state: power::policy::device::InternalState, } @@ -209,7 +218,6 @@ pub struct Storage<'a, const N: usize, M: RawMutex, S: event::Sender; N]>, port_proxies: [Mutex; N], // State-related @@ -229,20 +237,18 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Sto controller_id, pd_ports, cfu_device: embedded_services::cfu::component::CfuDevice::new(cfu_id), - power_state: Mutex::new(power_policy_senders.map(|sender| PortPower { - sender, - state: Default::default(), - })), port_proxies: from_fn(|_| Mutex::new(PortProxy)), pd_alerts: [const { PubSubChannel::new() }; N], } } + /// Create referenced storage from this storage pub fn create_referenced>( &self, policy_receivers: [(power::policy::DeviceId, R); N], + power_policy_senders: [S; N], ) -> Option> { - ReferencedStorage::try_from_storage(self) + ReferencedStorage::try_from_storage(self, policy_receivers, power_policy_senders) } } @@ -257,8 +263,8 @@ pub struct ReferencedStorage< S: event::Sender, R: event::Receiver, > { - storage: &'a Storage, - state: RefCell>, + storage: &'a Storage, + state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, power_devices: [power::policy::device::Device<'a, Mutex, R>; N], } @@ -270,11 +276,12 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: fn try_from_storage( storage: &'a Storage, policy_receivers: [(power::policy::DeviceId, R); N], - ) -> Option { + power_policy_senders: [S; N], + ) -> Option { { let mut policy_iter = policy_receivers.into_iter(); Some(Self { storage, - state: RefCell::new(InternalState::try_new(storage)?), + state: RefCell::new(InternalState::try_new(storage, power_policy_senders)?), pd_controller: embedded_services::type_c::controller::Device::new( storage.controller_id, storage.pd_ports.as_slice(), @@ -288,11 +295,11 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: } /// Creates the backing, returns `None` if a backing has already been created - pub fn create_backing<'b>(&'b self) -> Option> + pub fn create_backing<'b>(&'b self) -> Option> where 'b: 'a, { - self.state.try_borrow_mut().ok().map(|state| Backing:: { + self.state.try_borrow_mut().ok().map(|state| Backing:: { registration: Registration { context: self.storage.context, pd_controller: &self.pd_controller, @@ -305,7 +312,7 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: } /// Wrapper around registration and type-erased state -pub struct Backing<'a, M: RawMutex, R: event::Receiver> { +pub struct Backing<'a, M: RawMutex, S: event::Sender, R: event::Receiver> { pub(crate) registration: Registration<'a, M, R>, - pub(crate) state: RefMut<'a, dyn DynPortState<'a>>, + pub(crate) state: RefMut<'a, dyn DynPortState<'a, S>>, } diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index 7f468cb0b..9ec63bb85 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -30,8 +30,14 @@ impl FwUpdateState { } } -impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> - ControllerWrapper<'device, M, C, S, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { @@ -100,7 +106,7 @@ where async fn process_abort_update( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, ) -> InternalResponseData { // abort the update process match controller.abort_fw_update().await { @@ -125,7 +131,7 @@ where async fn process_give_content( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, content: &FwUpdateContentCommand, ) -> InternalResponseData { let data = if let Some(data) = content.data.get(0..content.header.data_length as usize) { @@ -143,47 +149,14 @@ where // Detach from the power policy so it doesn't attempt to do anything while we are updating let controller_id = self.registration.pd_controller.id(); - let mut detached_all = true; - for power in self.registration.power_event_senders { + for power in state.port_power_mut() { info!("Controller{}: checking power device", controller_id.0); - if power.state().await != power::policy::device::State::Detached { + if power.state.state() != power::policy::device::State::Detached { info!("Controller{}: Detaching power device", controller_id.0); - if let Err(e) = power.detach().await { - error!("Controller{}: Failed to detach power device: {:?}", controller_id.0, e); - - // Sync to bring the controller to a known state with all services - match self.sync_state_internal(controller, state).await { - Ok(_) => debug!( - "Controller{}: Synced state after detaching power device", - controller_id.0 - ), - Err(Error::Pd(e)) => error!( - "Controller{}: Failed to sync state after detaching power device: {:?}", - controller_id.0, e - ), - Err(Error::Bus(_)) => error!( - "Controller{}: Failed to sync state after detaching power device, bus error", - controller_id.0 - ), - } - - detached_all = false; - break; - } + power.sender.send(policy::RequestData::Detached).await; } } - if !detached_all { - error!( - "Controller{}: Failed to detach all power devices, rejecting offer", - controller_id.0 - ); - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorPrepare, - )); - } - // Need to start the update self.fw_update_ticker.lock().await.reset(); match controller.start_fw_update().await { @@ -260,7 +233,7 @@ where } /// Process a CFU tick - pub async fn process_cfu_tick(&self, controller: &mut C::Inner, state: &mut dyn DynPortState<'_>) { + pub async fn process_cfu_tick(&self, controller: &mut C::Inner, state: &mut dyn DynPortState<'_, S>) { match state.controller_state_mut().fw_update_state { FwUpdateState::Idle => { // No FW update in progress, nothing to do @@ -303,7 +276,7 @@ where pub async fn process_cfu_command( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, command: &RequestData, ) -> InternalResponseData { if state.controller_state().fw_update_state == FwUpdateState::Recovery { diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index dd51129c1..d02fd561d 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -4,8 +4,14 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, power::policy::policy, sync::Lockable, trace, type_c::controller::Controller}; use embedded_usb_pd::{Error, LocalPortId}; -impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> - ControllerWrapper<'device, M, C, S, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 6b9bc4146..c2f2777da 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -37,7 +37,7 @@ use embedded_services::{debug, error, info, trace, warn}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; -use crate::wrapper::backing::DynPortState; +use crate::wrapper::backing::{DynPortState, PortPower}; use crate::wrapper::message::*; use crate::{PortEventStreamer, PortEventVariant}; @@ -73,6 +73,7 @@ pub struct ControllerWrapper< M: RawMutex, C: Lockable, S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > where ::Inner: Controller, @@ -83,17 +84,23 @@ pub struct ControllerWrapper< /// FW update ticker used to check for timeouts and recovery attempts fw_update_ticker: Mutex, /// Registration information for services - registration: backing::Registration<'device, S>, + registration: backing::Registration<'device, M, R>, /// State - state: Mutex>>, + state: Mutex>>, /// SW port status event signal sw_status_event: Signal, /// General config config: config::Config, } -impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> - ControllerWrapper<'device, M, C, S, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { @@ -101,7 +108,7 @@ where pub fn try_new( controller: &'device C, config: config::Config, - storage: &'device backing::ReferencedStorage<'device, N, M, S>, + storage: &'device backing::ReferencedStorage<'device, N, M, S, R>, fw_version_validator: V, ) -> Option { const { @@ -122,11 +129,6 @@ where }) } - /// Get the power policy devices for this controller. - pub fn power_policy_senders(&self) -> &[S] { - self.registration.power_event_senders - } - /// Get the cached port status, returns None if the port is invalid pub async fn get_cached_port_status(&self, local_port: LocalPortId) -> Option { self.state @@ -149,7 +151,7 @@ where async fn sync_state_internal( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, ) -> Result<(), Error<::BusError>> { // Sync the controller state with the PD controller for (i, port_state) in state.port_states_mut().iter_mut().enumerate() { @@ -186,7 +188,7 @@ where async fn process_plug_event( &self, _controller: &mut C::Inner, - power: &mut S, + power: &mut PortPower, port: LocalPortId, status: &PortStatus, ) -> Result<(), Error<::BusError>> { @@ -198,32 +200,12 @@ where info!("Plug event"); if status.is_connected() { info!("Plug inserted"); - - // Recover if we're not in the correct state - if power.state().await.kind() != StateKind::Detached { - warn!("Power device not in detached state, recovering"); - if let Err(e) = power.detach().await { - error!("Error detaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - - if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.attach().await { - error!("Error attaching power device: {:?}", e); - return PdError::Failed.into(); - } - } else { - // This should never happen - error!("Power device not in detached state"); - return PdError::InvalidMode.into(); + if let Err(e) = power.state.attach() { + warn!("Power device not in detached state, recovering: {:#?}", e); } } else { info!("Plug removed"); - if let Err(e) = power.detach().await { - error!("Error detaching power device: {:?}", e); - return PdError::Failed.into(); - }; + power.state.detach(); } Ok(()) @@ -233,8 +215,7 @@ where async fn process_port_status_changed<'b>( &self, controller: &mut C::Inner, - power: &mut S, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port_id: LocalPortId, status_event: PortStatusChanged, ) -> Result, Error<::BusError>> { @@ -247,6 +228,11 @@ where let status = controller.get_port_status(local_port_id).await?; trace!("Port{} status: {:#?}", global_port_id.0, status); trace!("Port{} status events: {:#?}", global_port_id.0, status_event); + + let power = state + .port_power_mut() + .get_mut(local_port_id.0 as usize) + .ok_or(PdError::InvalidPort)?; if status_event.plug_inserted_or_removed() { self.process_plug_event(controller, power, local_port_id, &status) .await?; @@ -279,7 +265,7 @@ where /// Finalize a port status change output fn finalize_port_status_change( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, status_event: PortStatusChanged, status: PortStatus, @@ -317,7 +303,7 @@ where /// Finalize a PD alert output fn finalize_pd_alert( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, alert: Ado, ) -> Result<(), Error<::BusError>> { @@ -610,7 +596,10 @@ where } /// Register all devices with their respective services - pub async fn register(&'static self, controllers: &intrusive_list::IntrusiveList,) -> Result<(), Error<::BusError>> { + pub async fn register( + &'static self, + controllers: &intrusive_list::IntrusiveList, + ) -> Result<(), Error<::BusError>> { for device in self.registration.power_event_senders { policy::register_device(device).await.map_err(|_| { error!( @@ -644,7 +633,14 @@ where } } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> Lockable for ControllerWrapper<'device, M, C, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> Lockable for ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index f36b29261..64d7c37b1 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -10,14 +10,20 @@ use embedded_usb_pd::ucsi::{self, lpm}; use super::*; -impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> - ControllerWrapper<'device, M, C, S, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { async fn process_get_pd_alert( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, ) -> Result, PdError> { loop { @@ -47,7 +53,7 @@ where /// even for controllers that might not always broadcast sink ready events. pub(super) fn check_sink_ready_timeout( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, status: &PortStatus, port: LocalPortId, new_contract: bool, @@ -121,23 +127,28 @@ where async fn process_set_max_sink_voltage( &self, controller: &mut C::Inner, - power_state: &mut embedded_services::power::policy::device::InternalState, - power_sender: &mut S, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, voltage_mv: Option, ) -> Result { - let state = power_state.state(); + let port_power = state + .port_power_mut() + .get_mut(local_port.0 as usize) + .ok_or(PdError::InvalidPort)?; + let state = port_power.state.state(); debug!("Port{}: Current state: {:#?}", local_port.0, state); if matches!(state, State::ConnectedConsumer(_)) { debug!("Port{}: Set max sink voltage, connected consumer found", local_port.0); - if voltage_mv.is_some() && voltage_mv < power_state.consumer_capability().map(|c| c.capability.voltage_mv) { + if voltage_mv.is_some() + && voltage_mv < port_power.state.consumer_capability().map(|c| c.capability.voltage_mv) + { // New max voltage is lower than current consumer capability which will trigger a renegociation // So disconnect first debug!( "Port{}: Disconnecting consumer before setting max sink voltage", local_port.0 ); - power_sender.send(policy::RequestData::Disconnected).await; + port_power.sender.send(policy::RequestData::Disconnected).await; } } @@ -153,7 +164,7 @@ where async fn process_get_port_status( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, cached: Cached, ) -> Result { @@ -180,7 +191,7 @@ where async fn process_port_command( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, command: &controller::PortCommand, ) -> Response<'static> { if state.controller_state().fw_update_state.in_progress() { @@ -259,7 +270,7 @@ where controller::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { match self.registration.pd_controller.lookup_local_port(command.port) { Ok(local_port) => { - self.process_set_max_sink_voltage(controller, local_port, voltage_mv) + self.process_set_max_sink_voltage(controller, state, local_port, voltage_mv) .await } Err(e) => Err(e), @@ -389,7 +400,7 @@ where async fn process_controller_command( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, command: &controller::InternalCommandData, ) -> Response<'static> { if state.controller_state().fw_update_state.in_progress() { @@ -425,7 +436,7 @@ where pub(super) async fn process_pd_command( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, command: &controller::Command, ) -> Response<'static> { match command { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 4c4b035d6..c9cbaf2ae 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -6,17 +6,23 @@ use embedded_services::{ ipc::deferred, power::policy::{ ConsumerPowerCapability, ProviderPowerCapability, - device::{CommandData, InternalResponseData}, - flags::PsuType, + device::{CommandData, InternalResponseData, ResponseData}, }, }; -use crate::wrapper::config::UnconstrainedSink; +use embedded_services::power::PowerCommand; +use embedded_services::power::policy::Error as PowerError; use super::*; -impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> - ControllerWrapper<'device, M, C, S, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { @@ -28,25 +34,14 @@ where /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, - power: &mut S, + power: &mut PortPower, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); - let current_state = power.state().await.kind(); + let current_state = power.state.state(); info!("current power state: {:?}", current_state); - // Recover if we're not in the correct state - if status.is_connected() { - if let action::device::AnyState::Detached(state) = power.device_action().await { - warn!("Power device is detached, attempting to attach"); - if let Err(e) = state.attach().await { - error!("Error attaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - } - let available_sink_contract = status.available_sink_contract.map(|c| { let mut c: ConsumerPowerCapability = c.into(); let unconstrained = match self.config.unconstrained_sink { @@ -59,89 +54,34 @@ where c }); - if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.notify_consumer_power_capability(available_sink_contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.notify_consumer_power_capability(available_sink_contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.notify_consumer_power_capability(available_sink_contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else { - error!("Invalid mode"); - return PdError::InvalidMode.into(); + if let Err(e) = power.state.update_consumer_power_capability(available_sink_contract) { + warn!( + "Device was not in correct state for consumer contract, recovered: {:#?}", + e + ); } - Ok(()) } /// Handle a new contract as provider pub(super) async fn process_new_provider_contract( &self, - power: &policy::device::Device, + power: &mut PortPower, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); - let current_state = power.state().await.kind(); + let current_state = power.state.state(); info!("current power state: {:?}", current_state); - if let action::device::AnyState::ConnectedConsumer(state) = power.device_action().await { - info!("ConnectedConsumer"); - if let Err(e) = state.detach().await { - info!("Error detaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - - // Recover if we're not in the correct state - if status.is_connected() { - if let action::device::AnyState::Detached(state) = power.device_action().await { - warn!("Power device is detached, attempting to attach"); - if let Err(e) = state.attach().await { - error!("Error attaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - } - - let contract = status.available_source_contract.map(|c| { - let mut c: ProviderPowerCapability = c.into(); - c.flags.set_psu_type(PsuType::TypeC); - c - }); - if let Ok(state) = power.try_device_action::().await { - if let Some(contract) = contract { - if let Err(e) = state.request_provider_power_capability(contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } - } else if let Ok(state) = power.try_device_action::().await { - if let Some(contract) = contract { - if let Err(e) = state.request_provider_power_capability(contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else { - // No longer need to source, so disconnect - if let Err(e) = state.disconnect().await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } - } else { - error!("Invalid mode"); - return PdError::InvalidMode.into(); + if let Err(e) = power.state.update_requested_provider_power_capability( + status.available_sink_contract.map(ProviderPowerCapability::from), + ) { + warn!( + "Device was not in correct state for provider contract, recovered: {:#?}", + e + ); } - Ok(()) } @@ -150,15 +90,21 @@ where &self, port: LocalPortId, controller: &mut C::Inner, - power: &policy::device::Device, + power: &mut PortPower, ) -> Result<(), Error<::BusError>> { - let state = power.state().await.kind(); - if state == StateKind::ConnectedConsumer { + if power.state.state().kind() == StateKind::ConnectedConsumer { info!("Port{}: Disconnect from ConnectedConsumer", port.0); if controller.enable_sink_path(port, false).await.is_err() { error!("Error disabling sink path"); return PdError::Failed.into(); } + + if let Err(e) = power.state.disconnect(false) { + warn!( + "{:?}: Device was not in correct state for disconnect, recovered: {:#?}", + port, e + ); + } } Ok(()) @@ -204,49 +150,46 @@ where pub(super) async fn process_power_command( &self, controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, port: LocalPortId, command: &CommandData, ) -> InternalResponseData { trace!("Processing power command: device{} {:#?}", port.0, command); if state.controller_state().fw_update_state.in_progress() { debug!("Port{}: Firmware update in progress", port.0); - return Err(policy::Error::Busy); + return Err(PowerError::Busy); } - let power = match self.get_power_device(port) { - Some(power) => power, - None => { - error!("Port{}: Error getting power device for port", port.0); - return Err(policy::Error::InvalidDevice); - } - }; - + let power = state.port_states_mut().get_mut(port.0).ok_or(PdError::InvalidPort)?; match command { - policy::device::CommandData::ConnectAsConsumer(capability) => { + PowerCommand::ConnectAsConsumer(capability) => { info!( "Port{}: Connect as consumer: {:?}, enable input switch", port.0, capability ); if controller.enable_sink_path(port, true).await.is_err() { error!("Error enabling sink path"); - return Err(policy::Error::Failed); + return Err(PowerError::Failed); } } - policy::device::CommandData::ConnectAsProvider(capability) => { - if self.process_connect_as_provider(port, *capability, controller).is_err() { + PowerCommand::ConnectAsProvider(capability) => { + if self + .process_connect_as_provider(port, *capability, controller) + .await + .is_err() + { error!("Error processing connect provider"); - return Err(policy::Error::Failed); + return Err(PowerError::Failed); } } - policy::device::CommandData::Disconnect => { + PowerCommand::Disconnect => { if self.process_disconnect(port, controller, power).await.is_err() { error!("Error processing disconnect"); - return Err(policy::Error::Failed); + return Err(PowerError::Failed); } } } - Ok(policy::device::ResponseData::Complete) + Ok(ResponseData::Complete) } } diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 9d61d9013..63f2e3d6b 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -15,8 +15,14 @@ use crate::wrapper::{DynPortState, message::vdm::OutputKind}; use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; -impl<'device, M: RawMutex, C: Lockable, S: event::Sender, V: FwOfferValidator> - ControllerWrapper<'device, M, C, S, V> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { @@ -39,7 +45,11 @@ where } /// Finalize a VDM output by notifying the service. - pub(super) fn finalize_vdm(&self, state: &mut dyn DynPortState<'_>, output: Output) -> Result<(), PdError> { + pub(super) async fn finalize_vdm( + &self, + state: &mut dyn DynPortState<'_, S>, + output: Output, + ) -> Result<(), PdError> { trace!("Finalizing VDM output: {:?}", output); let Output { port, kind } = output; let global_port_id = self.registration.pd_controller.lookup_global_port(port)?; From 5d4ba5cd3bb46f4cdb3707d9e023fba5d8e78e83 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 6 Nov 2025 10:56:47 -0800 Subject: [PATCH 11/22] WIP: Type-c proxies --- type-c-service/src/wrapper/backing.rs | 113 ++++++++++++-------------- type-c-service/src/wrapper/mod.rs | 1 + type-c-service/src/wrapper/power.rs | 14 ++-- type-c-service/src/wrapper/proxy.rs | 99 ++++++++++++++++++++++ 4 files changed, 158 insertions(+), 69 deletions(-) create mode 100644 type-c-service/src/wrapper/proxy.rs diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 2fd78ef27..5490540b7 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -59,7 +59,13 @@ use embedded_services::{ }; use embedded_usb_pd::{GlobalPortId, ado::Ado}; -use crate::{PortEventStreamer, wrapper::cfu}; +use crate::{ + PortEventStreamer, + wrapper::{ + cfu, + proxy::{PowerProxyChannel, PowerProxyDevice, PowerProxyReceiver}, + }, +}; /// Per-port state pub struct PortState<'a> { @@ -95,37 +101,18 @@ impl Default for ControllerState { } } -pub struct PortProxy; - -impl power::policy::device::DeviceTrait for PortProxy { - async fn disconnect(&mut self) -> Result<(), power::policy::Error> { - Ok(()) - } - - async fn connect_provider( - &mut self, - _capability: power::policy::ProviderPowerCapability, - ) -> Result<(), power::policy::Error> { - Ok(()) - } - - async fn connect_consumer( - &mut self, - _capability: power::policy::ConsumerPowerCapability, - ) -> Result<(), power::policy::Error> { - Ok(()) - } -} - /// Internal state containing all per-port and per-controller state struct InternalState<'a, const N: usize, S: event::Sender> { controller_state: ControllerState, port_states: [PortState<'a>; N], - port_power: [PortPower; N], + port_power: [PortPower<'a, S>; N], } impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { - fn try_new(storage: &'a Storage, power_policy_senders: [S; N]) -> Option { + fn try_new( + storage: &'a Storage, + power_events: [(S, PowerProxyReceiver<'a>); N], + ) -> Option { Some(Self { controller_state: ControllerState::default(), port_states: from_fn(|i| PortState { @@ -138,8 +125,9 @@ impl<'a, const N: usize, S: event::Sender> InternalState<'a storage.pd_alerts[i].dyn_subscriber()?, ), }), - port_power: power_policy_senders.map(|sender| PortPower { + port_power: power_events.map(|(sender, receiver)| PortPower { sender, + receiver, state: Default::default(), }), }) @@ -167,11 +155,11 @@ impl<'a, const N: usize, S: event::Sender> DynPortState<'a, &mut self.controller_state } - fn port_power(&self) -> &[PortPower] { + fn port_power(&self) -> &[PortPower<'a, S>] { &self.port_power } - fn port_power_mut(&mut self) -> &mut [PortPower] { + fn port_power_mut(&mut self) -> &mut [PortPower<'a, S>] { &mut self.port_power } } @@ -186,15 +174,15 @@ pub trait DynPortState<'a, S: event::Sender> { fn controller_state(&self) -> &ControllerState; fn controller_state_mut(&mut self) -> &mut ControllerState; - fn port_power(&self) -> &[PortPower]; - fn port_power_mut(&mut self) -> &mut [PortPower]; + fn port_power(&self) -> &[PortPower<'a, S>]; + fn port_power_mut(&mut self) -> &mut [PortPower<'a, S>]; } /// Service registration objects pub struct Registration<'a, M: RawMutex, R: event::Receiver> { pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, pub cfu_device: &'a embedded_services::cfu::component::CfuDevice, - pub power_devices: &'a [embedded_services::power::policy::device::Device<'a, Mutex, R>], + pub power_devices: &'a [embedded_services::power::policy::device::Device<'a, Mutex>, R>], } impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, M, R> { @@ -206,8 +194,9 @@ impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, /// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. const MAX_BUFFERED_PD_ALERTS: usize = 4; -pub struct PortPower> { +pub struct PortPower<'a, S: event::Sender> { pub sender: S, + pub receiver: PowerProxyReceiver<'a>, pub state: power::policy::device::InternalState, } @@ -218,7 +207,7 @@ pub struct Storage<'a, const N: usize, M: RawMutex, S: event::Sender; N], + power_proxy_channels: [PowerProxyChannel; N], // State-related pd_alerts: [PubSubChannel; N], @@ -237,18 +226,17 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Sto controller_id, pd_ports, cfu_device: embedded_services::cfu::component::CfuDevice::new(cfu_id), - port_proxies: from_fn(|_| Mutex::new(PortProxy)), + power_proxy_channels: from_fn(|_| PowerProxyChannel::new()), pd_alerts: [const { PubSubChannel::new() }; N], } } /// Create referenced storage from this storage - pub fn create_referenced>( + pub fn create_referenced>( &self, - policy_receivers: [(power::policy::DeviceId, R); N], - power_policy_senders: [S; N], - ) -> Option> { - ReferencedStorage::try_from_storage(self, policy_receivers, power_policy_senders) + policy_senders: [S; N], + ) -> Option> { + ReferencedStorage::try_from_storage(self, policy_senders) } } @@ -256,41 +244,42 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Sto /// /// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state /// completely separately. -pub struct ReferencedStorage< - 'a, - const N: usize, - M: RawMutex, - S: event::Sender, - R: event::Receiver, -> { +pub struct ReferencedStorage<'a, const N: usize, M: RawMutex, S: event::Sender> { storage: &'a Storage, state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, - power_devices: [power::policy::device::Device<'a, Mutex, R>; N], + power_proxy_devices: [Mutex>; N], } -impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: event::Receiver> - ReferencedStorage<'a, N, M, S, R> -{ +impl<'a, const N: usize, M: RawMutex, S: event::Sender> ReferencedStorage<'a, N, M, S> { /// Create a new referenced storage from the given storage and controller ID - fn try_from_storage( - storage: &'a Storage, - policy_receivers: [(power::policy::DeviceId, R); N], - power_policy_senders: [S; N], - ) -> Option { { - let mut policy_iter = policy_receivers.into_iter(); + fn try_from_storage(storage: &'a Storage, policy_senders: [S; N]) -> Option { + let mut power_proxy_devices = heapless::Vec::<_, N>::new(); + let mut power_events = heapless::Vec::<_, N>::new(); + + for (power_proxy_channel, policy_sender) in storage.power_proxy_channels.iter().zip(policy_senders.into_iter()) + { + power_proxy_devices.push(Mutex::new(power_proxy_channel.get_device())); + power_events.push((policy_sender, power_proxy_channel.get_receiver())); + } + Some(Self { storage, - state: RefCell::new(InternalState::try_new(storage, power_policy_senders)?), + state: RefCell::new(InternalState::new( + storage, + // Safe because both have N elements + power_events + .into_array() + .unwrap_or_else(|_| panic!("Failed to create power events")), + )), pd_controller: embedded_services::type_c::controller::Device::new( storage.controller_id, storage.pd_ports.as_slice(), ), - power_devices: from_fn(|i| { - // Always safe because both arrays have length N - let (id, receiver) = policy_iter.next().unwrap(); - power::policy::device::Device::new(id, &storage.port_proxies[i], receiver) - }), + // Safe because both have N elements + power_proxy_devices: power_proxy_devices + .into_array() + .unwrap_or_else(|_| panic!("Failed to create power devices")), }) } diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index c2f2777da..a2e6e73f0 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -48,6 +48,7 @@ mod dp; pub mod message; mod pd; mod power; +pub mod proxy; mod vdm; /// Base interval for checking for FW update timeouts and recovery attempts diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index c9cbaf2ae..33868ea70 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -10,8 +10,8 @@ use embedded_services::{ }, }; -use embedded_services::power::PowerCommand; use embedded_services::power::policy::Error as PowerError; +use embedded_services::power::policy::device::CommandData as PowerCommand; use super::*; @@ -26,11 +26,6 @@ impl< where ::Inner: Controller, { - /// Return the power device for the given port - pub fn get_power_device(&self, port: LocalPortId) -> Option<&S> { - self.registration.power_event_senders.get(port.0 as usize) - } - /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, @@ -160,7 +155,12 @@ where return Err(PowerError::Busy); } - let power = state.port_states_mut().get_mut(port.0).ok_or(PdError::InvalidPort)?; + let power = state.port_power_mut().get_mut(port.0 as usize); + if power.is_none() { + return Err(PowerError::InvalidDevice); + } + + let power = power.unwrap(); match command { PowerCommand::ConnectAsConsumer(capability) => { info!( diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs new file mode 100644 index 000000000..7a69646a4 --- /dev/null +++ b/type-c-service/src/wrapper/proxy.rs @@ -0,0 +1,99 @@ +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embedded_services::power; +use embedded_services::power::policy::device::{ + CommandData as PolicyCommandData, DeviceTrait, InternalResponseData as PolicyResponseData, +}; + +pub struct PowerProxyChannel { + command_channel: Channel, + response_channel: Channel, +} + +impl PowerProxyChannel { + pub fn new() -> Self { + Self { + command_channel: Channel::new(), + response_channel: Channel::new(), + } + } + + pub fn get_device(&self) -> PowerProxyDevice<'_> { + PowerProxyDevice { + sender: self.command_channel.dyn_sender(), + receiver: self.response_channel.dyn_receiver(), + } + } + + pub fn get_receiver(&self) -> PowerProxyReceiver<'_> { + PowerProxyReceiver { + receiver: self.command_channel.dyn_receiver(), + sender: self.response_channel.dyn_sender(), + } + } +} + +pub struct PowerProxyReceiver<'a> { + sender: DynamicSender<'a, PolicyResponseData>, + receiver: DynamicReceiver<'a, PolicyCommandData>, +} + +impl<'a> PowerProxyReceiver<'a> { + pub fn new( + receiver: DynamicReceiver<'a, PolicyCommandData>, + sender: DynamicSender<'a, PolicyResponseData>, + ) -> Self { + Self { receiver, sender } + } + + pub async fn receive(&mut self) -> PolicyCommandData { + self.receiver.receive().await + } + + pub async fn send(&mut self, response: PolicyResponseData) { + self.sender.send(response).await; + } +} + +pub struct PowerProxyDevice<'a> { + sender: DynamicSender<'a, PolicyCommandData>, + receiver: DynamicReceiver<'a, PolicyResponseData>, +} + +impl<'a> PowerProxyDevice<'a> { + pub fn new( + sender: DynamicSender<'a, PolicyCommandData>, + receiver: DynamicReceiver<'a, PolicyResponseData>, + ) -> Self { + Self { sender, receiver } + } + + async fn execute(&mut self, command: PolicyCommandData) -> PolicyResponseData { + self.sender.send(command).await; + self.receiver.receive().await + } +} + +impl<'a> DeviceTrait for PowerProxyDevice<'a> { + async fn disconnect(&mut self) -> Result<(), power::policy::Error> { + self.execute(PolicyCommandData::Disconnect).await?.complete_or_err() + } + + async fn connect_provider( + &mut self, + capability: power::policy::ProviderPowerCapability, + ) -> Result<(), power::policy::Error> { + self.execute(PolicyCommandData::ConnectAsProvider(capability)) + .await? + .complete_or_err() + } + + async fn connect_consumer( + &mut self, + capability: power::policy::ConsumerPowerCapability, + ) -> Result<(), power::policy::Error> { + self.execute(PolicyCommandData::ConnectAsConsumer(capability)) + .await? + .complete_or_err() + } +} From da31fca75fe0e6cf0dcf60636932dd9fe9a5dffe Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 6 Nov 2025 11:43:02 -0800 Subject: [PATCH 12/22] WIP: type-c service now builds --- type-c-service/src/wrapper/backing.rs | 147 ++++++++++++++++++-------- type-c-service/src/wrapper/message.rs | 14 +-- type-c-service/src/wrapper/mod.rs | 28 +++-- type-c-service/src/wrapper/power.rs | 33 +++--- 4 files changed, 144 insertions(+), 78 deletions(-) diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 5490540b7..5cb453eaa 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -21,7 +21,7 @@ //! use embedded_services::type_c::ControllerId; //! use embedded_services::power; //! use embedded_usb_pd::GlobalPortId; -//! use type_c_service::wrapper::backing::{Storage, ReferencedStorage}; +//! use type_c_service::wrapper::backing::{Storage, IntermediateStorage, ReferencedStorage}; //! //! //! const NUM_PORTS: usize = 2; @@ -34,8 +34,10 @@ //! 0x0, //! [(GlobalPortId(0), power::policy::DeviceId(0)), (GlobalPortId(1), power::policy::DeviceId(1))], //! )); +//! static INTERMEDIATE: StaticCell> = StaticCell::new(); +//! let intermediate = INTERMEDIATE.init(storage.create_intermediate()); //! static REFERENCED: StaticCell> = StaticCell::new(); -//! let referenced = REFERENCED.init(storage.create_referenced().unwrap()); +//! let referenced = REFERENCED.init(intermediate.create_referenced()); //! let _backing = referenced.create_backing().unwrap(); //! } //! ``` @@ -50,7 +52,10 @@ use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::{ event, - power::{self, policy::policy}, + power::{ + self, + policy::{DeviceId, policy}, + }, type_c::{ ControllerId, controller::PortStatus, @@ -105,14 +110,11 @@ impl Default for ControllerState { struct InternalState<'a, const N: usize, S: event::Sender> { controller_state: ControllerState, port_states: [PortState<'a>; N], - port_power: [PortPower<'a, S>; N], + port_power: [PortPower; N], } impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { - fn try_new( - storage: &'a Storage, - power_events: [(S, PowerProxyReceiver<'a>); N], - ) -> Option { + fn try_new(storage: &'a Storage, power_events: [S; N]) -> Option { Some(Self { controller_state: ControllerState::default(), port_states: from_fn(|i| PortState { @@ -125,9 +127,8 @@ impl<'a, const N: usize, S: event::Sender> InternalState<'a storage.pd_alerts[i].dyn_subscriber()?, ), }), - port_power: power_events.map(|(sender, receiver)| PortPower { + port_power: power_events.map(|sender| PortPower { sender, - receiver, state: Default::default(), }), }) @@ -155,11 +156,11 @@ impl<'a, const N: usize, S: event::Sender> DynPortState<'a, &mut self.controller_state } - fn port_power(&self) -> &[PortPower<'a, S>] { + fn port_power(&self) -> &[PortPower] { &self.port_power } - fn port_power_mut(&mut self) -> &mut [PortPower<'a, S>] { + fn port_power_mut(&mut self) -> &mut [PortPower] { &mut self.port_power } } @@ -174,8 +175,8 @@ pub trait DynPortState<'a, S: event::Sender> { fn controller_state(&self) -> &ControllerState; fn controller_state_mut(&mut self) -> &mut ControllerState; - fn port_power(&self) -> &[PortPower<'a, S>]; - fn port_power_mut(&mut self) -> &mut [PortPower<'a, S>]; + fn port_power(&self) -> &[PortPower]; + fn port_power_mut(&mut self) -> &mut [PortPower]; } /// Service registration objects @@ -194,9 +195,8 @@ impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, /// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. const MAX_BUFFERED_PD_ALERTS: usize = 4; -pub struct PortPower<'a, S: event::Sender> { +pub struct PortPower> { pub sender: S, - pub receiver: PowerProxyReceiver<'a>, pub state: power::policy::device::InternalState, } @@ -231,12 +231,55 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Sto } } - /// Create referenced storage from this storage - pub fn create_referenced>( - &self, - policy_senders: [S; N], - ) -> Option> { - ReferencedStorage::try_from_storage(self, policy_senders) + /// Create intermediate storage from this storage + pub fn create_intermediate(&self) -> IntermediateStorage<'_, N, M> { + IntermediateStorage::from_storage(self) + } +} + +/// Intermediate storage that holds power proxy devices +pub struct IntermediateStorage<'a, const N: usize, M: RawMutex> { + storage: &'a Storage, + power_proxy_devices: [Mutex>; N], + power_proxy_receivers: [Mutex>; N], +} + +impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { + fn from_storage(storage: &'a Storage) -> Self { + let mut power_proxy_devices = heapless::Vec::<_, N>::new(); + let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); + + for power_proxy_channel in storage.power_proxy_channels.iter() { + // Safe because everything has a length of N + power_proxy_devices + .push(Mutex::new(power_proxy_channel.get_device())) + .expect("Failed to insert power proxy device"); + power_proxy_receivers + .push(Mutex::new(power_proxy_channel.get_receiver())) + .expect("Failed to insert power proxy receiver"); + } + + Self { + storage, + // Safe because both have N elements + power_proxy_devices: power_proxy_devices + .into_array() + .expect("Failed to create power devices"), + power_proxy_receivers: power_proxy_receivers + .into_array() + .expect("Failed to create power receivers"), + } + } + + /// Create referenced storage from this intermediate storage + pub fn create_referenced<'b, S: event::Sender, R: event::Receiver>( + &'b self, + policy_args: [(DeviceId, S, R); N], + ) -> ReferencedStorage<'b, N, M, S, R> + where + 'b: 'a, + { + ReferencedStorage::from_intermediate(self, policy_args) } } @@ -244,40 +287,54 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Sto /// /// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state /// completely separately. -pub struct ReferencedStorage<'a, const N: usize, M: RawMutex, S: event::Sender> { - storage: &'a Storage, +pub struct ReferencedStorage< + 'a, + const N: usize, + M: RawMutex, + S: event::Sender, + R: event::Receiver, +> { + intermediate: &'a IntermediateStorage<'a, N, M>, state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, - power_proxy_devices: [Mutex>; N], + power_devices: [embedded_services::power::policy::device::Device<'a, Mutex>, R>; N], } -impl<'a, const N: usize, M: RawMutex, S: event::Sender> ReferencedStorage<'a, N, M, S> { - /// Create a new referenced storage from the given storage and controller ID - fn try_from_storage(storage: &'a Storage, policy_senders: [S; N]) -> Option { - let mut power_proxy_devices = heapless::Vec::<_, N>::new(); - let mut power_events = heapless::Vec::<_, N>::new(); - - for (power_proxy_channel, policy_sender) in storage.power_proxy_channels.iter().zip(policy_senders.into_iter()) - { - power_proxy_devices.push(Mutex::new(power_proxy_channel.get_device())); - power_events.push((policy_sender, power_proxy_channel.get_receiver())); +impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: event::Receiver> + ReferencedStorage<'a, N, M, S, R> +{ + /// Create a new referenced storage from the given intermediate storage + fn from_intermediate(intermediate: &'a IntermediateStorage<'a, N, M>, policy_args: [(DeviceId, S, R); N]) -> Self { + let mut power_senders = heapless::Vec::<_, N>::new(); + let mut power_devices = heapless::Vec::<_, N>::new(); + + for (i, (device_id, policy_sender, policy_receiver)) in policy_args.into_iter().enumerate() { + power_senders + .push(policy_sender) + .unwrap_or_else(|_| panic!("Failed to insert policy sender")); + power_devices + .push(embedded_services::power::policy::device::Device::new( + device_id, + &intermediate.power_proxy_devices[i], + policy_receiver, + )) + .unwrap_or_else(|_| panic!("Failed to insert power device")); } - Some(Self { - storage, + Self { + intermediate, state: RefCell::new(InternalState::new( - storage, + intermediate.storage, // Safe because both have N elements - power_events + power_senders .into_array() .unwrap_or_else(|_| panic!("Failed to create power events")), )), pd_controller: embedded_services::type_c::controller::Device::new( - storage.controller_id, - storage.pd_ports.as_slice(), + intermediate.storage.controller_id, + intermediate.storage.pd_ports.as_slice(), ), - // Safe because both have N elements - power_proxy_devices: power_proxy_devices + power_devices: power_devices .into_array() .unwrap_or_else(|_| panic!("Failed to create power devices")), }) @@ -292,10 +349,11 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Ref registration: Registration { context: self.storage.context, pd_controller: &self.pd_controller, - cfu_device: &self.storage.cfu_device, + cfu_device: &self.intermediate.storage.cfu_device, power_devices: &self.power_devices, }, state, + power_receivers: &self.intermediate.power_proxy_receivers, }) } } @@ -304,4 +362,5 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Ref pub struct Backing<'a, M: RawMutex, S: event::Sender, R: event::Receiver> { pub(crate) registration: Registration<'a, M, R>, pub(crate) state: RefMut<'a, dyn DynPortState<'a, S>>, + pub(crate) power_receivers: &'a [Mutex>], } diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index f8416b3ae..e5650f219 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -31,12 +31,11 @@ pub struct EventPortNotification { } /// Power policy command event data -pub struct EventPowerPolicyCommand<'a> { +pub struct EventPowerPolicyCommand { /// Port ID pub port: LocalPortId, /// Power policy request - pub request: - deferred::Request<'a, GlobalRawMutex, policy::device::CommandData, policy::device::InternalResponseData>, + pub request: policy::device::CommandData, } /// CFU events @@ -58,7 +57,7 @@ pub enum Event<'a> { /// Port notification PortNotification(EventPortNotification), /// Power policy command received - PowerPolicyCommand(EventPowerPolicyCommand<'a>), + PowerPolicyCommand(EventPowerPolicyCommand), /// Command from TCPM ControllerCommand(deferred::Request<'a, GlobalRawMutex, controller::Command, controller::Response<'static>>), /// Cfu event @@ -88,12 +87,9 @@ pub struct OutputPdAlert { } /// Power policy command output data -pub struct OutputPowerPolicyCommand<'a> { +pub struct OutputPowerPolicyCommand { /// Port ID pub port: LocalPortId, - /// Power policy request - pub request: - deferred::Request<'a, GlobalRawMutex, policy::device::CommandData, policy::device::InternalResponseData>, /// Response pub response: policy::device::InternalResponseData, } @@ -158,7 +154,7 @@ pub enum Output<'a> { /// Vendor-defined messaging. Vdm(vdm::Output), /// Power policy command received - PowerPolicyCommand(OutputPowerPolicyCommand<'a>), + PowerPolicyCommand(OutputPowerPolicyCommand), /// TPCM command response ControllerCommand(OutputControllerCommand<'a>), /// CFU recovery tick diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index a2e6e73f0..c55c81d32 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -27,6 +27,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; +use embedded_services::event; use embedded_services::power::policy::device::StateKind; use embedded_services::power::policy::policy; use embedded_services::sync::Lockable; @@ -39,6 +40,7 @@ use embedded_usb_pd::{Error, LocalPortId, PdError}; use crate::wrapper::backing::{DynPortState, PortPower}; use crate::wrapper::message::*; +use crate::wrapper::proxy::PowerProxyReceiver; use crate::{PortEventStreamer, PortEventVariant}; pub mod backing; @@ -92,6 +94,8 @@ pub struct ControllerWrapper< sw_status_event: Signal, /// General config config: config::Config, + /// Power proxy receivers + power_proxy_receivers: &'device [Mutex>], } impl< @@ -127,6 +131,7 @@ where registration: backing.registration, state: Mutex::new(backing.state), sw_status_event: Signal::new(), + power_proxy_receivers: backing.power_receivers, }) } @@ -508,13 +513,9 @@ where } Event::PowerPolicyCommand(EventPowerPolicyCommand { port, request }) => { let response = self - .process_power_command(&mut controller, state.deref_mut().deref_mut(), port, &request.command) + .process_power_command(&mut controller, state.deref_mut().deref_mut(), port, &request) .await; - Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { - port, - request, - response, - })) + Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) } Event::ControllerCommand(request) => { let response = self @@ -557,9 +558,18 @@ where Output::PdAlert(OutputPdAlert { port, ado }) => { self.finalize_pd_alert(state.deref_mut().deref_mut(), port, ado) } - Output::Vdm(vdm) => self.finalize_vdm(state.deref_mut().deref_mut(), vdm).map_err(Error::Pd), - Output::PowerPolicyCommand(OutputPowerPolicyCommand { request, response, .. }) => { - request.respond(response); + Output::Vdm(vdm) => self + .finalize_vdm(state.deref_mut().deref_mut(), vdm) + .await + .map_err(Error::Pd), + Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response }) => { + self.power_proxy_receivers + .get(port.0 as usize) + .ok_or(Error::Pd(PdError::InvalidPort))? + .lock() + .await + .send(response) + .await; Ok(()) } Output::ControllerCommand(OutputControllerCommand { request, response }) => { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 33868ea70..46a90a314 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -1,9 +1,9 @@ //! Module contain power-policy related message handling -use core::future; +use core::pin::pin; +use embassy_futures::select::select_slice; use embedded_services::{ debug, - ipc::deferred, power::policy::{ ConsumerPowerCapability, ProviderPowerCapability, device::{CommandData, InternalResponseData, ResponseData}, @@ -121,22 +121,23 @@ where /// /// Returns (local port ID, deferred request) /// DROP SAFETY: Call to a select over drop safe futures - pub(super) async fn wait_power_command( - &self, - ) -> ( - LocalPortId, - deferred::Request<'_, GlobalRawMutex, CommandData, InternalResponseData>, - ) { - let futures: [_; MAX_SUPPORTED_PORTS] = from_fn(|i| async move { - if let Some(device) = self.registration.power_event_senders.get(i) { - device.receive().await - } else { - future::pending().await + pub(super) async fn wait_power_command(&self) -> (LocalPortId, CommandData) { + let mut futures = heapless::Vec::<_, MAX_SUPPORTED_PORTS>::new(); + for receiver in self.power_proxy_receivers { + if futures + .push(async { + let mut lock = receiver.lock().await; + lock.receive().await + }) + .is_err() + { + error!("Futures vec overflow"); } - }); + } + // DROP SAFETY: Select over drop safe futures - let (request, local_id) = select_array(futures).await; - trace!("Power command: device{} {:#?}", local_id, request.command); + let (request, local_id) = select_slice(pin!(futures.as_mut_slice())).await; + trace!("Power command: device{} {:#?}", local_id, request); (LocalPortId(local_id as u8), request) } From 38fb44b452103eba26d526fd73e2ebd4376805ec Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 10 Nov 2025 09:23:54 -0800 Subject: [PATCH 13/22] Initial examples refactoring --- embedded-service/src/power/policy/device.rs | 12 +- examples/std/src/bin/power_policy.rs | 312 ++++++++++-------- examples/std/src/bin/type_c/unconstrained.rs | 93 ++++-- .../std/src/lib/type_c/mock_controller.rs | 35 +- 4 files changed, 257 insertions(+), 195 deletions(-) diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 545d868c0..de2ac73b5 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -128,11 +128,7 @@ impl InternalState { Ok(()) } else { Err(Error::InvalidState( - &[ - StateKind::Idle, - StateKind::ConnectedConsumer, - StateKind::ConnectedProvider, - ], + &[StateKind::Idle, StateKind::ConnectedConsumer], self.state.kind(), )) }; @@ -157,11 +153,7 @@ impl InternalState { Ok(()) } else { Err(Error::InvalidState( - &[ - StateKind::Idle, - StateKind::ConnectedConsumer, - StateKind::ConnectedProvider, - ], + &[StateKind::Idle, StateKind::ConnectedProvider], self.state.kind(), )) }; diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index af2ee9608..e6efb6781 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -1,11 +1,20 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, once_lock::OnceLock, pubsub::PubSubChannel}; +use embassy_sync::{ + blocking_mutex::raw::NoopRawMutex, + channel::{self, Channel}, + mutex::Mutex, + pubsub::PubSubChannel, +}; use embassy_time::{self as _, Timer}; use embedded_services::{ + GlobalRawMutex, broadcaster::immediate as broadcaster, - power::policy::{self, ConsumerPowerCapability, PowerCapability, device, flags}, + power::policy::{ + self, ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability, device::DeviceTrait, flags, + }, }; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; const LOW_POWER: PowerCapability = PowerCapability { @@ -18,195 +27,209 @@ const HIGH_POWER: PowerCapability = PowerCapability { current_ma: 3000, }; -struct ExampleDevice { - device: policy::device::Device, +const DEVICE0_ID: policy::DeviceId = policy::DeviceId(0); +const DEVICE1_ID: policy::DeviceId = policy::DeviceId(1); + +const PER_CALL_DELAY_MS: u64 = 1000; + +struct ExampleDevice<'a> { + sender: channel::DynamicSender<'a, policy::policy::RequestData>, } -impl ExampleDevice { - fn new(id: policy::DeviceId) -> Self { - Self { - device: policy::device::Device::new(id), - } +impl<'a> ExampleDevice<'a> { + fn new(sender: channel::DynamicSender<'a, policy::policy::RequestData>) -> Self { + Self { sender } } - async fn process_request(&self) -> Result<(), policy::Error> { - let request = self.device.receive().await; - match request.command { - device::CommandData::ConnectAsConsumer(capability) => { - info!( - "Device {} received connect consumer at {:#?}", - self.device.id().0, - capability - ); - } - device::CommandData::ConnectAsProvider(capability) => { - info!( - "Device {} received connect provider at {:#?}", - self.device.id().0, - capability - ); - } - device::CommandData::Disconnect => { - info!("Device {} received disconnect", self.device.id().0); - } - } + pub async fn simulate_attach(&mut self) { + self.sender.send(policy::policy::RequestData::Attached).await; + } - request.respond(Ok(policy::device::ResponseData::Complete)); - Ok(()) + pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option) { + self.sender + .send(policy::policy::RequestData::UpdatedConsumerCapability(capability)) + .await; } -} -impl policy::device::DeviceContainer for ExampleDevice { - fn get_power_policy_device(&self) -> &policy::device::Device { - &self.device + pub async fn simulate_detach(&mut self) { + self.sender.send(policy::policy::RequestData::Detached).await; } -} -#[embassy_executor::task] -async fn device_task0(device: &'static ExampleDevice) { - loop { - if let Err(e) = device.process_request().await { - error!("Error processing request: {e:?}"); - } + pub async fn simulate_update_requested_provider_power_capability( + &mut self, + capability: Option, + ) { + self.sender + .send(policy::policy::RequestData::RequestedProviderCapability(capability)) + .await } } -#[embassy_executor::task] -async fn device_task1(device: &'static ExampleDevice) { - loop { - if let Err(e) = device.process_request().await { - error!("Error processing request: {e:?}"); - } +impl DeviceTrait for ExampleDevice<'_> { + async fn disconnect(&mut self) -> Result<(), Error> { + Ok(()) + } + + async fn connect_provider(&mut self, _capability: ProviderPowerCapability) -> Result<(), Error> { + Ok(()) + } + + async fn connect_consumer(&mut self, _capability: ConsumerPowerCapability) -> Result<(), Error> { + Ok(()) } } #[embassy_executor::task] -async fn run(spawner: Spawner) { +async fn run(_spawner: Spawner) { embedded_services::init().await; info!("Creating device 0"); - static DEVICE0: OnceLock = OnceLock::new(); - let device0_mock = DEVICE0.get_or_init(|| ExampleDevice::new(policy::DeviceId(0))); - policy::register_device(device0_mock).unwrap(); - spawner.must_spawn(device_task0(device0_mock)); - let device0 = device0_mock.device.try_device_action().await.unwrap(); + static DEVICE0_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); + static DEVICE0: StaticCell> = StaticCell::new(); + let device0 = DEVICE0.init(Mutex::new(ExampleDevice::new(device0_event_channel.dyn_sender()))); + static DEVICE0_REGISTRATION: StaticCell< + policy::device::Device< + 'static, + Mutex, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let device0_registration = DEVICE0_REGISTRATION.init(policy::device::Device::new( + DEVICE0_ID, + device0, + device0_event_channel.dyn_receiver(), + )); + policy::register_device(device0_registration).await.unwrap(); info!("Creating device 1"); - static DEVICE1: OnceLock = OnceLock::new(); - let device1_mock = DEVICE1.get_or_init(|| ExampleDevice::new(policy::DeviceId(1))); - policy::register_device(device1_mock).unwrap(); - spawner.must_spawn(device_task1(device1_mock)); - let device1 = device1_mock.device.try_device_action().await.unwrap(); + static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); + static DEVICE1: StaticCell> = StaticCell::new(); + let device1 = DEVICE1.init(Mutex::new(ExampleDevice::new(device1_event_channel.dyn_sender()))); + static DEVICE1_REGISTRATION: StaticCell< + policy::device::Device< + 'static, + Mutex, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let device1_registration = DEVICE1_REGISTRATION.init(policy::device::Device::new( + DEVICE1_ID, + device1, + device1_event_channel.dyn_receiver(), + )); + policy::register_device(device1_registration).await.unwrap(); // Plug in device 0, should become current consumer info!("Connecting device 0"); - let device0 = device0.attach().await.unwrap(); - device0 - .notify_consumer_power_capability(Some(ConsumerPowerCapability { + { + let mut dev0 = device0.lock().await; + dev0.simulate_attach().await; + dev0.simulate_update_consumer_power_capability(Some(ConsumerPowerCapability { capability: LOW_POWER, flags: flags::Consumer::none().with_unconstrained_power(), })) - .await - .unwrap(); + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Plug in device 1, should become current consumer info!("Connecting device 1"); - let device1 = device1.attach().await.unwrap(); - device1 - .notify_consumer_power_capability(Some(HIGH_POWER.into())) - .await - .unwrap(); + { + let mut dev1 = device1.lock().await; + dev1.simulate_attach().await; + dev1.simulate_update_consumer_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Unplug device 0, device 1 should remain current consumer - info!("Unpluging device 0"); - let device0 = device0.detach().await.unwrap(); + info!("Unplugging device 0"); + { + let mut dev0 = device0.lock().await; + dev0.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Plug in device 0, device 1 should remain current consumer info!("Connecting device 0"); - let device0 = device0.attach().await.unwrap(); - device0 - .notify_consumer_power_capability(Some(LOW_POWER.into())) - .await - .unwrap(); + { + let mut dev0 = device0.lock().await; + dev0.simulate_attach().await; + dev0.simulate_update_consumer_power_capability(Some(LOW_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Unplug device 1, device 0 should become current consumer info!("Unplugging device 1"); - let device1 = device1.detach().await.unwrap(); + { + let mut dev1 = device1.lock().await; + dev1.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Replug device 1, device 1 becomes current consumer info!("Connecting device 1"); - let device1 = device1.attach().await.unwrap(); - device1 - .notify_consumer_power_capability(Some(HIGH_POWER.into())) - .await - .unwrap(); + { + let mut dev1 = device1.lock().await; + dev1.simulate_attach().await; + dev1.simulate_update_consumer_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; - // Disconnect consumer device 0, device 1 should remain current consumer + // Detach consumer device 0, device 1 should remain current consumer // Device 0 should not be able to consume after device 1 is unplugged - info!("Disconnecting device 0"); - device0.notify_consumer_power_capability(None).await.unwrap(); - let device1 = device1.detach().await.unwrap(); + info!("Detach device 0"); + { + let mut dev0 = device0.lock().await; + dev0.simulate_update_consumer_power_capability(None).await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; - // Switch to provider on device0 - info!("Device 0 requesting provider"); - device0 - .request_provider_power_capability(LOW_POWER.into()) - .await - .unwrap(); - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await - ); + { + let mut dev1 = device1.lock().await; + dev1.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + // Switch to provider on device0 info!("Device 1 attach and requesting provider"); - let device1 = device1.attach().await.unwrap(); - device1 - .request_provider_power_capability(LOW_POWER.into()) - .await - .unwrap(); - // Wait for the provider to be connected - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await - ); + { + let mut dev1 = device1.lock().await; + dev1.simulate_attach().await; + dev1.simulate_update_requested_provider_power_capability(Some(LOW_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Provider upgrade should fail because device 0 is already connected info!("Device 1 attempting provider upgrade"); - device1 - .request_provider_power_capability(HIGH_POWER.into()) - .await - .unwrap(); - // Wait for the upgrade flow to complete - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await - ); + { + let mut dev1 = device1.lock().await; + dev1.simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Disconnect device 0 info!("Device 0 disconnecting"); - device0.detach().await.unwrap(); - // Wait for the detach flow to complete - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await - ); + { + let mut dev0 = device0.lock().await; + dev0.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Provider upgrade should succeed now info!("Device 1 attempting provider upgrade"); - device1 - .request_provider_power_capability(HIGH_POWER.into()) - .await - .unwrap(); - // Wait for the upgrade flow to complete - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await - ); + { + let mut dev1 = device1.lock().await; + dev1.simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; } #[embassy_executor::task] @@ -235,10 +258,17 @@ async fn receiver_task() { } #[embassy_executor::task] -async fn power_policy_task(config: power_policy_service::config::Config) { - power_policy_service::task::task(config) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_task() { + static POWER_POLICY: StaticCell< + PowerPolicy< + Mutex>, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let power_policy = POWER_POLICY.init(PowerPolicy::create(Default::default()).unwrap()); + loop { + power_policy.process().await.unwrap(); + } } fn main() { @@ -247,7 +277,7 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(power_policy_task(power_policy_service::config::Config::default())); + spawner.must_spawn(power_policy_task()); spawner.must_spawn(run(spawner)); spawner.must_spawn(receiver_task()); }); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index c9f489b88..f5cc1d764 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,9 +1,13 @@ use crate::mock_controller::Wrapper; use embassy_executor::Executor; +use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::GlobalRawMutex; use embedded_services::power::policy::PowerCapability; +use embedded_services::power::policy::{self, PowerCapability}; use embedded_services::power::{self}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; @@ -14,6 +18,7 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; use type_c_service::service::config::Config; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; const NUM_PD_CONTROLLERS: usize = 3; @@ -147,14 +152,25 @@ fn main() { static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); - static REFERENCED: StaticCell> = StaticCell::new(); - let referenced = REFERENCED.init( - storage - .create_referenced() - .expect("Failed to create referenced storage"), - ); + static STORAGE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + static INTERMEDIATE0: StaticCell> = StaticCell::new(); + let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); + static CHANNEL0: StaticCell> = StaticCell::new(); + let channel0 = CHANNEL0.init(channel::Channel::new()); + static REFERENCED0: StaticCell< + ReferencedStorage< + 1, + GlobalRawMutex, + channel::DynamicSender, + channel::DynamicReceiver, + >, + > = StaticCell::new(); + let referenced0 = REFERENCED0.init(intermediate0.create_referenced([( + POWER0_ID, + channel0.dyn_sender(), + channel0.dyn_receiver(), + )])); static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); @@ -162,23 +178,29 @@ fn main() { let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); static WRAPPER0: StaticCell = StaticCell::new(); let wrapper0 = WRAPPER0.init( - mock_controller::Wrapper::try_new( - controller0, - Default::default(), - referenced, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), + mock_controller::Wrapper::try_new(controller0, referenced0, crate::mock_controller::Validator) + .expect("Failed to create wrapper"), ); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); - static REFERENCED1: StaticCell> = StaticCell::new(); - let referenced1 = REFERENCED1.init( - storage1 - .create_referenced() - .expect("Failed to create referenced storage"), - ); + let storage1 = STORAGE1.init(Storage::new(CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + static INTERMEDIATE1: StaticCell> = StaticCell::new(); + let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); + static CHANNEL1: StaticCell> = StaticCell::new(); + let channel1 = CHANNEL1.init(channel::Channel::new()); + static REFERENCED1: StaticCell< + ReferencedStorage< + 1, + GlobalRawMutex, + channel::DynamicSender, + channel::DynamicReceiver, + >, + > = StaticCell::new(); + let referenced1 = REFERENCED1.init(intermediate1.create_referenced([( + POWER0_ID, + channel1.dyn_sender(), + channel1.dyn_receiver(), + )])); static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); @@ -196,13 +218,24 @@ fn main() { ); static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [(PORT2_ID, POWER2_ID)])); - static REFERENCED2: StaticCell> = StaticCell::new(); - let referenced2 = REFERENCED2.init( - storage2 - .create_referenced() - .expect("Failed to create referenced storage"), - ); + let storage2 = STORAGE2.init(Storage::new(CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); + static INTERMEDIATE2: StaticCell> = StaticCell::new(); + let intermediate2 = INTERMEDIATE2.init(storage2.create_intermediate()); + static CHANNEL2: StaticCell> = StaticCell::new(); + let channel2 = CHANNEL2.init(channel::Channel::new()); + static REFERENCED2: StaticCell< + ReferencedStorage< + 1, + GlobalRawMutex, + channel::DynamicSender, + channel::DynamicReceiver, + >, + > = StaticCell::new(); + let referenced2 = REFERENCED2.init(intermediate2.create_referenced([( + POWER0_ID, + channel2.dyn_sender(), + channel2.dyn_receiver(), + )])); static STATE2: StaticCell = StaticCell::new(); let state2 = STATE2.init(mock_controller::ControllerState::new()); @@ -219,6 +252,8 @@ fn main() { .expect("Failed to create wrapper"), ); + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { spawner.must_spawn(power_policy_service_task()); spawner.must_spawn(service_task(context, controller_list, [wrapper0, wrapper1, wrapper2])); diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 766373508..fa9a59f86 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -1,8 +1,8 @@ -use embassy_sync::{mutex::Mutex, signal::Signal}; +use embassy_sync::{channel, mutex::Mutex, signal::Signal}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOfferResponse, HostToken}; use embedded_services::{ GlobalRawMutex, - power::policy::PowerCapability, + power::policy::{PowerCapability, policy}, type_c::{ controller::{ AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, @@ -16,7 +16,6 @@ use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; use log::{debug, info, trace}; -use std::cell::Cell; pub struct ControllerState { events: Signal, @@ -95,26 +94,26 @@ impl Default for ControllerState { } } -pub struct Controller<'a> { - state: &'a ControllerState, - events: Cell, +pub struct Controller { + state: ControllerState, + events: PortEvent, } -impl<'a> Controller<'a> { - pub fn new(state: &'a ControllerState) -> Self { +impl Controller { + pub fn new(state: ControllerState) -> Self { Self { state, - events: Cell::new(PortEvent::none()), + events: PortEvent::none(), } } /// Function to demonstrate calling functions directly on the controller - pub fn custom_function(&self) { + pub fn custom_function(&mut self) { info!("Custom function called on controller"); } } -impl embedded_services::type_c::controller::Controller for Controller<'_> { +impl embedded_services::type_c::controller::Controller for Controller { type BusError = (); async fn wait_port_event(&mut self) -> Result<(), Error> { @@ -125,9 +124,9 @@ impl embedded_services::type_c::controller::Controller for Controller<'_> { } async fn clear_port_events(&mut self, _port: LocalPortId) -> Result> { - let events = self.events.get(); + let events = self.events; debug!("Clear port events: {events:#?}"); - self.events.set(PortEvent::none()); + self.events = PortEvent::none(); Ok(events) } @@ -336,5 +335,11 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { } } -pub type Wrapper<'a> = - type_c_service::wrapper::ControllerWrapper<'a, GlobalRawMutex, Mutex>, Validator>; +pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< + 'a, + GlobalRawMutex, + Mutex>, + channel::DynamicSender<'a, policy::RequestData>, + channel::DynamicReceiver<'a, policy::RequestData>, + Validator, +>; From b3cc2f6a9dd57285967f701837dd237949196784 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 21 Jan 2026 10:27:24 -0800 Subject: [PATCH 14/22] Rebase fix-up --- embedded-service/src/power/policy/device.rs | 22 ++++----- embedded-service/src/power/policy/policy.rs | 35 ++++++------- power-policy-service/src/consumer.rs | 8 +-- power-policy-service/src/provider.rs | 12 +++-- power-policy-service/src/task.rs | 25 +++++----- type-c-service/src/wrapper/backing.rs | 55 +++++++++++++-------- type-c-service/src/wrapper/cfu.rs | 18 +++---- type-c-service/src/wrapper/dp.rs | 10 ++-- type-c-service/src/wrapper/mod.rs | 54 ++++++++++---------- type-c-service/src/wrapper/pd.rs | 16 +++--- type-c-service/src/wrapper/power.rs | 29 ++++++----- type-c-service/src/wrapper/vdm.rs | 10 ++-- 12 files changed, 157 insertions(+), 137 deletions(-) diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index de2ac73b5..31e887612 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -277,12 +277,12 @@ where pub receiver: Mutex, } -impl<'a, C: Lockable, R: Receiver> Device<'a, C, R> +impl<'a, D: Lockable, R: Receiver> Device<'a, D, R> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { /// Create a new device - pub fn new(id: DeviceId, device: &'a C, receiver: R) -> Self { + pub fn new(id: DeviceId, device: &'a D, receiver: R) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -330,9 +330,9 @@ where } } -impl + 'static> intrusive_list::NodeContainer for Device<'static, C, R> +impl + 'static> intrusive_list::NodeContainer for Device<'static, D, R> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { fn get_node(&self) -> &crate::Node { &self.node @@ -340,19 +340,19 @@ where } /// Trait for any container that holds a device -pub trait DeviceContainer> +pub trait DeviceContainer> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { /// Get the underlying device struct - fn get_power_policy_device(&self) -> &Device<'_, C, R>; + fn get_power_policy_device(&self) -> &Device<'_, D, R>; } -impl> DeviceContainer for Device<'_, C, R> +impl> DeviceContainer for Device<'_, D, R> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { - fn get_power_policy_device(&self) -> &Device<'_, C, R> { + fn get_power_policy_device(&self) -> &Device<'_, D, R> { self } } diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 9dd33f044..77ed8be16 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -9,7 +9,6 @@ use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; use crate::sync::Lockable; use embassy_futures::select::select_slice; -use embassy_sync::once_lock::OnceLock; use super::charger::ChargerResponse; use super::device::{self}; @@ -85,7 +84,7 @@ impl Context { Self { devices: intrusive_list::IntrusiveList::new(), chargers: intrusive_list::IntrusiveList::new(), - broadcaster: broadcaster::Immediate::default(), + broadcaster: broadcaster::Immediate::new(), } } } @@ -96,14 +95,14 @@ static CONTEXT: Context = Context::new(); pub fn init() {} /// Register a device with the power policy service -pub async fn register_device + 'static>( - device: &'static impl device::DeviceContainer, +pub fn register_device + 'static>( + device: &'static impl device::DeviceContainer, ) -> Result<(), intrusive_list::Error> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { let device = device.get_power_policy_device(); - if get_device::(device.id()).await.is_some() { + if get_device::(device.id()).is_some() { return Err(intrusive_list::Error::NodeAlreadyInList); } @@ -121,14 +120,14 @@ pub fn register_charger(device: &'static impl charger::ChargerContainer) -> Resu } /// Find a device by its ID -async fn get_device + 'static>( +fn get_device + 'static>( id: DeviceId, -) -> Option<&'static device::Device<'static, C, R>> +) -> Option<&'static device::Device<'static, D, R>> where - C::Inner: DeviceTrait, + D::Inner: DeviceTrait, { - for device in &CONTEXT.get().await.devices { - if let Some(data) = device.data::>() { + for device in &CONTEXT.devices { + if let Some(data) = device.data::>() { if data.id() == id { return Some(data); } @@ -141,9 +140,12 @@ where } /// Returns the total amount of power that is being supplied to external devices -pub async fn compute_total_provider_power_mw() -> u32 { +pub async fn compute_total_provider_power_mw + 'static>() -> u32 +where + D::Inner: DeviceTrait, +{ let mut total = 0; - for device in CONTEXT.devices.iter_only::() { + for device in CONTEXT.devices.iter_only::>() { if let Some(capability) = device.provider_capability().await { if device.is_provider().await { total += capability.capability.max_power_mw(); @@ -233,8 +235,8 @@ where } /// Get a device by its ID - pub async fn get_device(&self, id: DeviceId) -> Result<&'static device::Device<'static, D, R>, Error> { - get_device(id).await.ok_or(Error::InvalidDevice) + pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device<'static, D, R>, Error> { + get_device(id).ok_or(Error::InvalidDevice) } /// Provides access to the device list @@ -260,7 +262,7 @@ where /// Get the next pending device event pub async fn wait_request(&self) -> Request { let mut futures = heapless::Vec::<_, 16>::new(); - for device in self.devices().await.iter_only::>() { + for device in self.devices().iter_only::>() { // TODO: check this at compile time let _ = futures.push(async { device.receiver.lock().await.wait_next().await }); } @@ -268,7 +270,6 @@ where let (event, index) = select_slice(pin!(&mut futures)).await; let device = self .devices() - .await .iter_only::>() .nth(index) .unwrap(); diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index c886e2965..830006686 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -41,7 +41,7 @@ where let mut best_consumer = None; let current_consumer_id = state.current_consumer_state.map(|f| f.device_id); - for node in self.context.devices().await { + for node in self.context.devices() { let device = node.data::>().ok_or(Error::InvalidDevice)?; let consumer_capability = device.consumer_capability().await; @@ -95,7 +95,7 @@ where async fn update_unconstrained_state(&self, state: &mut InternalState) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for node in self.context.devices().await { + for node in self.context.devices() { let device = node.data::>().ok_or(Error::InvalidDevice)?; if let Some(capability) = device.consumer_capability().await { if capability.flags.unconstrained_power() { @@ -201,7 +201,7 @@ where } state.current_consumer_state = None; - let consumer_device = self.context.get_device(current_consumer.device_id).await?; + let consumer_device = self.context.get_device(current_consumer.device_id)?; if matches!(consumer_device.state.lock().await.state(), State::ConnectedConsumer(_)) { // Disconnect the current consumer if needed info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); @@ -231,7 +231,7 @@ where } info!("Device {}, connecting new consumer", new_consumer.device_id.0); - let device = self.context.get_device(new_consumer.device_id).await?; + let device = self.context.get_device(new_consumer.device_id)?; let device_state = device.state.lock().await.state(); if matches!(device_state, device::State::Idle | device::State::ConnectedConsumer(_)) { diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index e4bc107f1..07fb64be5 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -37,7 +37,7 @@ where /// Attempt to connect the requester as a provider pub(super) async fn connect_provider(&self, requester_id: DeviceId) -> Result<(), Error> { trace!("Device{}: Attempting to connect as provider", requester_id.0); - let requester = self.context.get_device(requester_id).await?; + let requester = self.context.get_device(requester_id)?; let requested_power_capability = match requester.requested_provider_capability().await { Some(cap) => cap, // Requester is no longer requesting power @@ -50,7 +50,7 @@ where let mut total_power_mw = 0; // Determine total requested power draw - for device in self.context.devices().await.iter_only::>() { + for device in self.context.devices().iter_only::>() { let target_provider_cap = if device.id() == requester_id { // Use the requester's requested power capability // this handles both new connections and upgrade requests @@ -89,10 +89,14 @@ where } }; - let device = self.context.get_device(requester_id).await?; + let mut policy_state = self.state.lock().await; + let device = self.context.get_device(requester_id)?; let state = device.state.lock().await.state(); if matches!(state, device::State::Idle | device::State::ConnectedProvider(_)) { - device.device.lock().await.connect_provider(target_power).await + device.device.lock().await.connect_provider(target_power).await?; + self.post_provider_connected(&mut policy_state, requester_id, target_power) + .await; + Ok(()) } else { error!( "Device{}: Cannot provide, device is in state {:#?}", diff --git a/power-policy-service/src/task.rs b/power-policy-service/src/task.rs index 916b2909f..bc1469a78 100644 --- a/power-policy-service/src/task.rs +++ b/power-policy-service/src/task.rs @@ -1,7 +1,12 @@ -use embassy_sync::once_lock::OnceLock; -use embedded_services::{comms, error, info}; +use embedded_services::{ + comms, error, + event::Receiver, + info, + power::policy::{device::DeviceTrait, policy::RequestData}, + sync::Lockable, +}; -use crate::{PowerPolicy, config}; +use crate::{PowerPolicy}; #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -12,15 +17,13 @@ pub enum InitError { RegistrationFailed, } -pub async fn task(config: config::Config) -> Result { +pub async fn task + 'static>( + policy: &'static PowerPolicy, +) -> Result +where + D::Inner: DeviceTrait, +{ info!("Starting power policy task"); - static POLICY: OnceLock = OnceLock::new(); - let policy = if let Some(policy) = PowerPolicy::create(config) { - POLICY.get_or_init(|| policy) - } else { - error!("Power policy service already initialized"); - return Err(InitError::AlreadyInitialized); - }; if comms::register_endpoint(policy, &policy.tp).await.is_err() { error!("Failed to register power policy endpoint"); diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 5cb453eaa..b71630082 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -41,7 +41,10 @@ //! let _backing = referenced.create_backing().unwrap(); //! } //! ``` -use core::cell::{RefCell, RefMut}; +use core::{ + array::from_fn, + cell::{RefCell, RefMut}, +}; use embassy_sync::{ blocking_mutex::raw::RawMutex, @@ -115,18 +118,25 @@ struct InternalState<'a, const N: usize, S: event::Sender> impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { fn try_new(storage: &'a Storage, power_events: [S; N]) -> Option { - Some(Self { - controller_state: ControllerState::default(), - port_states: from_fn(|i| PortState { + let port_states = storage.pd_alerts.each_ref().map(|pd_alert| { + Some(PortState { status: PortStatus::new(), sw_status_event: PortStatusChanged::none(), sink_ready_deadline: None, pending_events: PortEvent::none(), - pd_alerts: ( - storage.pd_alerts[i].dyn_immediate_publisher(), - storage.pd_alerts[i].dyn_subscriber()?, - ), - }), + pd_alerts: (pd_alert.dyn_immediate_publisher(), pd_alert.dyn_subscriber().ok()?), + }) + }); + + if port_states.iter().any(|s| s.is_none()) { + return None; + } + + Some(Self { + controller_state: ControllerState::default(), + // Panic safety: All array elements checked above + #[allow(clippy::unwrap_used)] + port_states: port_states.map(|s| s.unwrap()), port_power: power_events.map(|sender| PortPower { sender, state: Default::default(), @@ -245,18 +255,18 @@ pub struct IntermediateStorage<'a, const N: usize, M: RawMutex> { } impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { + // Panic Safety: size of everything is fixed at compile time to N fn from_storage(storage: &'a Storage) -> Self { let mut power_proxy_devices = heapless::Vec::<_, N>::new(); let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); for power_proxy_channel in storage.power_proxy_channels.iter() { - // Safe because everything has a length of N power_proxy_devices .push(Mutex::new(power_proxy_channel.get_device())) - .expect("Failed to insert power proxy device"); + .unwrap_or_else(|_| panic!("Failed to insert power proxy device")); power_proxy_receivers .push(Mutex::new(power_proxy_channel.get_receiver())) - .expect("Failed to insert power proxy receiver"); + .unwrap_or_else(|_| panic!("Failed to insert power proxy receiver")); } Self { @@ -264,22 +274,22 @@ impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { // Safe because both have N elements power_proxy_devices: power_proxy_devices .into_array() - .expect("Failed to create power devices"), + .unwrap_or_else(|_| panic!("Failed to create power devices")), power_proxy_receivers: power_proxy_receivers .into_array() - .expect("Failed to create power receivers"), + .unwrap_or_else(|_| panic!("Failed to create power receivers")), } } /// Create referenced storage from this intermediate storage - pub fn create_referenced<'b, S: event::Sender, R: event::Receiver>( + pub fn try_create_referenced<'b, S: event::Sender, R: event::Receiver>( &'b self, policy_args: [(DeviceId, S, R); N], - ) -> ReferencedStorage<'b, N, M, S, R> + ) -> Option> where 'b: 'a, { - ReferencedStorage::from_intermediate(self, policy_args) + ReferencedStorage::try_from_intermediate(self, policy_args) } } @@ -304,7 +314,10 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: ReferencedStorage<'a, N, M, S, R> { /// Create a new referenced storage from the given intermediate storage - fn from_intermediate(intermediate: &'a IntermediateStorage<'a, N, M>, policy_args: [(DeviceId, S, R); N]) -> Self { + fn try_from_intermediate( + intermediate: &'a IntermediateStorage<'a, N, M>, + policy_args: [(DeviceId, S, R); N], + ) -> Option { let mut power_senders = heapless::Vec::<_, N>::new(); let mut power_devices = heapless::Vec::<_, N>::new(); @@ -321,15 +334,15 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: .unwrap_or_else(|_| panic!("Failed to insert power device")); } - Self { + Some(Self { intermediate, - state: RefCell::new(InternalState::new( + state: RefCell::new(InternalState::try_new( intermediate.storage, // Safe because both have N elements power_senders .into_array() .unwrap_or_else(|_| panic!("Failed to create power events")), - )), + )?), pd_controller: embedded_services::type_c::controller::Device::new( intermediate.storage.controller_id, intermediate.storage.pd_ports.as_slice(), diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index 9ec63bb85..581975019 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -33,13 +33,13 @@ impl FwUpdateState { impl< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, -> ControllerWrapper<'device, M, C, S, R, V> +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + ::Inner: Controller, { /// Create a new invalid FW version response fn create_invalid_fw_version_response(&self) -> InternalResponseData { @@ -52,7 +52,7 @@ where } /// Process a GetFwVersion command - async fn process_get_fw_version(&self, target: &mut C::Inner) -> InternalResponseData { + async fn process_get_fw_version(&self, target: &mut D::Inner) -> InternalResponseData { let version = match target.get_active_fw_version().await { Ok(v) => v, Err(Error::Pd(e)) => { @@ -83,7 +83,7 @@ where } /// Process a GiveOffer command - async fn process_give_offer(&self, target: &mut C::Inner, offer: &FwUpdateOffer) -> InternalResponseData { + async fn process_give_offer(&self, target: &mut D::Inner, offer: &FwUpdateOffer) -> InternalResponseData { if offer.component_info.component_id != self.registration.cfu_device.component_id() { return Self::create_offer_rejection(); } @@ -105,7 +105,7 @@ where async fn process_abort_update( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, ) -> InternalResponseData { // abort the update process @@ -130,7 +130,7 @@ where /// Process a GiveContent command async fn process_give_content( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, content: &FwUpdateContentCommand, ) -> InternalResponseData { @@ -233,7 +233,7 @@ where } /// Process a CFU tick - pub async fn process_cfu_tick(&self, controller: &mut C::Inner, state: &mut dyn DynPortState<'_, S>) { + pub async fn process_cfu_tick(&self, controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>) { match state.controller_state_mut().fw_update_state { FwUpdateState::Idle => { // No FW update in progress, nothing to do @@ -275,7 +275,7 @@ where /// Process a CFU command pub async fn process_cfu_command( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, command: &RequestData, ) -> InternalResponseData { diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index d02fd561d..f25012976 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -7,20 +7,20 @@ use embedded_usb_pd::{Error, LocalPortId}; impl< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, -> ControllerWrapper<'device, M, C, S, R, V> +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + ::Inner: Controller, { /// Process a DisplayPort status update by retrieving the current DP status from the `controller` for the appropriate `port`. pub(super) async fn process_dp_status_update( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, port: LocalPortId, - ) -> Result::BusError>> { + ) -> Result::BusError>> { trace!("Processing DP status update event on port {}", port.0); let status = controller.get_dp_status(port).await?; diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index c55c81d32..4c725e516 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -74,14 +74,14 @@ pub const MAX_SUPPORTED_PORTS: usize = 2; pub struct ControllerWrapper< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, > where - ::Inner: Controller, + ::Inner: Controller, { - controller: &'device C, + controller: &'device D, /// Trait object for validating firmware versions fw_version_validator: V, /// FW update ticker used to check for timeouts and recovery attempts @@ -101,17 +101,17 @@ pub struct ControllerWrapper< impl< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, -> ControllerWrapper<'device, M, C, S, R, V> +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + ::Inner: Controller, { /// Create a new controller wrapper, returns `None` if the backing storage is already in use pub fn try_new( - controller: &'device C, + controller: &'device D, config: config::Config, storage: &'device backing::ReferencedStorage<'device, N, M, S, R>, fw_version_validator: V, @@ -146,7 +146,7 @@ where } /// Synchronize the state between the controller and the internal state - pub async fn sync_state(&self) -> Result<(), Error<::BusError>> { + pub async fn sync_state(&self) -> Result<(), Error<::BusError>> { let mut controller = self.controller.lock().await; let mut state = self.state.lock().await; self.sync_state_internal(&mut controller, state.deref_mut().deref_mut()) @@ -156,9 +156,9 @@ where /// Synchronize the state between the controller and the internal state async fn sync_state_internal( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { // Sync the controller state with the PD controller for (i, port_state) in state.port_states_mut().iter_mut().enumerate() { let mut status_changed = port_state.sw_status_event; @@ -193,11 +193,11 @@ where /// Handle a plug event async fn process_plug_event( &self, - _controller: &mut C::Inner, + _controller: &mut D::Inner, power: &mut PortPower, port: LocalPortId, status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { if port.0 as usize >= self.registration.num_ports() { error!("Invalid port {}", port.0); return PdError::InvalidPort.into(); @@ -220,11 +220,11 @@ where /// Process port status changed events async fn process_port_status_changed<'b>( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, local_port_id: LocalPortId, status_event: PortStatusChanged, - ) -> Result, Error<::BusError>> { + ) -> Result, Error<::BusError>> { let global_port_id = self .registration .pd_controller @@ -275,7 +275,7 @@ where local_port: LocalPortId, status_event: PortStatusChanged, status: PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { let port_index = local_port.0 as usize; let global_port_id = self .registration @@ -312,7 +312,7 @@ where state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, alert: Ado, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { let port_index = local_port.0 as usize; let global_port_id = self .registration @@ -346,8 +346,8 @@ where /// DROP SAFETY: No state that needs to be restored async fn wait_port_pending( &self, - controller: &mut C::Inner, - ) -> Result::BusError>> { + controller: &mut D::Inner, + ) -> Result::BusError>> { if self.state.lock().await.controller_state().fw_update_state.in_progress() { // Don't process events while firmware update is in progress debug!("Firmware update in progress, ignoring port events"); @@ -364,7 +364,7 @@ where // DROP SAFETY: Safe as long as `wait_port_event` is drop safe match select(controller.wait_port_event(), async { self.sw_status_event.wait().await; - Ok::<_, Error<::BusError>>(()) + Ok::<_, Error<::BusError>>(()) }) .await { @@ -377,7 +377,7 @@ where } /// Wait for the next event - pub async fn wait_next(&self) -> Result, Error<::BusError>> { + pub async fn wait_next(&self) -> Result, Error<::BusError>> { // This loop is to ensure that if we finish streaming events we go back to waiting for the next port event loop { let event = { @@ -396,7 +396,7 @@ where Either5::First(stream) => { let mut stream = stream?; if let Some((port_index, event)) = stream - .next::::BusError>, _, _>(async |port_index| { + .next::::BusError>, _, _>(async |port_index| { // Combine the event read from the controller with any software generated events // Acquire the locks first to centralize the awaits here let mut controller = self.controller.lock().await; @@ -468,10 +468,10 @@ where /// Process a port notification async fn process_port_notification<'b>( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, port: LocalPortId, notification: PortNotificationSingle, - ) -> Result, Error<::BusError>> { + ) -> Result, Error<::BusError>> { match notification { PortNotificationSingle::Alert => { let ado = controller.get_pd_alert(port).await?; @@ -503,7 +503,7 @@ where pub async fn process_event<'b>( &self, event: Event<'b>, - ) -> Result, Error<::BusError>> { + ) -> Result, Error<::BusError>> { let mut controller = self.controller.lock().await; let mut state = self.state.lock().await; match event { @@ -545,7 +545,7 @@ where } /// Event loop finalize - pub async fn finalize<'b>(&self, output: Output<'b>) -> Result<(), Error<::BusError>> { + pub async fn finalize<'b>(&self, output: Output<'b>) -> Result<(), Error<::BusError>> { let mut state = self.state.lock().await; match output { @@ -595,13 +595,13 @@ where pub async fn process_and_finalize_event<'b>( &self, event: Event<'b>, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { let output = self.process_event(event).await?; self.finalize(output).await } /// Combined processing function - pub async fn process_next_event(&self) -> Result<(), Error<::BusError>> { + pub async fn process_next_event(&self) -> Result<(), Error<::BusError>> { let event = self.wait_next().await?; self.process_and_finalize_event(event).await } diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index 64d7c37b1..8eb62d697 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -13,13 +13,13 @@ use super::*; impl< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, -> ControllerWrapper<'device, M, C, S, R, V> +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + ::Inner: Controller, { async fn process_get_pd_alert( &self, @@ -126,7 +126,7 @@ where /// Process a request to set the maximum sink voltage for a port async fn process_set_max_sink_voltage( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, voltage_mv: Option, @@ -163,7 +163,7 @@ where async fn process_get_port_status( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, cached: Cached, @@ -190,7 +190,7 @@ where /// Handle a port command async fn process_port_command( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, command: &controller::PortCommand, ) -> Response<'static> { @@ -399,7 +399,7 @@ where async fn process_controller_command( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, command: &controller::InternalCommandData, ) -> Response<'static> { @@ -435,7 +435,7 @@ where /// Handle a PD controller command pub(super) async fn process_pd_command( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, command: &controller::Command, ) -> Response<'static> { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 46a90a314..80ba7c45f 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -7,31 +7,34 @@ use embedded_services::{ power::policy::{ ConsumerPowerCapability, ProviderPowerCapability, device::{CommandData, InternalResponseData, ResponseData}, + flags::PsuType, }, }; use embedded_services::power::policy::Error as PowerError; use embedded_services::power::policy::device::CommandData as PowerCommand; +use crate::wrapper::config::UnconstrainedSink; + use super::*; impl< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, -> ControllerWrapper<'device, M, C, S, R, V> +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + ::Inner: Controller, { /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, power: &mut PortPower, status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); let current_state = power.state.state(); @@ -63,7 +66,7 @@ where &self, power: &mut PortPower, status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); let current_state = power.state.state(); @@ -84,9 +87,9 @@ where async fn process_disconnect( &self, port: LocalPortId, - controller: &mut C::Inner, + controller: &mut D::Inner, power: &mut PortPower, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { if power.state.state().kind() == StateKind::ConnectedConsumer { info!("Port{}: Disconnect from ConnectedConsumer", port.0); if controller.enable_sink_path(port, false).await.is_err() { @@ -110,8 +113,8 @@ where &self, port: LocalPortId, capability: ProviderPowerCapability, - _controller: &mut C::Inner, - ) -> Result<(), Error<::BusError>> { + _controller: &mut D::Inner, + ) -> Result<(), Error<::BusError>> { info!("Port{}: Connect as provider: {:#?}", port.0, capability); // TODO: double check explicit contract handling Ok(()) @@ -145,7 +148,7 @@ where /// Returns no error because this is a top-level function pub(super) async fn process_power_command( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>, port: LocalPortId, command: &CommandData, @@ -174,11 +177,7 @@ where } } PowerCommand::ConnectAsProvider(capability) => { - if self - .process_connect_as_provider(port, *capability, controller) - .await - .is_err() - { + if self.process_connect_as_provider(port, *capability, controller).is_err() { error!("Error processing connect provider"); return Err(PowerError::Failed); } diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 63f2e3d6b..844d8bfd1 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -18,21 +18,21 @@ use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; impl< 'device, M: RawMutex, - C: Lockable, + D: Lockable, S: event::Sender, R: event::Receiver, V: FwOfferValidator, -> ControllerWrapper<'device, M, C, S, R, V> +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + ::Inner: Controller, { /// Process a VDM event by retrieving the relevant VDM data from the `controller` for the appropriate `port`. pub(super) async fn process_vdm_event( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, port: LocalPortId, event: VdmNotification, - ) -> Result::BusError>> { + ) -> Result::BusError>> { trace!("Processing VDM event: {:?} on port {}", event, port.0); let kind = match event { VdmNotification::Entered => OutputKind::Entered(controller.get_other_vdm(port).await?), From 0a0c80f237e63880aa90843be171160f3884b71f Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 21 Jan 2026 15:17:20 -0800 Subject: [PATCH 15/22] Fixes after rebase --- embedded-service/src/power/policy/device.rs | 7 ++- power-policy-service/src/consumer.rs | 37 +++++-------- power-policy-service/src/provider.rs | 40 ++++++-------- type-c-service/src/wrapper/mod.rs | 7 +-- type-c-service/src/wrapper/power.rs | 58 +++++---------------- 5 files changed, 52 insertions(+), 97 deletions(-) diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 31e887612..22c3a501f 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -175,10 +175,13 @@ impl InternalState { /// Handle a request to connect as a provider from the policy pub fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { - let result = if self.state == State::Idle { + let result = if matches!(self.state, State::Idle | State::ConnectedProvider(_)) { Ok(()) } else { - Err(Error::InvalidState(&[StateKind::Idle], self.state.kind())) + Err(Error::InvalidState( + &[StateKind::Idle, StateKind::ConnectedProvider], + self.state.kind(), + )) }; self.state = State::ConnectedProvider(capability); result diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index 830006686..b7bd0a398 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -2,7 +2,6 @@ use core::cmp::Ordering; use embedded_services::debug; use embedded_services::power::policy::charger::Device as ChargerDevice; use embedded_services::power::policy::charger::PolicyEvent; -use embedded_services::power::policy::device::StateKind; use embedded_services::power::policy::policy::check_chargers_ready; use embedded_services::power::policy::policy::init_chargers; @@ -234,34 +233,26 @@ where let device = self.context.get_device(new_consumer.device_id)?; let device_state = device.state.lock().await.state(); - if matches!(device_state, device::State::Idle | device::State::ConnectedConsumer(_)) { + if let e @ Err(_) = device + .state + .lock() + .await + .connect_consumer(new_consumer.consumer_power_capability) + { + error!( + "Device{}: Not ready to connect consumer, state: {:#?}", + device.id().0, + device_state + ); + e + } else { device .device .lock() .await .connect_consumer(new_consumer.consumer_power_capability) .await?; - if let Err(e) = device - .state - .lock() - .await - .connect_consumer(new_consumer.consumer_power_capability) - { - // Should never happen because we checked the state above, log an error instead of a panic - error!( - "Device{}: Connect state transition failed: {:#?}", - new_consumer.device_id.0, e - ); - } - self.post_consumer_connected(state, new_consumer).await?; - Ok(()) - } else { - error!( - "Device{}: Not ready to connect consumer, state: {:#?}", - device.id().0, - device_state - ); - Err(Error::InvalidState(&[StateKind::Idle], device_state.kind())) + self.post_consumer_connected(state, new_consumer).await } } diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index 07fb64be5..04d2b0287 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -3,12 +3,7 @@ //! the system is in unlimited power state. In this mode up to [provider_unlimited](super::Config::provider_unlimited) //! is provided to each device. Above this threshold, the system is in limited power state. //! In this mode [provider_limited](super::Config::provider_limited) is provided to each device -use embedded_services::{ - debug, - event::Receiver, - power::policy::{device::StateKind, policy::RequestData}, - trace, -}; +use embedded_services::{debug, event::Receiver, power::policy::policy::RequestData, trace}; use super::*; @@ -46,7 +41,7 @@ where return Err(Error::CannotProvide(None)); } }; - let mut state = self.state.lock().await; + let mut policy_state = self.state.lock().await; let mut total_power_mw = 0; // Determine total requested power draw @@ -60,17 +55,17 @@ where device.provider_capability().await }; total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); + } - if total_power_mw > self.config.limited_power_threshold_mw { - state.current_provider_state.state = PowerState::Limited; - } else { - state.current_provider_state.state = PowerState::Unlimited; - } + if total_power_mw > self.config.limited_power_threshold_mw { + policy_state.current_provider_state.state = PowerState::Limited; + } else { + policy_state.current_provider_state.state = PowerState::Unlimited; } - debug!("New power state: {:?}", state.current_provider_state.state); + debug!("New power state: {:?}", policy_state.current_provider_state.state); - let target_power = match state.current_provider_state.state { + let target_power = match policy_state.current_provider_state.state { PowerState::Limited => ProviderPowerCapability { capability: self.config.provider_limited, flags: requested_power_capability.flags, @@ -89,21 +84,20 @@ where } }; - let mut policy_state = self.state.lock().await; let device = self.context.get_device(requester_id)?; - let state = device.state.lock().await.state(); - if matches!(state, device::State::Idle | device::State::ConnectedProvider(_)) { - device.device.lock().await.connect_provider(target_power).await?; - self.post_provider_connected(&mut policy_state, requester_id, target_power) - .await; - Ok(()) - } else { + if let e @ Err(_) = device.state.lock().await.connect_provider(target_power) { + let state = device.state.lock().await.state(); error!( "Device{}: Cannot provide, device is in state {:#?}", device.id().0, state ); - Err(Error::InvalidState(&[StateKind::Idle], state.kind())) + e + } else { + device.device.lock().await.connect_provider(target_power).await?; + self.post_provider_connected(&mut policy_state, requester_id, target_power) + .await; + Ok(()) } } diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 4c725e516..4caf4bd75 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -28,7 +28,6 @@ use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::event; -use embedded_services::power::policy::device::StateKind; use embedded_services::power::policy::policy; use embedded_services::sync::Lockable; use embedded_services::type_c::controller::{self, Controller, PortStatus}; @@ -206,12 +205,10 @@ where info!("Plug event"); if status.is_connected() { info!("Plug inserted"); - if let Err(e) = power.state.attach() { - warn!("Power device not in detached state, recovering: {:#?}", e); - } + power.sender.send(policy::RequestData::Attached).await; } else { info!("Plug removed"); - power.state.detach(); + power.sender.send(policy::RequestData::Detached).await; } Ok(()) diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 80ba7c45f..d1a7a6f32 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -36,10 +36,6 @@ where status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); - - let current_state = power.state.state(); - info!("current power state: {:?}", current_state); - let available_sink_contract = status.available_sink_contract.map(|c| { let mut c: ConsumerPowerCapability = c.into(); let unconstrained = match self.config.unconstrained_sink { @@ -52,12 +48,10 @@ where c }); - if let Err(e) = power.state.update_consumer_power_capability(available_sink_contract) { - warn!( - "Device was not in correct state for consumer contract, recovered: {:#?}", - e - ); - } + power + .sender + .send(policy::RequestData::UpdatedConsumerCapability(available_sink_contract)) + .await; Ok(()) } @@ -68,18 +62,12 @@ where status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); - - let current_state = power.state.state(); - info!("current power state: {:?}", current_state); - - if let Err(e) = power.state.update_requested_provider_power_capability( - status.available_sink_contract.map(ProviderPowerCapability::from), - ) { - warn!( - "Device was not in correct state for provider contract, recovered: {:#?}", - e - ); - } + power + .sender + .send(policy::RequestData::RequestedProviderCapability( + status.available_source_contract.map(ProviderPowerCapability::from), + )) + .await; Ok(()) } @@ -88,23 +76,11 @@ where &self, port: LocalPortId, controller: &mut D::Inner, - power: &mut PortPower, ) -> Result<(), Error<::BusError>> { - if power.state.state().kind() == StateKind::ConnectedConsumer { - info!("Port{}: Disconnect from ConnectedConsumer", port.0); - if controller.enable_sink_path(port, false).await.is_err() { - error!("Error disabling sink path"); - return PdError::Failed.into(); - } - - if let Err(e) = power.state.disconnect(false) { - warn!( - "{:?}: Device was not in correct state for disconnect, recovered: {:#?}", - port, e - ); - } + if controller.enable_sink_path(port, false).await.is_err() { + error!("Error disabling sink path"); + return PdError::Failed.into(); } - Ok(()) } @@ -159,12 +135,6 @@ where return Err(PowerError::Busy); } - let power = state.port_power_mut().get_mut(port.0 as usize); - if power.is_none() { - return Err(PowerError::InvalidDevice); - } - - let power = power.unwrap(); match command { PowerCommand::ConnectAsConsumer(capability) => { info!( @@ -183,7 +153,7 @@ where } } PowerCommand::Disconnect => { - if self.process_disconnect(port, controller, power).await.is_err() { + if self.process_disconnect(port, controller).await.is_err() { error!("Error processing disconnect"); return Err(PowerError::Failed); } From 1a7477d86f00d6c6e81043e3c2dd11e58da83ec9 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 21 Jan 2026 15:17:56 -0800 Subject: [PATCH 16/22] Update examples --- examples/std/src/bin/power_policy.rs | 24 +++-- examples/std/src/bin/type_c/basic.rs | 15 +-- examples/std/src/bin/type_c/external.rs | 27 ++++- examples/std/src/bin/type_c/service.rs | 67 ++++++++++++- examples/std/src/bin/type_c/ucsi.rs | 98 ++++++++++++++++++- examples/std/src/bin/type_c/unconstrained.rs | 41 ++++---- .../std/src/lib/type_c/mock_controller.rs | 20 ++-- 7 files changed, 234 insertions(+), 58 deletions(-) diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index e6efb6781..25a3af4c1 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -67,14 +67,17 @@ impl<'a> ExampleDevice<'a> { impl DeviceTrait for ExampleDevice<'_> { async fn disconnect(&mut self) -> Result<(), Error> { + debug!("ExampleDevice disconnect"); Ok(()) } - async fn connect_provider(&mut self, _capability: ProviderPowerCapability) -> Result<(), Error> { + async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + debug!("ExampleDevice connect_provider with {:?}", capability); Ok(()) } - async fn connect_consumer(&mut self, _capability: ConsumerPowerCapability) -> Result<(), Error> { + async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { + debug!("ExampleDevice connect_consumer with {:?}", capability); Ok(()) } } @@ -100,7 +103,7 @@ async fn run(_spawner: Spawner) { device0, device0_event_channel.dyn_receiver(), )); - policy::register_device(device0_registration).await.unwrap(); + policy::register_device(device0_registration).unwrap(); info!("Creating device 1"); static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); @@ -119,7 +122,7 @@ async fn run(_spawner: Spawner) { device1, device1_event_channel.dyn_receiver(), )); - policy::register_device(device1_registration).await.unwrap(); + policy::register_device(device1_registration).unwrap(); // Plug in device 0, should become current consumer info!("Connecting device 0"); @@ -195,7 +198,16 @@ async fn run(_spawner: Spawner) { } Timer::after_millis(PER_CALL_DELAY_MS).await; - // Switch to provider on device0 + // Switch device 0 to provider + info!("Device 0 switch to provider"); + { + let mut dev0 = device0.lock().await; + dev0.simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Attach device 1 and request provider info!("Device 1 attach and requesting provider"); { let mut dev1 = device1.lock().await; @@ -205,7 +217,7 @@ async fn run(_spawner: Spawner) { } Timer::after_millis(PER_CALL_DELAY_MS).await; - // Provider upgrade should fail because device 0 is already connected + // Provider upgrade should fail because device 0 is already connected at high power info!("Device 1 attempting provider upgrade"); { let mut dev1 = device1.lock().await; diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 1f8441fe5..c9befe733 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -11,7 +11,6 @@ use static_cell::StaticCell; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); mod test_controller { use embedded_services::type_c::controller::{ControllerStatus, PortStatus}; @@ -21,7 +20,6 @@ mod test_controller { pub struct Controller<'a> { pub controller: controller::Device<'a>, - pub power_policy: power::policy::device::Device, } impl controller::DeviceContainer for Controller<'_> { @@ -30,17 +28,10 @@ mod test_controller { } } - impl power::policy::device::DeviceContainer for Controller<'_> { - fn get_power_policy_device(&self) -> &power::policy::device::Device { - &self.power_policy - } - } - impl<'a> Controller<'a> { - pub fn new(id: ControllerId, power_id: power::policy::DeviceId, ports: &'a [GlobalPortId]) -> Self { + pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { Self { controller: controller::Device::new(id, ports), - power_policy: power::policy::device::Device::new(power_id), } } @@ -129,8 +120,8 @@ async fn controller_task(controller_list: &'static IntrusiveList) { static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; - let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, POWER0_ID, &PORTS)); - controller::register_controller(controller_list, controller).unwrap(); + let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, &PORTS)); + controller::register_controller(controller).unwrap(); loop { controller.process().await; diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 36a27b588..21e4cec41 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -1,8 +1,10 @@ //! Low-level example of external messaging with a simple type-C service use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::power::policy::policy; use embedded_services::{ GlobalRawMutex, IntrusiveList, power, type_c::{Cached, ControllerId, controller::Context}, @@ -133,13 +135,30 @@ fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper< controller_context, CONTROLLER0_ID, 0, // CFU component ID (unused) - [(PORT0_ID, POWER0_ID)], + [PORT0_ID], )); - static REFERENCED: StaticCell> = + + static INTERMEDIATE: StaticCell> = StaticCell::new(); + let intermediate = INTERMEDIATE.init(backing_storage.create_intermediate()); + + static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + let policy_channel = POLICY_CHANNEL.init(Channel::new()); + + let policy_sender = policy_channel.dyn_sender(); + let policy_receiver = policy_channel.dyn_receiver(); + + static REFERENCED: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); let referenced = REFERENCED.init( - backing_storage - .create_referenced() + intermediate + .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) .expect("Failed to create referenced storage"), ); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 5b7d3373a..7bc4a89f4 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,8 +1,10 @@ use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::power::policy::policy; use embedded_services::power::{self}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; @@ -11,13 +13,16 @@ use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; use type_c_service::service::Service; use type_c_service::service::config::Config; +use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; use type_c_service::wrapper::message::*; +use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -60,10 +65,54 @@ mod debug { } #[embassy_executor::task] -async fn controller_task( - wrapper: &'static Wrapper<'static>, - controller: &'static Mutex>, -) { +async fn controller_task(state: &'static mock_controller::ControllerState) { + static STORAGE: StaticCell> = StaticCell::new(); + let storage = STORAGE.init(Storage::new( + CONTROLLER0_ID, + 0, // CFU component ID (unused) + [(PORT0_ID)], + )); + + static INTERMEDIATE: StaticCell> = + StaticCell::new(); + let intermediate = INTERMEDIATE.init(storage.create_intermediate()); + + static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + let policy_channel = POLICY_CHANNEL.init(Channel::new()); + + let policy_sender = policy_channel.dyn_sender(); + let policy_receiver = policy_channel.dyn_receiver(); + + static REFERENCED: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) + .expect("Failed to create referenced storage"), + ); + + static CONTROLLER: StaticCell> = StaticCell::new(); + let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); + + static WRAPPER: StaticCell = StaticCell::new(); + let wrapper = WRAPPER.init( + mock_controller::Wrapper::try_new( + controller, + Default::default(), + referenced, + crate::mock_controller::Validator, + ) + .expect("Failed to create wrapper"), + ); + + wrapper.register().await.unwrap(); + controller.lock().await.custom_function(); loop { @@ -130,7 +179,15 @@ async fn task( #[embassy_executor::task] async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) + static POWER_POLICY: static_cell::StaticCell< + PowerPolicy>, DynamicReceiver<'static, policy::RequestData>>, + > = static_cell::StaticCell::new(); + let power_policy = + POWER_POLICY.init(PowerPolicy::create(Default::default()).expect("Failed to create power policy")); + + // TODO: remove once power policy task accepts context + Timer::after_millis(100).await; + power_policy_service::task::task(power_policy) .await .expect("Failed to start power policy service task"); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index bd8a617b5..5cdd1dd77 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,9 +1,14 @@ use crate::mock_controller::Wrapper; use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; +use embassy_time::Timer; +use embedded_services::GlobalRawMutex; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; +use embedded_services::power::policy::PowerCapability; +use embedded_services::power::policy::policy; use embedded_services::power::policy::{self, PowerCapability}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; @@ -15,24 +20,99 @@ use embedded_usb_pd::ucsi::ppm::get_capability::ResponseData as UcsiCapabilities use embedded_usb_pd::ucsi::ppm::set_notification_enable::NotificationEnable; use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::Storage; +use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 2; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: policy::DeviceId = policy::DeviceId(0); +const POWER0_ID: embedded_services::power::policy::DeviceId = embedded_services::power::policy::DeviceId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER1_ID: policy::DeviceId = policy::DeviceId(1); +const POWER1_ID: embedded_services::power::policy::DeviceId = embedded_services::power::policy::DeviceId(1); const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; #[embassy_executor::task] -async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { +async fn opm_task(spawner: Spawner) { + static STORAGE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + + static INTERMEDIATE0: StaticCell> = + StaticCell::new(); + let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static REFERENCED0: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced0 = REFERENCED0.init( + intermediate0 + .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) + .expect("Failed to create referenced storage"), + ); + + static STATE0: StaticCell = StaticCell::new(); + let state0 = STATE0.init(mock_controller::ControllerState::new()); + static CONTROLLER0: StaticCell> = StaticCell::new(); + let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); + static WRAPPER0: StaticCell = StaticCell::new(); + let wrapper0 = WRAPPER0.init( + mock_controller::Wrapper::try_new(controller0, Default::default(), referenced0, mock_controller::Validator) + .expect("Failed to create wrapper"), + ); + spawner.must_spawn(wrapper_task(wrapper0)); + + static STORAGE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new(CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + static INTERMEDIATE1: StaticCell> = + StaticCell::new(); + let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED1: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced1 = REFERENCED1.init( + intermediate1 + .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) + .expect("Failed to create referenced storage"), + ); + + static STATE1: StaticCell = StaticCell::new(); + let state1 = STATE1.init(mock_controller::ControllerState::new()); + static CONTROLLER1: StaticCell> = StaticCell::new(); + let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); + static WRAPPER1: StaticCell = StaticCell::new(); + let wrapper1 = WRAPPER1.init( + mock_controller::Wrapper::try_new(controller1, Default::default(), referenced1, mock_controller::Validator) + .expect("Failed to create wrapper"), + ); + spawner.must_spawn(wrapper_task(wrapper1)); + const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, current_ma: 5000, @@ -172,7 +252,15 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) + static POWER_POLICY: static_cell::StaticCell< + PowerPolicy>, DynamicReceiver<'static, policy::RequestData>>, + > = static_cell::StaticCell::new(); + let power_policy = + POWER_POLICY.init(PowerPolicy::create(Default::default()).expect("Failed to create power policy")); + + // TODO: remove once power policy task accepts context + Timer::after_millis(100).await; + power_policy_service::task::task(power_policy) .await .expect("Failed to start power policy service task"); } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index f5cc1d764..43bd37a93 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -14,6 +14,7 @@ use embedded_services::type_c::controller::Context; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -22,6 +23,7 @@ use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, S use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; const NUM_PD_CONTROLLERS: usize = 3; +use type_c_service::wrapper::proxy::PowerProxyDevice; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -166,11 +168,11 @@ fn main() { channel::DynamicReceiver, >, > = StaticCell::new(); - let referenced0 = REFERENCED0.init(intermediate0.create_referenced([( - POWER0_ID, - channel0.dyn_sender(), - channel0.dyn_receiver(), - )])); + let referenced0 = REFERENCED0.init( + intermediate0 + .try_create_referenced([(POWER0_ID, channel0.dyn_sender(), channel0.dyn_receiver())]) + .expect("Failed to create referenced storage"), + ); static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); @@ -178,8 +180,13 @@ fn main() { let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); static WRAPPER0: StaticCell = StaticCell::new(); let wrapper0 = WRAPPER0.init( - mock_controller::Wrapper::try_new(controller0, referenced0, crate::mock_controller::Validator) - .expect("Failed to create wrapper"), + mock_controller::Wrapper::try_new( + controller0, + Default::default(), + referenced0, + crate::mock_controller::Validator, + ) + .expect("Failed to create wrapper"), ); static STORAGE1: StaticCell> = StaticCell::new(); @@ -196,11 +203,11 @@ fn main() { channel::DynamicReceiver, >, > = StaticCell::new(); - let referenced1 = REFERENCED1.init(intermediate1.create_referenced([( - POWER0_ID, - channel1.dyn_sender(), - channel1.dyn_receiver(), - )])); + let referenced1 = REFERENCED1.init( + intermediate1 + .try_create_referenced([(POWER1_ID, channel1.dyn_sender(), channel1.dyn_receiver())]) + .expect("Failed to create referenced storage"), + ); static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); @@ -231,11 +238,11 @@ fn main() { channel::DynamicReceiver, >, > = StaticCell::new(); - let referenced2 = REFERENCED2.init(intermediate2.create_referenced([( - POWER0_ID, - channel2.dyn_sender(), - channel2.dyn_receiver(), - )])); + let referenced2 = REFERENCED2.init( + intermediate2 + .try_create_referenced([(POWER2_ID, channel2.dyn_sender(), channel2.dyn_receiver())]) + .expect("Failed to create referenced storage"), + ); static STATE2: StaticCell = StaticCell::new(); let state2 = STATE2.init(mock_controller::ControllerState::new()); diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index fa9a59f86..a3cda41be 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -40,22 +40,24 @@ impl ControllerState { } else { ConnectionState::Attached }); + + let mut events = PortEvent::none(); match role { PowerRole::Source => { status.available_source_contract = Some(capability); status.unconstrained_power = unconstrained; + events.status.set_new_power_contract_as_provider(true); } PowerRole::Sink => { status.available_sink_contract = Some(capability); status.unconstrained_power = unconstrained; + events.status.set_new_power_contract_as_consumer(true); + events.status.set_sink_ready(true); } } *self.status.lock().await = status; - let mut events = PortEvent::none(); events.status.set_plug_inserted_or_removed(true); - events.status.set_new_power_contract_as_consumer(true); - events.status.set_sink_ready(true); self.events.signal(events); } @@ -94,13 +96,13 @@ impl Default for ControllerState { } } -pub struct Controller { - state: ControllerState, +pub struct Controller<'a> { + state: &'a ControllerState, events: PortEvent, } -impl Controller { - pub fn new(state: ControllerState) -> Self { +impl<'a> Controller<'a> { + pub fn new(state: &'a ControllerState) -> Self { Self { state, events: PortEvent::none(), @@ -113,13 +115,13 @@ impl Controller { } } -impl embedded_services::type_c::controller::Controller for Controller { +impl embedded_services::type_c::controller::Controller for Controller<'_> { type BusError = (); async fn wait_port_event(&mut self) -> Result<(), Error> { let events = self.state.events.wait().await; trace!("Port event: {events:#?}"); - self.events.set(events); + self.events = self.events.union(events); Ok(()) } From daa1cc783e97cd6aa27329b6339371beff4707d2 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 22 Jan 2026 11:46:46 -0800 Subject: [PATCH 17/22] Rebase onto upstream type-C changes --- examples/std/src/bin/type_c/basic.rs | 4 +- examples/std/src/bin/type_c/service.rs | 95 +++++--------- examples/std/src/bin/type_c/ucsi.rs | 126 +++++++------------ examples/std/src/bin/type_c/unconstrained.rs | 76 ++++++----- type-c-service/src/task.rs | 22 ++-- type-c-service/src/wrapper/backing.rs | 12 +- type-c-service/src/wrapper/mod.rs | 9 +- 7 files changed, 143 insertions(+), 201 deletions(-) diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index c9befe733..14ca5ff1d 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,8 +1,8 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; +use embedded_services::IntrusiveList; use embedded_services::type_c::{Cached, ControllerId, controller}; -use embedded_services::{IntrusiveList, power}; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; @@ -121,7 +121,7 @@ async fn controller_task(controller_list: &'static IntrusiveList) { static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, &PORTS)); - controller::register_controller(controller).unwrap(); + controller::register_controller(controller_list, controller).unwrap(); loop { controller.process().await; diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 7bc4a89f4..7c14dc5d4 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,4 +1,4 @@ -use embassy_executor::{Executor, Spawner}; +use embassy_executor::Executor; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; @@ -20,7 +20,6 @@ use std_examples::type_c::mock_controller::Wrapper; use type_c_service::service::Service; use type_c_service::service::config::Config; use type_c_service::wrapper::backing::Storage; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; use type_c_service::wrapper::message::*; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -65,54 +64,10 @@ mod debug { } #[embassy_executor::task] -async fn controller_task(state: &'static mock_controller::ControllerState) { - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - CONTROLLER0_ID, - 0, // CFU component ID (unused) - [(PORT0_ID)], - )); - - static INTERMEDIATE: StaticCell> = - StaticCell::new(); - let intermediate = INTERMEDIATE.init(storage.create_intermediate()); - - static POLICY_CHANNEL: StaticCell> = StaticCell::new(); - let policy_channel = POLICY_CHANNEL.init(Channel::new()); - - let policy_sender = policy_channel.dyn_sender(); - let policy_receiver = policy_channel.dyn_receiver(); - - static REFERENCED: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, - >, - > = StaticCell::new(); - let referenced = REFERENCED.init( - intermediate - .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) - .expect("Failed to create referenced storage"), - ); - - static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - - static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = WRAPPER.init( - mock_controller::Wrapper::try_new( - controller, - Default::default(), - referenced, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ); - - wrapper.register().await.unwrap(); - +async fn controller_task( + wrapper: &'static Wrapper<'static>, + controller: &'static Mutex>, +) { controller.lock().await.custom_function(); loop { @@ -138,12 +93,7 @@ async fn controller_task(state: &'static mock_controller::ControllerState) { } #[embassy_executor::task] -async fn task( - spawner: Spawner, - wrapper: &'static Wrapper<'static>, - controller: &'static Mutex>, - state: &'static mock_controller::ControllerState, -) { +async fn task(state: &'static mock_controller::ControllerState) { embedded_services::init().await; // Register debug accessory listener @@ -151,8 +101,6 @@ async fn task( let listener = LISTENER.get_or_init(debug::Listener::new); comms::register_endpoint(listener, &listener.tp).await.unwrap(); - info!("Starting controller task"); - spawner.must_spawn(controller_task(wrapper, controller)); // Wait for controller to be registered Timer::after_secs(1).await; @@ -226,7 +174,7 @@ async fn service_task( fn create_wrapper( context: &'static Context, ) -> ( - &'static mut Wrapper<'static>, + &'static Wrapper<'static>, &'static Mutex>, &'static mock_controller::ControllerState, ) { @@ -238,12 +186,30 @@ fn create_wrapper( context, CONTROLLER0_ID, 0, // CFU component ID (unused) - [(PORT0_ID, POWER0_ID)], + [PORT0_ID], )); - static REFERENCED: StaticCell> = StaticCell::new(); + + static INTERMEDIATE: StaticCell> = + StaticCell::new(); + let intermediate = INTERMEDIATE.init(storage.create_intermediate()); + + static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + let policy_channel = POLICY_CHANNEL.init(Channel::new()); + + let policy_sender = policy_channel.dyn_sender(); + let policy_receiver = policy_channel.dyn_receiver(); + + static REFERENCED: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); let referenced = REFERENCED.init( - storage - .create_referenced() + intermediate + .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) .expect("Failed to create referenced storage"), ); @@ -282,6 +248,7 @@ fn main() { executor.run(|spawner| { spawner.must_spawn(power_policy_service_task()); spawner.must_spawn(service_task(controller_context, controller_list, [wrapper])); - spawner.must_spawn(task(spawner, wrapper, controller, state)); + spawner.must_spawn(task(state)); + spawner.must_spawn(controller_task(wrapper, controller)); }); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 5cdd1dd77..9b3515b30 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -5,11 +5,9 @@ use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embedded_services::GlobalRawMutex; -use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; use embedded_services::power::policy::PowerCapability; use embedded_services::power::policy::policy; -use embedded_services::power::policy::{self, PowerCapability}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; use embedded_services::type_c::external::UcsiResponseResult; @@ -39,80 +37,7 @@ const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; #[embassy_executor::task] -async fn opm_task(spawner: Spawner) { - static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); - - static INTERMEDIATE0: StaticCell> = - StaticCell::new(); - let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); - - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - - static REFERENCED0: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, - >, - > = StaticCell::new(); - let referenced0 = REFERENCED0.init( - intermediate0 - .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) - .expect("Failed to create referenced storage"), - ); - - static STATE0: StaticCell = StaticCell::new(); - let state0 = STATE0.init(mock_controller::ControllerState::new()); - static CONTROLLER0: StaticCell> = StaticCell::new(); - let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); - static WRAPPER0: StaticCell = StaticCell::new(); - let wrapper0 = WRAPPER0.init( - mock_controller::Wrapper::try_new(controller0, Default::default(), referenced0, mock_controller::Validator) - .expect("Failed to create wrapper"), - ); - spawner.must_spawn(wrapper_task(wrapper0)); - - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); - static INTERMEDIATE1: StaticCell> = - StaticCell::new(); - let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); - - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - - static REFERENCED1: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, - >, - > = StaticCell::new(); - let referenced1 = REFERENCED1.init( - intermediate1 - .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) - .expect("Failed to create referenced storage"), - ); - - static STATE1: StaticCell = StaticCell::new(); - let state1 = STATE1.init(mock_controller::ControllerState::new()); - static CONTROLLER1: StaticCell> = StaticCell::new(); - let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); - static WRAPPER1: StaticCell = StaticCell::new(); - let wrapper1 = WRAPPER1.init( - mock_controller::Wrapper::try_new(controller1, Default::default(), referenced1, mock_controller::Validator) - .expect("Failed to create wrapper"), - ); - spawner.must_spawn(wrapper_task(wrapper1)); - +async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, current_ma: 5000, @@ -311,11 +236,28 @@ async fn type_c_service_task(spawner: Spawner) { let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); - static REFERENCED0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + + static INTERMEDIATE0: StaticCell> = + StaticCell::new(); + let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static REFERENCED0: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); let referenced0 = REFERENCED0.init( - storage0 - .create_referenced() + intermediate0 + .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) .expect("Failed to create referenced storage"), ); @@ -330,11 +272,27 @@ async fn type_c_service_task(spawner: Spawner) { ); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); - static REFERENCED1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + static INTERMEDIATE1: StaticCell> = + StaticCell::new(); + let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED1: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); let referenced1 = REFERENCED1.init( - storage1 - .create_referenced() + intermediate1 + .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) .expect("Failed to create referenced storage"), ); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 43bd37a93..c7134752e 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,13 +1,11 @@ use crate::mock_controller::Wrapper; use embassy_executor::Executor; -use embassy_executor::{Executor, Spawner}; -use embassy_sync::channel; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::GlobalRawMutex; use embedded_services::power::policy::PowerCapability; -use embedded_services::power::policy::{self, PowerCapability}; +use embedded_services::power::policy::policy; use embedded_services::power::{self}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; @@ -19,10 +17,9 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; const NUM_PD_CONTROLLERS: usize = 3; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -106,7 +103,15 @@ async fn task(state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLL #[embassy_executor::task] async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) + static POWER_POLICY: static_cell::StaticCell< + PowerPolicy>, DynamicReceiver<'static, policy::RequestData>>, + > = static_cell::StaticCell::new(); + let power_policy = + POWER_POLICY.init(PowerPolicy::create(Default::default()).expect("Failed to create power policy")); + + // TODO: remove once power policy task accepts context + Timer::after_millis(100).await; + power_policy_service::task::task(power_policy) .await .expect("Failed to start power policy service task"); } @@ -146,31 +151,32 @@ async fn service_task( fn main() { env_logger::builder().filter_level(log::LevelFilter::Trace).init(); - static EXECUTOR: StaticCell = StaticCell::new(); - let executor = EXECUTOR.init(Executor::new()); - static CONTEXT: StaticCell = StaticCell::new(); let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); - static INTERMEDIATE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + static INTERMEDIATE0: StaticCell> = StaticCell::new(); let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); - static CHANNEL0: StaticCell> = StaticCell::new(); - let channel0 = CHANNEL0.init(channel::Channel::new()); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + static REFERENCED0: StaticCell< ReferencedStorage< 1, GlobalRawMutex, - channel::DynamicSender, - channel::DynamicReceiver, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, >, > = StaticCell::new(); let referenced0 = REFERENCED0.init( intermediate0 - .try_create_referenced([(POWER0_ID, channel0.dyn_sender(), channel0.dyn_receiver())]) + .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) .expect("Failed to create referenced storage"), ); @@ -190,22 +196,26 @@ fn main() { ); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); - static INTERMEDIATE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + static INTERMEDIATE1: StaticCell> = StaticCell::new(); let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); - static CHANNEL1: StaticCell> = StaticCell::new(); - let channel1 = CHANNEL1.init(channel::Channel::new()); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + static REFERENCED1: StaticCell< ReferencedStorage< 1, GlobalRawMutex, - channel::DynamicSender, - channel::DynamicReceiver, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, >, > = StaticCell::new(); let referenced1 = REFERENCED1.init( intermediate1 - .try_create_referenced([(POWER1_ID, channel1.dyn_sender(), channel1.dyn_receiver())]) + .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) .expect("Failed to create referenced storage"), ); @@ -225,22 +235,26 @@ fn main() { ); static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new(CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); - static INTERMEDIATE2: StaticCell> = StaticCell::new(); + let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); + static INTERMEDIATE2: StaticCell> = StaticCell::new(); let intermediate2 = INTERMEDIATE2.init(storage2.create_intermediate()); - static CHANNEL2: StaticCell> = StaticCell::new(); - let channel2 = CHANNEL2.init(channel::Channel::new()); + + static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); + let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); + let policy_sender2 = policy_channel2.dyn_sender(); + let policy_receiver2 = policy_channel2.dyn_receiver(); + static REFERENCED2: StaticCell< ReferencedStorage< 1, GlobalRawMutex, - channel::DynamicSender, - channel::DynamicReceiver, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, >, > = StaticCell::new(); let referenced2 = REFERENCED2.init( intermediate2 - .try_create_referenced([(POWER2_ID, channel2.dyn_sender(), channel2.dyn_receiver())]) + .try_create_referenced([(POWER2_ID, policy_sender2, policy_receiver2)]) .expect("Failed to create referenced storage"), ); diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index 11664a1dc..a66d6d44a 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,18 +1,20 @@ use core::future::Future; -use embedded_services::{error, info}; +use embedded_services::{error, event, info, power::policy::policy}; use crate::{service::Service, wrapper::ControllerWrapper}; /// Task to run the Type-C service, takes a closure to customize the event loop -pub async fn task_closure<'a, M, C, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( +pub async fn task_closure<'a, M, D, S, R, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, C, V>; N], + wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, - C: embedded_services::sync::Lockable, + D: embedded_services::sync::Lockable, + S: event::Sender, + R: event::Receiver, V: crate::wrapper::FwOfferValidator, - ::Inner: embedded_services::type_c::controller::Controller, + ::Inner: embedded_services::type_c::controller::Controller, { info!("Starting type-c task"); @@ -34,14 +36,16 @@ pub async fn task_closure<'a, M, C, V, Fut: Future, F: Fn(&'a Servi } /// Task to run the Type-C service, running the default event loop -pub async fn task<'a, M, C, V, const N: usize>( +pub async fn task<'a, M, D, S, R, V, const N: usize>( service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, C, V>; N], + wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], ) where M: embassy_sync::blocking_mutex::raw::RawMutex, - C: embedded_services::sync::Lockable, + D: embedded_services::sync::Lockable, + S: event::Sender, + R: event::Receiver, V: crate::wrapper::FwOfferValidator, - ::Inner: embedded_services::type_c::controller::Controller, + ::Inner: embedded_services::type_c::controller::Controller, { task_closure(service, wrappers, |service: &Service| async { if let Err(e) = service.process_next_event().await { diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index b71630082..89d6c1755 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -191,6 +191,7 @@ pub trait DynPortState<'a, S: event::Sender> { /// Service registration objects pub struct Registration<'a, M: RawMutex, R: event::Receiver> { + pub context: &'a embedded_services::type_c::controller::Context, pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, pub cfu_device: &'a embedded_services::cfu::component::CfuDevice, pub power_devices: &'a [embedded_services::power::policy::device::Device<'a, Mutex>, R>], @@ -211,7 +212,7 @@ pub struct PortPower> { } /// Base storage -pub struct Storage<'a, const N: usize, M: RawMutex, S: event::Sender> { +pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, @@ -223,13 +224,12 @@ pub struct Storage<'a, const N: usize, M: RawMutex, S: event::Sender; N], } -impl<'a, const N: usize, M: RawMutex, S: event::Sender> Storage { +impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, cfu_id: ComponentId, pd_ports: [GlobalPortId; N], - power_policy_senders: [S; N], ) -> Self { Self { context, @@ -249,14 +249,14 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> Sto /// Intermediate storage that holds power proxy devices pub struct IntermediateStorage<'a, const N: usize, M: RawMutex> { - storage: &'a Storage, + storage: &'a Storage<'a, N, M>, power_proxy_devices: [Mutex>; N], power_proxy_receivers: [Mutex>; N], } impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { // Panic Safety: size of everything is fixed at compile time to N - fn from_storage(storage: &'a Storage) -> Self { + fn from_storage(storage: &'a Storage<'a, N, M>) -> Self { let mut power_proxy_devices = heapless::Vec::<_, N>::new(); let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); @@ -360,7 +360,7 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: { self.state.try_borrow_mut().ok().map(|state| Backing:: { registration: Registration { - context: self.storage.context, + context: self.intermediate.storage.context, pd_controller: &self.pd_controller, cfu_device: &self.intermediate.storage.cfu_device, power_devices: &self.power_devices, diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 4caf4bd75..3a45e7634 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -27,13 +27,12 @@ use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::event; use embedded_services::power::policy::policy; use embedded_services::sync::Lockable; use embedded_services::type_c::controller::{self, Controller, PortStatus}; use embedded_services::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; -use embedded_services::{GlobalRawMutex, event, intrusive_list}; use embedded_services::{debug, error, info, trace, warn}; +use embedded_services::{event, intrusive_list}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; @@ -607,9 +606,9 @@ where pub async fn register( &'static self, controllers: &intrusive_list::IntrusiveList, - ) -> Result<(), Error<::BusError>> { - for device in self.registration.power_event_senders { - policy::register_device(device).await.map_err(|_| { + ) -> Result<(), Error<::BusError>> { + for device in self.registration.power_devices { + policy::register_device(device).map_err(|_| { error!( "Controller{}: Failed to register power device {}", self.registration.pd_controller.id().0, From 2fa1e17255425ea9d676ed80eb55cdc99cc3f8d7 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 22 Jan 2026 12:00:42 -0800 Subject: [PATCH 18/22] CI fixes --- embedded-service/src/power/policy/policy.rs | 2 + examples/std/src/bin/power_policy.rs | 4 +- examples/std/src/bin/type_c/external.rs | 6 +- examples/std/src/bin/type_c/service.rs | 6 +- examples/std/src/bin/type_c/ucsi.rs | 12 ++- examples/std/src/bin/type_c/unconstrained.rs | 18 ++++- power-policy-service/src/task.rs | 2 +- power-policy-service/tests/common/mod.rs | 4 +- type-c-service/src/wrapper/backing.rs | 78 ++++++++++---------- type-c-service/src/wrapper/proxy.rs | 6 ++ 10 files changed, 89 insertions(+), 49 deletions(-) diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 77ed8be16..3e931c4bb 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -268,6 +268,8 @@ where } let (event, index) = select_slice(pin!(&mut futures)).await; + // Panic safety: The index is guaranteed to be within bounds since it comes from the select_slice result + #[allow(clippy::unwrap_used)] let device = self .devices() .iter_only::>() diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 25a3af4c1..7608e214a 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -72,12 +72,12 @@ impl DeviceTrait for ExampleDevice<'_> { } async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { - debug!("ExampleDevice connect_provider with {:?}", capability); + debug!("ExampleDevice connect_provider with {capability:?}"); Ok(()) } async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { - debug!("ExampleDevice connect_consumer with {:?}", capability); + debug!("ExampleDevice connect_consumer with {capability:?}"); Ok(()) } } diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 21e4cec41..0a3ae0e0d 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -140,7 +140,11 @@ fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper< static INTERMEDIATE: StaticCell> = StaticCell::new(); - let intermediate = INTERMEDIATE.init(backing_storage.create_intermediate()); + let intermediate = INTERMEDIATE.init( + backing_storage + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL: StaticCell> = StaticCell::new(); let policy_channel = POLICY_CHANNEL.init(Channel::new()); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 7c14dc5d4..6823ae095 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -191,7 +191,11 @@ fn create_wrapper( static INTERMEDIATE: StaticCell> = StaticCell::new(); - let intermediate = INTERMEDIATE.init(storage.create_intermediate()); + let intermediate = INTERMEDIATE.init( + storage + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL: StaticCell> = StaticCell::new(); let policy_channel = POLICY_CHANNEL.init(Channel::new()); diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 9b3515b30..e13cc83dc 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -240,7 +240,11 @@ async fn type_c_service_task(spawner: Spawner) { static INTERMEDIATE0: StaticCell> = StaticCell::new(); - let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); + let intermediate0 = INTERMEDIATE0.init( + storage0 + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); @@ -275,7 +279,11 @@ async fn type_c_service_task(spawner: Spawner) { let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); static INTERMEDIATE1: StaticCell> = StaticCell::new(); - let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); + let intermediate1 = INTERMEDIATE1.init( + storage1 + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index c7134752e..e148edbd4 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -159,7 +159,11 @@ fn main() { static STORAGE0: StaticCell> = StaticCell::new(); let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); static INTERMEDIATE0: StaticCell> = StaticCell::new(); - let intermediate0 = INTERMEDIATE0.init(storage0.create_intermediate()); + let intermediate0 = INTERMEDIATE0.init( + storage0 + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); @@ -198,7 +202,11 @@ fn main() { static STORAGE1: StaticCell> = StaticCell::new(); let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); static INTERMEDIATE1: StaticCell> = StaticCell::new(); - let intermediate1 = INTERMEDIATE1.init(storage1.create_intermediate()); + let intermediate1 = INTERMEDIATE1.init( + storage1 + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); @@ -237,7 +245,11 @@ fn main() { static STORAGE2: StaticCell> = StaticCell::new(); let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); static INTERMEDIATE2: StaticCell> = StaticCell::new(); - let intermediate2 = INTERMEDIATE2.init(storage2.create_intermediate()); + let intermediate2 = INTERMEDIATE2.init( + storage2 + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); diff --git a/power-policy-service/src/task.rs b/power-policy-service/src/task.rs index bc1469a78..aa8a9a83a 100644 --- a/power-policy-service/src/task.rs +++ b/power-policy-service/src/task.rs @@ -6,7 +6,7 @@ use embedded_services::{ sync::Lockable, }; -use crate::{PowerPolicy}; +use crate::PowerPolicy; #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 33885699c..dcfe82c66 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -85,7 +85,7 @@ pub async fn run_test>( > = StaticCell::new(); let device0_registration = DEVICE0_REGISTRATION.init(device::Device::new(DeviceId(0), device0, device0_receiver)); - policy::register_device(device0_registration).await.unwrap(); + policy::register_device(device0_registration).unwrap(); static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); @@ -107,7 +107,7 @@ pub async fn run_test>( > = StaticCell::new(); let device1_registration = DEVICE1_REGISTRATION.init(device::Device::new(DeviceId(1), device1, device1_receiver)); - policy::register_device(device1_registration).await.unwrap(); + policy::register_device(device1_registration).unwrap(); static POWER_POLICY: StaticCell< PowerPolicy< diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 89d6c1755..303632b75 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -23,22 +23,38 @@ //! use embedded_usb_pd::GlobalPortId; //! use type_c_service::wrapper::backing::{Storage, IntermediateStorage, ReferencedStorage}; //! -//! -//! const NUM_PORTS: usize = 2; -//! //! fn init(context: &'static embedded_services::type_c::controller::Context) { -//! static STORAGE: StaticCell> = StaticCell::new(); +//! static STORAGE: StaticCell> = StaticCell::new(); //! let storage = STORAGE.init(Storage::new( //! context, //! ControllerId(0), -//! 0x0, -//! [(GlobalPortId(0), power::policy::DeviceId(0)), (GlobalPortId(1), power::policy::DeviceId(1))], +//! 0x0, // CFU component ID (unused) +//! [power::policy::DeviceId(0)], //! )); -//! static INTERMEDIATE: StaticCell> = StaticCell::new(); -//! let intermediate = INTERMEDIATE.init(storage.create_intermediate()); -//! static REFERENCED: StaticCell> = StaticCell::new(); -//! let referenced = REFERENCED.init(intermediate.create_referenced()); -//! let _backing = referenced.create_backing().unwrap(); +//! +//! static INTERMEDIATE: StaticCell> = +//! StaticCell::new(); +//! let intermediate = INTERMEDIATE.init(storage.try_create_intermediate().expect("Failed to create intermediate storage")); +//! +//! static POLICY_CHANNEL: StaticCell> = StaticCell::new(); +//! let policy_channel = POLICY_CHANNEL.init(Channel::new()); +//! +//! let policy_sender = policy_channel.dyn_sender(); +//! let policy_receiver = policy_channel.dyn_receiver(); +//! +//! static REFERENCED: StaticCell< +//! type_c_service::wrapper::backing::ReferencedStorage< +//! 1, +//! GlobalRawMutex, +//! DynamicSender<'_, policy::RequestData>, +//! DynamicReceiver<'_, policy::RequestData>, +//! >, +//! > = StaticCell::new(); +//! let referenced = REFERENCED.init( +//! intermediate +//! .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) +//! .expect("Failed to create referenced storage"), +//! ); //! } //! ``` use core::{ @@ -242,8 +258,8 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { } /// Create intermediate storage from this storage - pub fn create_intermediate(&self) -> IntermediateStorage<'_, N, M> { - IntermediateStorage::from_storage(self) + pub fn try_create_intermediate(&self) -> Option> { + IntermediateStorage::try_from_storage(self) } } @@ -255,30 +271,24 @@ pub struct IntermediateStorage<'a, const N: usize, M: RawMutex> { } impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { - // Panic Safety: size of everything is fixed at compile time to N - fn from_storage(storage: &'a Storage<'a, N, M>) -> Self { + fn try_from_storage(storage: &'a Storage<'a, N, M>) -> Option { let mut power_proxy_devices = heapless::Vec::<_, N>::new(); let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); for power_proxy_channel in storage.power_proxy_channels.iter() { power_proxy_devices .push(Mutex::new(power_proxy_channel.get_device())) - .unwrap_or_else(|_| panic!("Failed to insert power proxy device")); + .ok()?; power_proxy_receivers .push(Mutex::new(power_proxy_channel.get_receiver())) - .unwrap_or_else(|_| panic!("Failed to insert power proxy receiver")); + .ok()?; } - Self { + Some(Self { storage, - // Safe because both have N elements - power_proxy_devices: power_proxy_devices - .into_array() - .unwrap_or_else(|_| panic!("Failed to create power devices")), - power_proxy_receivers: power_proxy_receivers - .into_array() - .unwrap_or_else(|_| panic!("Failed to create power receivers")), - } + power_proxy_devices: power_proxy_devices.into_array().ok()?, + power_proxy_receivers: power_proxy_receivers.into_array().ok()?, + }) } /// Create referenced storage from this intermediate storage @@ -322,16 +332,14 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: let mut power_devices = heapless::Vec::<_, N>::new(); for (i, (device_id, policy_sender, policy_receiver)) in policy_args.into_iter().enumerate() { - power_senders - .push(policy_sender) - .unwrap_or_else(|_| panic!("Failed to insert policy sender")); + power_senders.push(policy_sender).ok()?; power_devices .push(embedded_services::power::policy::device::Device::new( device_id, - &intermediate.power_proxy_devices[i], + intermediate.power_proxy_devices.get(i)?, policy_receiver, )) - .unwrap_or_else(|_| panic!("Failed to insert power device")); + .ok()?; } Some(Self { @@ -339,17 +347,13 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: state: RefCell::new(InternalState::try_new( intermediate.storage, // Safe because both have N elements - power_senders - .into_array() - .unwrap_or_else(|_| panic!("Failed to create power events")), + power_senders.into_array().ok()?, )?), pd_controller: embedded_services::type_c::controller::Device::new( intermediate.storage.controller_id, intermediate.storage.pd_ports.as_slice(), ), - power_devices: power_devices - .into_array() - .unwrap_or_else(|_| panic!("Failed to create power devices")), + power_devices: power_devices.into_array().ok()?, }) } diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs index 7a69646a4..252247e88 100644 --- a/type-c-service/src/wrapper/proxy.rs +++ b/type-c-service/src/wrapper/proxy.rs @@ -97,3 +97,9 @@ impl<'a> DeviceTrait for PowerProxyDevice<'a> { .complete_or_err() } } + +impl Default for PowerProxyChannel { + fn default() -> Self { + Self::new() + } +} From 944831560d100ef8bc40f4d57ae25b3fb91cdb10 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 22 Jan 2026 13:54:18 -0800 Subject: [PATCH 19/22] Update examples --- examples/rt685s-evk/src/bin/type_c.rs | 71 ++++++++++++++++++----- examples/rt685s-evk/src/bin/type_c_cfu.rs | 63 +++++++++++++++++--- 2 files changed, 111 insertions(+), 23 deletions(-) diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 3d46d274d..e7a57ccca 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -8,21 +8,24 @@ use embassy_imxrt::gpio::{Input, Inverter, Pull}; use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; -use embassy_time::{self as _, Delay}; +use embassy_time::{self as _, Delay, Timer}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; -use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; +use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId, policy}; use embedded_services::type_c::{Cached, ControllerId}; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -49,7 +52,14 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator>; +type Wrapper<'a> = ControllerWrapper< + 'a, + GlobalRawMutex, + Tps6699xMutex<'a>, + DynamicSender<'a, policy::RequestData>, + DynamicReceiver<'a, policy::RequestData>, + Validator, +>; type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; @@ -69,7 +79,15 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) + static POWER_POLICY: static_cell::StaticCell< + PowerPolicy>, DynamicReceiver<'static, policy::RequestData>>, + > = static_cell::StaticCell::new(); + let power_policy = + POWER_POLICY.init(PowerPolicy::create(Default::default()).expect("Failed to create power policy")); + + // TODO: remove once power policy task accepts context + Timer::after_millis(100).await; + power_policy_service::task::task(power_policy) .await .expect("Failed to start power policy service task"); } @@ -80,8 +98,6 @@ async fn service_task( controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], ) { - info!("Starting type-c task"); - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -111,9 +127,6 @@ async fn main(spawner: Spawner) { info!("Embedded service init"); embedded_services::init().await; - info!("Spawining power policy task"); - spawner.must_spawn(power_policy_service_task()); - static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); static CONTEXT: StaticCell = StaticCell::new(); @@ -157,13 +170,40 @@ async fn main(spawner: Spawner) { controller_context, CONTROLLER0_ID, 0, // CFU component ID - [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], + [PORT0_ID, PORT1_ID], )); - static REFERENCED: StaticCell> = StaticCell::new(); - let referenced = REFERENCED.init( + static INTERMEDIATE: StaticCell> = StaticCell::new(); + let intermediate = INTERMEDIATE.init( storage - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED: StaticCell< + ReferencedStorage< + TPS66994_NUM_PORTS, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced([ + (PORT0_PWR_ID, policy_sender0, policy_receiver0), + (PORT1_PWR_ID, policy_sender1, policy_receiver1), + ]) .expect("Failed to create referenced storage"), ); @@ -178,6 +218,9 @@ async fn main(spawner: Spawner) { info!("Spawining type-c service task"); spawner.must_spawn(service_task(controller_context, controllers, [wrapper])); + info!("Spawining power policy task"); + spawner.must_spawn(power_policy_service_task()); + spawner.must_spawn(pd_controller_task(wrapper)); // Sync our internal state with the hardware diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 87cfed3a4..38e87f024 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -8,6 +8,7 @@ use embassy_imxrt::gpio::{Input, Inverter, Pull}; use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; @@ -16,18 +17,20 @@ use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::cfu::component::InternalResponseData; use embedded_services::cfu::component::RequestData; -use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; +use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId, policy}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; use embedded_services::{GlobalRawMutex, IntrusiveList, cfu}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -47,7 +50,14 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator>; +type Wrapper<'a> = ControllerWrapper< + 'a, + GlobalRawMutex, + Tps6699xMutex<'a>, + DynamicSender<'a, policy::RequestData>, + DynamicReceiver<'a, policy::RequestData>, + Validator, +>; type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; @@ -105,7 +115,7 @@ async fn fw_update_task() { .unwrap(); info!("Got response: {:?}", offer); - let fw = &[]; //include_bytes!("../../fw.bin"); + let fw = include_bytes!("../../TPS66994_Host.bin").as_slice(); let num_chunks = fw.len() / DEFAULT_DATA_LENGTH + (fw.len() % DEFAULT_DATA_LENGTH != 0) as usize; for (i, chunk) in fw.chunks(DEFAULT_DATA_LENGTH).enumerate() { @@ -155,7 +165,15 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) + static POWER_POLICY: static_cell::StaticCell< + PowerPolicy>, DynamicReceiver<'static, policy::RequestData>>, + > = static_cell::StaticCell::new(); + let power_policy = + POWER_POLICY.init(PowerPolicy::create(Default::default()).expect("Failed to create power policy")); + + // TODO: remove once power policy task accepts context + Timer::after_millis(100).await; + power_policy_service::task::task(power_policy) .await .expect("Failed to start power policy service task"); } @@ -243,13 +261,40 @@ async fn main(spawner: Spawner) { controller_context, CONTROLLER0_ID, CONTROLLER0_CFU_ID, - [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], + [PORT0_ID, PORT1_ID], )); - static REFERENCED: StaticCell> = StaticCell::new(); - let referenced = REFERENCED.init( + static INTERMEDIATE: StaticCell> = StaticCell::new(); + let intermediate = INTERMEDIATE.init( storage - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED: StaticCell< + ReferencedStorage< + TPS66994_NUM_PORTS, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced([ + (PORT0_PWR_ID, policy_sender0, policy_receiver0), + (PORT1_PWR_ID, policy_sender1, policy_receiver1), + ]) .expect("Failed to create referenced storage"), ); From fd95083eb9bc266de9a8b14c5d9d58b02256e890 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 22 Jan 2026 14:41:28 -0800 Subject: [PATCH 20/22] Fixup doc code --- examples/rt685s-evk/src/bin/type_c_cfu.rs | 2 +- type-c-service/src/wrapper/backing.rs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 38e87f024..91f68d3e9 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -115,7 +115,7 @@ async fn fw_update_task() { .unwrap(); info!("Got response: {:?}", offer); - let fw = include_bytes!("../../TPS66994_Host.bin").as_slice(); + let fw = &[]; //include_bytes!("../../fw.bin"); let num_chunks = fw.len() / DEFAULT_DATA_LENGTH + (fw.len() % DEFAULT_DATA_LENGTH != 0) as usize; for (i, chunk) in fw.chunks(DEFAULT_DATA_LENGTH).enumerate() { diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 303632b75..bd6949573 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -22,21 +22,23 @@ //! use embedded_services::power; //! use embedded_usb_pd::GlobalPortId; //! use type_c_service::wrapper::backing::{Storage, IntermediateStorage, ReferencedStorage}; +//! use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +//! use embedded_services::power::policy::policy; //! //! fn init(context: &'static embedded_services::type_c::controller::Context) { -//! static STORAGE: StaticCell> = StaticCell::new(); +//! static STORAGE: StaticCell> = StaticCell::new(); //! let storage = STORAGE.init(Storage::new( //! context, //! ControllerId(0), //! 0x0, // CFU component ID (unused) -//! [power::policy::DeviceId(0)], +//! [GlobalPortId(0)], //! )); //! -//! static INTERMEDIATE: StaticCell> = +//! static INTERMEDIATE: StaticCell> = //! StaticCell::new(); //! let intermediate = INTERMEDIATE.init(storage.try_create_intermediate().expect("Failed to create intermediate storage")); //! -//! static POLICY_CHANNEL: StaticCell> = StaticCell::new(); +//! static POLICY_CHANNEL: StaticCell> = StaticCell::new(); //! let policy_channel = POLICY_CHANNEL.init(Channel::new()); //! //! let policy_sender = policy_channel.dyn_sender(); @@ -45,14 +47,14 @@ //! static REFERENCED: StaticCell< //! type_c_service::wrapper::backing::ReferencedStorage< //! 1, -//! GlobalRawMutex, +//! NoopRawMutex, //! DynamicSender<'_, policy::RequestData>, //! DynamicReceiver<'_, policy::RequestData>, //! >, //! > = StaticCell::new(); //! let referenced = REFERENCED.init( //! intermediate -//! .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) +//! .try_create_referenced([(power::policy::DeviceId(0), policy_sender, policy_receiver)]) //! .expect("Failed to create referenced storage"), //! ); //! } From 7f52beaa857c90f9a2ef31ad4b1548e78e97b158 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 22 Jan 2026 14:48:31 -0800 Subject: [PATCH 21/22] Update env_logger --- Cargo.lock | 242 +++++++++++++++++++++----------- examples/std/Cargo.lock | 213 ++++++++++++++++++---------- examples/std/Cargo.toml | 2 +- power-policy-service/Cargo.toml | 2 +- 4 files changed, 298 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aeaef8c8..9deeef477 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,56 @@ dependencies = [ "as-slice", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -119,17 +169,6 @@ dependencies = [ "winnow 0.7.13", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -364,6 +403,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "const-init" version = "1.0.0" @@ -926,16 +971,26 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.9.3" +name = "env_filter" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ - "atty", - "humantime", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] @@ -1093,15 +1148,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hid-service" version = "0.1.0" @@ -1115,12 +1161,6 @@ dependencies = [ "log", ] -[[package]] -name = "humantime" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" - [[package]] name = "include_dir" version = "0.7.4" @@ -1161,6 +1201,12 @@ dependencies = [ "libc", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1194,6 +1240,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "kdl" version = "6.3.4" @@ -1549,6 +1619,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "partition-manager" version = "0.1.0" @@ -1643,6 +1719,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "power-button-service" version = "0.1.0" @@ -1890,18 +1975,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2031,15 +2126,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thermal-service" version = "0.1.0" @@ -2351,6 +2437,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.17.0" @@ -2402,37 +2494,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.61.3" @@ -2442,7 +2503,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -2463,7 +2524,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -2475,7 +2536,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -2507,6 +2568,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -2514,7 +2581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2523,7 +2590,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2532,7 +2599,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2553,6 +2620,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2575,7 +2651,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index f7141241a..ec7c39e2d 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -23,6 +23,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -86,17 +136,6 @@ dependencies = [ "winnow 0.7.13", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -286,6 +325,12 @@ dependencies = [ "log", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "const-init" version = "1.0.0" @@ -795,16 +840,26 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.9.3" +name = "env_filter" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ - "atty", - "humantime", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] @@ -907,21 +962,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "humantime" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" - [[package]] name = "ident_case" version = "1.0.1" @@ -947,6 +987,12 @@ dependencies = [ "quote", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -971,6 +1017,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "kdl" version = "6.3.4" @@ -1253,6 +1323,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "paste" version = "1.0.15" @@ -1297,6 +1373,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "power-policy-service" version = "0.1.0" @@ -1445,18 +1530,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1605,15 +1700,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thermal-service" version = "0.1.0" @@ -1821,6 +1907,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.17.0" @@ -1866,37 +1958,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" -dependencies = [ - "windows-sys 0.60.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.61.3" diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 39ffeb396..20d53f7aa 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -41,7 +41,7 @@ embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log"] } thermal-service-messages = { path = "../../thermal-service-messages" } -env_logger = "0.9.0" +env_logger = "0.11.8" log = "0.4.14" heapless = "0.8.0" static_cell = "2" diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 986ab18b7..9be53aed5 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -24,7 +24,7 @@ static_cell.workspace = true critical-section = { workspace = true, features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } tokio = { workspace = true, features = ["rt", "macros", "time"] } -env_logger = "0.9.0" +env_logger = "0.11.8" log = { workspace = true } [features] From 31b3b0080fc0b165376a610dca418af42f5c20ef Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 22 Jan 2026 14:58:46 -0800 Subject: [PATCH 22/22] Upgrade power policy integration tests --- power-policy-service/tests/common/mock.rs | 1 + power-policy-service/tests/common/mod.rs | 10 +++------- power-policy-service/tests/consumer.rs | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index d87226387..443842958 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use embassy_sync::signal::Signal; use embedded_services::power::policy::device::{DeviceTrait, InternalState}; use embedded_services::power::policy::flags::Consumer; diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index dcfe82c66..8a3587a0f 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use embassy_futures::{ join::join, select::{Either, select}, @@ -43,13 +44,8 @@ async fn power_policy_task( DynamicReceiver<'static, RequestData>, >, ) { - loop { - match select(power_policy.process(), completion_signal.wait()).await { - Either::First(result) => result.unwrap(), - Either::Second(_) => { - break; - } - } + while let Either::First(result) = select(power_policy.process(), completion_signal.wait()).await { + result.unwrap(); } } diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index c201e5278..bf104cbc2 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::{