diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index 416a92c5..dfabd3d8 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -1,5 +1,7 @@ //! Common traits for event senders and receivers +use core::marker::PhantomData; + use embassy_sync::channel::{DynamicReceiver, DynamicSender}; /// Common event sender trait @@ -41,3 +43,42 @@ impl Receiver for DynamicReceiver<'_, E> { self.receive() } } + +/// A sender that discards all events sent to it. +pub struct NoopSender; + +impl Sender for NoopSender { + fn try_send(&mut self, _event: E) -> Option<()> { + Some(()) + } + + async fn send(&mut self, _event: E) {} +} + +/// Applies a function on events before passing them to the wrapped sender +pub struct MapSender, F: FnMut(I) -> O> { + sender: S, + map_fn: F, + _phantom: PhantomData<(I, O)>, +} + +impl, F: FnMut(I) -> O> MapSender { + /// Create a new self + pub fn new(sender: S, map_fn: F) -> Self { + Self { + sender, + map_fn, + _phantom: PhantomData, + } + } +} + +impl, F: FnMut(I) -> O> Sender for MapSender { + fn try_send(&mut self, event: I) -> Option<()> { + self.sender.try_send((self.map_fn)(event)) + } + + fn send(&mut self, event: I) -> impl Future { + self.sender.send((self.map_fn)(event)) + } +} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index c53aba53..e62562e3 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -21,6 +21,7 @@ pub mod hid; pub mod init; pub mod ipc; pub mod keyboard; +pub mod named; pub mod relay; pub mod sync; diff --git a/embedded-service/src/named.rs b/embedded-service/src/named.rs new file mode 100644 index 00000000..feebe699 --- /dev/null +++ b/embedded-service/src/named.rs @@ -0,0 +1,7 @@ +//! Traits for things that have names. + +/// Trait for anything that has a name. +pub trait Named { + /// Return name + fn name(&self) -> &'static str; +} diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 5232dcf0..83070d97 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -15,18 +15,20 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; 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::type_c::{Cached, ControllerId}; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::controller::ControllerId; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -64,6 +66,11 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { loop { @@ -80,8 +87,8 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -136,11 +143,8 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( @@ -204,13 +208,14 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -219,7 +224,6 @@ async fn main(spawner: Spawner) { let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); @@ -233,7 +237,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), @@ -241,31 +245,4 @@ async fn main(spawner: Spawner) { )); spawner.must_spawn(pd_controller_task(wrapper)); - - // Sync our internal state with the hardware - controller_context - .sync_controller_state_external(CONTROLLER0_ID) - .await - .unwrap(); - - embassy_time::Timer::after_secs(10).await; - - let status = controller_context - .get_controller_status_external(CONTROLLER0_ID) - .await - .unwrap(); - - info!("Controller status: {:?}", status); - - let status = controller_context - .get_port_status_external(PORT0_ID, Cached(true)) - .await - .unwrap(); - info!("Port status: {:?}", status); - - let status = controller_context - .get_port_status_external(PORT1_ID, Cached(true)) - .await - .unwrap(); - info!("Port status: {:?}", status); } diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index f8ab5c20..91294af5 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -18,18 +18,19 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::{GlobalRawMutex, IntrusiveList}; +use embedded_services::GlobalRawMutex; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; 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::type_c::ControllerId; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::controller::ControllerId; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -62,6 +63,11 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; @@ -164,8 +170,8 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -220,11 +226,8 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( @@ -282,13 +285,14 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -307,7 +311,6 @@ async fn main(spawner: Spawner) { let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); @@ -321,7 +324,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 40e8a292..6cf7e51f 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -5,16 +5,21 @@ use embassy_sync::{ mutex::Mutex, }; use embassy_time::{self as _, Timer}; -use embedded_services::GlobalRawMutex; +use embedded_services::{GlobalRawMutex, event::NoopSender, named::Named}; use log::*; use power_policy_interface::psu::{Error, Psu}; use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, psu, }; -use power_policy_service::psu::EventReceivers; +use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; use static_cell::StaticCell; +type ServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + const LOW_POWER: PowerCapability = PowerCapability { voltage_mv: 5000, current_ma: 1500, @@ -96,7 +101,9 @@ impl Psu for ExampleDevice<'_> { fn state_mut(&mut self) -> &mut psu::State { &mut self.state } +} +impl Named for ExampleDevice<'_> { fn name(&self) -> &'static str { self.name } @@ -131,19 +138,20 @@ async fn run(spawner: Spawner) { static SERVICE_CONTEXT: StaticCell = StaticCell::new(); let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); + let registration = ArrayRegistration { + psus: [device0, device1], + service_senders: [NoopSender], + }; - static SERVICE: StaticCell>> = - StaticCell::new(); + static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration.as_slice(), + registration, service_context, power_policy_service::service::config::Config::default(), ))); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [device0, device1], [ device0_event_channel.dyn_receiver(), @@ -275,13 +283,13 @@ async fn run(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers< + psu_events: ArrayEventReceivers< 'static, 2, DeviceType, channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, - power_policy: &'static Mutex>, + power_policy: &'static ServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 1bdbb604..44321670 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,11 +1,11 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; -use embedded_services::IntrusiveList; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; +use type_c_service::service::context::{Context, DeviceContainer}; use type_c_service::type_c::{Cached, ControllerId, controller}; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -22,7 +22,7 @@ mod test_controller { pub controller: controller::Device<'a>, } - impl controller::DeviceContainer for Controller<'_> { + impl DeviceContainer for Controller<'_> { fn get_pd_controller_device(&self) -> &controller::Device<'_> { &self.controller } @@ -115,13 +115,13 @@ mod test_controller { } #[embassy_executor::task] -async fn controller_task(controller_list: &'static IntrusiveList) { +async fn controller_task(controller_context: &'static Context) { static CONTROLLER: OnceLock = OnceLock::new(); 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_list, controller).unwrap(); + controller_context.register_controller(controller).unwrap(); loop { controller.process().await; @@ -132,32 +132,27 @@ async fn controller_task(controller_list: &'static IntrusiveList) { async fn task(spawner: Spawner) { embedded_services::init().await; - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(Context::new()); info!("Starting controller task"); - spawner.must_spawn(controller_task(controller_list)); + spawner.must_spawn(controller_task(controller_context)); // Wait for controller to be registered Timer::after_secs(1).await; - let context = controller::Context::new(); + controller_context.reset_controller(CONTROLLER0_ID).await.unwrap(); - context.reset_controller(controller_list, CONTROLLER0_ID).await.unwrap(); - - let status = context - .get_controller_status(controller_list, CONTROLLER0_ID) - .await - .unwrap(); + let status = controller_context.get_controller_status(CONTROLLER0_ID).await.unwrap(); info!("Controller 0 status: {status:#?}"); - let status = context - .get_port_status(controller_list, PORT0_ID, Cached(true)) + let status = controller_context + .get_port_status(PORT0_ID, Cached(true)) .await .unwrap(); info!("Port 0 status: {status:#?}"); - let status = context - .get_port_status(controller_list, PORT1_ID, Cached(true)) + let status = controller_context + .get_port_status(PORT1_ID, Cached(true)) .await .unwrap(); info!("Port 1 status: {status:#?}"); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index a03d7c29..94202ad8 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,19 +5,21 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; 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::type_c::controller::Context; +use type_c_service::service::context::Context; use type_c_service::type_c::{ControllerId, power_capability_from_current}; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::message::*; @@ -30,6 +32,11 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 1, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn controller_task( wrapper: &'static Wrapper<'static>, @@ -67,18 +74,19 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(type_c_service::service::context::Context::new()); let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 1]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -94,14 +102,10 @@ async fn task(spawner: Spawner) { // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Config::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); @@ -111,7 +115,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), + ArrayEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), power_service, )); spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); @@ -143,8 +147,8 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 24d0cb9c..8187012a 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,3 +1,4 @@ +#![allow(unused_imports)] use crate::mock_controller::Wrapper; use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; @@ -7,6 +8,7 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; +use embedded_services::event::NoopSender; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -16,14 +18,14 @@ use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; 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::service::context::Context; use type_c_service::type_c::ControllerId; -use type_c_service::type_c::controller::Context; -use type_c_service::type_c::external::UcsiResponseResult; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -37,9 +39,14 @@ const CFU1_ID: u8 = 0x01; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + #[embassy_executor::task] -async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { - const CAPABILITY: PowerCapability = PowerCapability { +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, }; @@ -165,7 +172,7 @@ async fn opm_task(context: &'static Context, state: [&'static mock_controller::C "Sending command complete ack successful, connector change: {:?}", response.cci.connector_change() ); - } + }*/ } #[embassy_executor::task(pool_size = 2)] @@ -179,8 +186,8 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -201,11 +208,8 @@ async fn task(spawner: Spawner) { embedded_services::init().await; - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(Context::new()); static STORAGE0: StaticCell> = StaticCell::new(); let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); @@ -302,13 +306,14 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -351,7 +356,6 @@ async fn task(spawner: Spawner) { ..Default::default() }, controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); @@ -361,7 +365,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 813e8052..6344b1bd 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -8,12 +8,14 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -39,6 +41,11 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 3, NoopSender, 1>>, +>; + #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -56,13 +63,8 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTEXT: StaticCell = StaticCell::new(); - let context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); @@ -172,17 +174,18 @@ async fn task(spawner: Spawner) { crate::mock_controller::Validator, )); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 3]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([ - &wrapper0.ports[0].proxy, - &wrapper1.ports[0].proxy, - &wrapper2.ports[0].proxy, - ]); + let power_policy_registration = ArrayRegistration { + psus: [ + &wrapper0.ports[0].proxy, + &wrapper1.ports[0].proxy, + &wrapper2.ports[0].proxy, + ], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -202,7 +205,6 @@ async fn task(spawner: Spawner) { let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); @@ -212,7 +214,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [ &wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy, @@ -283,8 +285,8 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/power-policy-interface/src/psu/mod.rs b/power-policy-interface/src/psu/mod.rs index 323b8318..bae5ced5 100644 --- a/power-policy-interface/src/psu/mod.rs +++ b/power-policy-interface/src/psu/mod.rs @@ -1,4 +1,6 @@ //! Device struct and methods +use embedded_services::named::Named; + use crate::capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}; pub mod event; @@ -280,7 +282,7 @@ pub struct Response { } /// Trait for PSU devices -pub trait Psu { +pub trait Psu: Named { /// Disconnect power from this device fn disconnect(&mut self) -> impl Future>; /// Connect this device to provide power to an external connection @@ -291,6 +293,4 @@ pub trait Psu { fn state(&self) -> &State; /// Return a mutable reference to the current PSU state fn state_mut(&mut self) -> &mut State; - /// Return the name of the PSU - fn name(&self) -> &'static str; } diff --git a/power-policy-interface/src/service/event.rs b/power-policy-interface/src/service/event.rs index 9888142e..40874119 100644 --- a/power-policy-interface/src/service/event.rs +++ b/power-policy-interface/src/service/event.rs @@ -6,38 +6,73 @@ use crate::{ service::UnconstrainedState, }; +/// Event data broadcast from the service. +/// +/// This enum doesn't contain a reference to the device and is suitable +/// for receivers that don't need to know which device triggered the event +/// and allows for receivers that don't need to be generic over the device type. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EventData { + /// Consumer disconnected + ConsumerDisconnected, + /// Consumer connected + ConsumerConnected(ConsumerPowerCapability), + /// Provider disconnected + ProviderDisconnected, + /// Provider connected + ProviderConnected(ProviderPowerCapability), + /// Unconstrained state changed + Unconstrained(UnconstrainedState), +} + +impl<'device, PSU: Lockable> From> for EventData +where + PSU::Inner: Psu, +{ + fn from(value: Event<'device, PSU>) -> Self { + match value { + Event::ConsumerDisconnected(_) => EventData::ConsumerDisconnected, + Event::ConsumerConnected(_, capability) => EventData::ConsumerConnected(capability), + Event::ProviderDisconnected(_) => EventData::ProviderDisconnected, + Event::ProviderConnected(_, capability) => EventData::ProviderConnected(capability), + Event::Unconstrained(unconstrained) => EventData::Unconstrained(unconstrained), + } + } +} + /// Events broadcast from the service. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event<'device, D: Lockable> +pub enum Event<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Consumer disconnected - ConsumerDisconnected(&'device D), + ConsumerDisconnected(&'device PSU), /// Consumer connected - ConsumerConnected(&'device D, ConsumerPowerCapability), + ConsumerConnected(&'device PSU, ConsumerPowerCapability), /// Provider disconnected - ProviderDisconnected(&'device D), + ProviderDisconnected(&'device PSU), /// Provider connected - ProviderConnected(&'device D, ProviderPowerCapability), + ProviderConnected(&'device PSU, ProviderPowerCapability), /// Unconstrained state changed Unconstrained(UnconstrainedState), } -impl<'device, D> Clone for Event<'device, D> +impl<'device, PSU> Clone for Event<'device, PSU> where - D: Lockable, - D::Inner: Psu, + PSU: Lockable, + PSU::Inner: Psu, { fn clone(&self) -> Self { *self } } -impl<'device, D> Copy for Event<'device, D> +impl<'device, PSU> Copy for Event<'device, PSU> where - D: Lockable, - D::Inner: Psu, + PSU: Lockable, + PSU::Inner: Psu, { } diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 2f0552f0..13d23611 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -32,7 +32,7 @@ env_logger = "0.11.8" log = { workspace = true } # TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile # Uncomment this line to enable log output in tests -#power-policy-service = { workspace = true, features = ["log"] } +# power-policy-service = { workspace = true, features = ["log"] } [features] default = [] diff --git a/power-policy-service/src/psu.rs b/power-policy-service/src/psu.rs index 47ce355d..81bd2133 100644 --- a/power-policy-service/src/psu.rs +++ b/power-policy-service/src/psu.rs @@ -7,7 +7,7 @@ use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::{Event, EventData}; /// Struct used to contain PSU event receivers and manage mapping from a receiver to its corresponding device. -pub struct EventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> +pub struct ArrayEventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> where PSU::Inner: Psu, { @@ -15,7 +15,7 @@ where pub receivers: [R; N], } -impl<'a, const N: usize, PSU: Lockable, R: Receiver> EventReceivers<'a, N, PSU, R> +impl<'a, const N: usize, PSU: Lockable, R: Receiver> ArrayEventReceivers<'a, N, PSU, R> where PSU::Inner: Psu, { diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index ea271d62..b52b0e2b 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -1,36 +1,32 @@ use core::cmp::Ordering; +use embedded_services::named::Named; use embedded_services::{debug, error}; use super::*; use power_policy_interface::capability::ConsumerFlags; use power_policy_interface::charger::Device as ChargerDevice; +use power_policy_interface::psu; use power_policy_interface::service::event::Event as ServiceEvent; use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::PsuState}; /// State of the current consumer #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AvailableConsumer<'device, D: Lockable> -where - D::Inner: Psu, -{ +pub struct AvailableConsumer<'device, Psu: Lockable> { /// Device reference - pub psu: &'device D, + pub psu: &'device Psu, /// The power capability of the currently connected consumer pub consumer_power_capability: ConsumerPowerCapability, } -impl<'device, D: Lockable> Clone for AvailableConsumer<'device, D> -where - D::Inner: Psu, -{ +impl<'device, Psu: Lockable> Clone for AvailableConsumer<'device, Psu> { fn clone(&self) -> Self { *self } } -impl<'device, D: Lockable> Copy for AvailableConsumer<'device, D> where D::Inner: Psu {} +impl<'device, Psu: Lockable> Copy for AvailableConsumer<'device, Psu> {} /// Compare two consumer capabilities to determine which one is better /// @@ -46,16 +42,13 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Iterate over all devices to determine what is best power port provides the highest power - async fn find_best_consumer(&self) -> Result>, Error> { + async fn find_best_consumer(&self) -> Result>, Error> { let mut best_consumer = None; let current_consumer = self.state.current_consumer_state.as_ref().map(|f| f.psu); - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let locked_psu = psu.lock().await; let consumer_capability = locked_psu.state().consumer_capability; // Don't consider consumers below minimum threshold @@ -108,7 +101,7 @@ where async fn update_unconstrained_state(&mut self) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { if let Some(capability) = psu.lock().await.state().consumer_capability { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; @@ -133,7 +126,10 @@ where } /// Common logic to execute after a consumer is connected - async fn post_consumer_connected(&mut self, connected_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + async fn post_consumer_connected( + &mut self, + connected_consumer: AvailableConsumer<'device, Reg::Psu>, + ) -> Result<(), Error> { self.state.current_consumer_state = Some(connected_consumer); // todo: review the delay time embassy_time::Timer::after_millis(800).await; @@ -191,7 +187,7 @@ where } /// Connect to a new consumer - async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'device, Reg::Psu>) -> Result<(), Error> { // Handle our current consumer if let Some(current_consumer) = self.state.current_consumer_state { if ptr::eq(current_consumer.psu, new_consumer.psu) diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 7ea2feee..3934bc34 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -5,9 +5,11 @@ pub mod config; pub mod consumer; pub mod context; pub mod provider; +pub mod registration; pub mod task; -use embedded_services::{error, info, sync::Lockable}; +use embedded_services::named::Named; +use embedded_services::{error, event::Sender, info, sync::Lockable}; use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, @@ -18,15 +20,17 @@ use power_policy_interface::{ service::{UnconstrainedState, event::Event as ServiceEvent}, }; +use crate::service::registration::Registration; + const MAX_CONNECTED_PROVIDERS: usize = 4; #[derive(Clone)] -struct InternalState<'device, D: Lockable> +struct InternalState<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Current consumer state, if any - current_consumer_state: Option>, + current_consumer_state: Option>, /// Current provider global state current_provider_state: provider::State, /// System unconstrained power @@ -35,9 +39,9 @@ where connected_providers: heapless::FnvIndexSet, } -impl Default for InternalState<'_, D> +impl Default for InternalState<'_, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { fn default() -> Self { Self { @@ -50,29 +54,23 @@ where } /// Power policy service -pub struct Service<'a, PSU: Lockable> -where - PSU::Inner: Psu, -{ +pub struct Service<'device, Reg: Registration<'device>> { + /// Service registration + registration: Reg, /// Power policy context - pub context: &'a context::Context, - /// PSU devices - psu_devices: &'a [&'a PSU], + pub context: &'device context::Context, /// State - state: InternalState<'a, PSU>, + state: InternalState<'device, Reg::Psu>, /// Config config: config::Config, } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Create a new power policy - pub fn new(psu_devices: &'a [&'a PSU], context: &'a context::Context, config: config::Config) -> Self { + pub fn new(registration: Reg, context: &'device context::Context, config: config::Config) -> Self { Self { + registration, context, - psu_devices, state: InternalState::default(), config, } @@ -82,7 +80,7 @@ where pub async fn compute_total_provider_power_mw(&self) -> u32 { let mut total = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let psu = psu.lock().await; total += psu .state() @@ -94,7 +92,7 @@ where total } - async fn process_notify_attach(&self, device: &PSU) { + async fn process_notify_attach(&self, device: &'device Reg::Psu) { let mut device = device.lock().await; info!("({}): Received notify attached", device.name()); if let Err(e) = device.state_mut().attach() { @@ -102,18 +100,21 @@ where } } - async fn process_notify_detach(&mut self, device: &PSU) -> Result<(), Error> { + async fn process_notify_detach(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { { let mut device = device.lock().await; info!("({}): Received notify detached", device.name()); device.state_mut().detach(); } - self.update_current_consumer().await + + self.post_provider_removed(device).await; + self.update_current_consumer().await?; + Ok(()) } async fn process_notify_consumer_power_capability( &mut self, - device: &PSU, + device: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { { @@ -137,7 +138,7 @@ where async fn process_request_provider_power_capabilities( &mut self, - requester: &'a PSU, + requester: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { { @@ -152,7 +153,7 @@ where .update_requested_provider_power_capability(capability) { error!( - "({}): Invalid state for notify consumer capability, catching up: {:#?}", + "({}): Invalid state for notify provider capability, catching up: {:#?}", requester.name(), e, ); @@ -162,54 +163,33 @@ where self.connect_provider(requester).await } - async fn process_notify_disconnect(&mut self, device: &'a PSU) -> Result<(), Error> { - let mut locked_device = device.lock().await; - info!("({}): Received notify disconnect", locked_device.name()); - - if let Err(e) = locked_device.state_mut().disconnect(true) { - error!( - "({}): Invalid state for notify disconnect, catching up: {:#?}", - locked_device.name(), - e, - ); - } - - if self - .state - .current_consumer_state - .as_ref() - .is_some_and(|current| ptr::eq(current.psu, device)) + async fn process_notify_disconnect(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { { - info!("({}): Connected consumer disconnected", locked_device.name()); - self.disconnect_chargers().await?; + let mut locked_device = device.lock().await; + info!("({}): Received notify disconnect", locked_device.name()); - self.broadcast_event(ServiceEvent::ConsumerDisconnected(device)).await; + if let Err(e) = locked_device.state_mut().disconnect(true) { + error!( + "({}): Invalid state for notify disconnect, catching up: {:#?}", + locked_device.name(), + e, + ); + } } - self.remove_connected_provider(device).await; + self.post_provider_removed(device).await; self.update_current_consumer().await?; Ok(()) } /// Send an event to all registered listeners - async fn broadcast_event(&mut self, _message: ServiceEvent<'a, PSU>) { - // TODO: Add this back as part of the migration away from comms - // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 - } - - /// Common logic for when a provider is disconnected - /// - /// Returns true if the device was operating as a provider - async fn remove_connected_provider(&mut self, psu: &'a PSU) -> bool { - if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { - self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; - true - } else { - false + async fn broadcast_event(&mut self, event: ServiceEvent<'device, Reg::Psu>) { + for sender in self.registration.event_senders() { + sender.send(event).await; } } - pub async fn process_psu_event(&mut self, event: PsuEvent<'a, PSU>) -> Result<(), Error> { + pub async fn process_psu_event(&mut self, event: PsuEvent<'device, Reg::Psu>) -> Result<(), Error> { let device = event.psu; match event.event { PsuEventData::Attached => { diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index dd174d15..02029e61 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -6,6 +6,7 @@ use core::ptr; use embedded_services::debug; +use embedded_services::named::Named; use super::*; @@ -27,12 +28,9 @@ pub(super) struct State { state: PowerState, } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&mut self, requester: &'a PSU) -> Result<(), Error> { + pub(super) async fn connect_provider(&mut self, requester: &'device Reg::Psu) -> Result<(), Error> { let requested_power_capability = { let requester = requester.lock().await; debug!("({}): Attempting to connect as provider", requester.name()); @@ -48,7 +46,7 @@ where // Determine total requested power draw let mut total_power_mw = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let target_provider_cap = if ptr::eq(*psu, requester) { // Use the requester's requested power capability // this handles both new connections and upgrade requests @@ -104,9 +102,45 @@ where } /// Common logic for after a provider has successfully connected - async fn post_provider_connected(&mut self, requester: &'a PSU, target_power: ProviderPowerCapability) { - let _ = self.state.connected_providers.insert(requester as *const PSU as usize); + async fn post_provider_connected(&mut self, requester: &'device Reg::Psu, target_power: ProviderPowerCapability) { + if self + .state + .connected_providers + .insert(requester as *const Reg::Psu as usize) + .is_err() + { + error!("Tracked providers set is full"); + } self.broadcast_event(ServiceEvent::ProviderConnected(requester, target_power)) .await; } + + /// Common logic for when a provider is removed + /// + /// Returns true if the device was operating as a provider + pub(super) async fn post_provider_removed(&mut self, psu: &'device Reg::Psu) -> bool { + if self + .state + .connected_providers + .remove(&(psu as *const Reg::Psu as usize)) + { + // Determine total requested power draw + let mut total_power_mw = 0; + for psu in self.registration.psus() { + let target_provider_cap = psu.lock().await.state().connected_provider_capability(); + 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 { + self.state.current_provider_state.state = PowerState::Limited; + } else { + self.state.current_provider_state.state = PowerState::Unlimited; + } + + self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; + true + } else { + false + } + } } diff --git a/power-policy-service/src/service/registration.rs b/power-policy-service/src/service/registration.rs new file mode 100644 index 00000000..e7de916e --- /dev/null +++ b/power-policy-service/src/service/registration.rs @@ -0,0 +1,48 @@ +//! Code related to registration with the power policy service. +use embedded_services::{event::Sender, sync::Lockable}; +use power_policy_interface::{psu, service::event::Event as ServiceEvent}; + +/// Registration trait that abstracts over various registration details. +pub trait Registration<'device> { + type Psu: Lockable + 'device; + type ServiceSender: Sender>; + + /// Returns a slice to access PSU devices + fn psus(&self) -> &[&'device Self::Psu]; + /// Returns a slice to access power policy event senders + fn event_senders(&mut self) -> &mut [Self::ServiceSender]; +} + +/// A registration implementation based around arrays +pub struct ArrayRegistration< + 'device, + Psu: Lockable + 'device, + const PSU_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> { + /// Array of registered PSUs + pub psus: [&'device Psu; PSU_COUNT], + /// Array of power policy service event senders + pub service_senders: [ServiceSender; SERVICE_SENDER_COUNT], +} + +impl< + 'device, + Psu: Lockable + 'device, + const PSU_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> Registration<'device> for ArrayRegistration<'device, Psu, PSU_COUNT, ServiceSender, SERVICE_SENDER_COUNT> +{ + type Psu = Psu; + type ServiceSender = ServiceSender; + + fn psus(&self) -> &[&'device Self::Psu] { + &self.psus + } + + fn event_senders(&mut self) -> &mut [Self::ServiceSender] { + &mut self.service_senders + } +} diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index d394957d..41c3e246 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,25 +1,23 @@ use embedded_services::{error, info, sync::Lockable}; use embedded_services::event::Receiver; -use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::EventData; +use crate::service::registration::Registration; + use super::Service; /// Runs the power policy task. pub async fn task< - 'a, + 'device, const PSU_COUNT: usize, - S: Lockable>, - PSU: Lockable, - R: Receiver, + S: Lockable>, + Reg: Registration<'device>, + PsuReceiver: Receiver, >( - mut psu_events: crate::psu::EventReceivers<'a, PSU_COUNT, PSU, R>, - policy: &'a S, -) -> ! -where - PSU::Inner: Psu, -{ + mut psu_events: crate::psu::ArrayEventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, + policy: &'device S, +) -> ! { info!("Starting power policy task"); loop { let event = psu_events.wait_event().await; diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index f117b73b..ab359787 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,8 +1,9 @@ #![allow(clippy::unwrap_used)] +#![allow(dead_code)] use embassy_sync::signal::Signal; -use embedded_services::{GlobalRawMutex, event::Sender, info}; +use embedded_services::{GlobalRawMutex, event::Sender, info, named::Named}; use power_policy_interface::{ - capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + capability::{ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability}, psu::{Error, Psu, State, event::EventData}, }; @@ -42,18 +43,40 @@ impl<'a, S: Sender> Mock<'a, S> { self.fn_call.signal((num_fn_calls + 1, fn_call)); } - pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { + pub async fn simulate_consumer_connection(&mut self, capability: ConsumerPowerCapability) { + self.sender.send(EventData::Attached).await; + self.sender + .send(EventData::UpdatedConsumerCapability(Some(capability))) + .await; + } + + pub async fn simulate_detach(&mut self) { + self.sender.send(EventData::Detached).await; + } + + pub async fn simulate_provider_connection(&mut self, capability: PowerCapability) { self.sender.send(EventData::Attached).await; - let capability = Some(ConsumerPowerCapability { + let capability = Some(ProviderPowerCapability { capability, - flags: ConsumerFlags::none(), + flags: ProviderFlags::none(), }); - self.sender.send(EventData::UpdatedConsumerCapability(capability)).await; + self.sender + .send(EventData::RequestedProviderCapability(capability)) + .await; } - pub async fn simulate_detach(&mut self) { - self.sender.send(EventData::Detached).await; + pub async fn simulate_disconnect(&mut self) { + self.sender.send(EventData::Disconnected).await; + } + + pub async fn simulate_update_requested_provider_power_capability( + &mut self, + capability: Option, + ) { + self.sender + .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) + .await } } @@ -83,7 +106,9 @@ impl<'a, S: Sender> Psu for Mock<'a, S> { fn state_mut(&mut self) -> &mut State { &mut self.state } +} +impl<'a, S: Sender> Named for Mock<'a, S> { fn name(&self) -> &'static str { self.name } diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 5ec32058..69261909 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -1,4 +1,8 @@ #![allow(clippy::unwrap_used)] +#![allow(dead_code)] +#![allow(clippy::panic)] +use std::mem::ManuallyDrop; + use embassy_futures::{ join::join, select::{Either, select}, @@ -11,10 +15,13 @@ use embassy_sync::{ }; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; -use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu::event::EventData; -use power_policy_service::psu::EventReceivers; +use power_policy_interface::{ + capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + service::{UnconstrainedState, event::Event as ServiceEvent}, +}; use power_policy_service::service::Service; +use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; pub mod mock; @@ -38,12 +45,21 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); const EVENT_CHANNEL_SIZE: usize = 4; pub type DeviceType<'a> = Mutex>>; -pub type ServiceType<'a> = Service<'a, DeviceType<'a>>; - -async fn power_policy_task<'a, const N: usize>( - completion_signal: &'a Signal, - mut power_policy: ServiceType<'a>, - mut event_receivers: EventReceivers<'a, N, DeviceType<'a>, DynamicReceiver<'a, EventData>>, +pub type ServiceType<'device, 'sender> = Service< + 'device, + ArrayRegistration< + 'device, + DeviceType<'device>, + 2, + DynamicSender<'sender, ServiceEvent<'device, DeviceType<'device>>>, + 1, + >, +>; + +async fn power_policy_task<'device, 'sender, const N: usize>( + completion_signal: &'device Signal, + mut power_policy: ServiceType<'device, 'sender>, + mut event_receivers: ArrayEventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { power_policy.process_psu_event(result).await.unwrap(); @@ -63,15 +79,16 @@ async fn power_policy_task<'a, const N: usize>( /// ``` /// However, `impl (Future + 'a)` is not real syntax. This could be done with the unstable feature type_alias_impl_trait, /// but we use this helper trait so as to not require use of nightly. -pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a>: - FnOnce(Arg0, Arg1, Arg2, Arg3) -> Self::Fut +pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a>: + FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Self::Fut { type Fut: Future; } -impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3> for F +impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3, Arg4> + for F where - F: FnOnce(Arg0, Arg1, Arg2, Arg3) -> Fut, + F: FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Fut, Fut: Future, { type Fut = Fut; @@ -81,9 +98,10 @@ pub async fn run_test(timeout: Duration, test: F) where for<'a> F: TestArgsFnOnce< 'a, - &'a Mutex>>, + DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + &'a DeviceType<'a>, &'a Signal, - &'a Mutex>>, + &'a DeviceType<'a>, &'a Signal, >, { @@ -109,11 +127,25 @@ where let device1 = Mutex::new(Mock::new("PSU1", device1_sender, &device1_signal)); let service_context = power_policy_service::service::context::Context::new(); - let psu_registration = [&device0, &device1]; let completion_signal = Signal::new(); + // Ideally F would have two lifetime arguments: 'device and 'sender because the event type requires 'device: 'sender. + // But Rust doesn't currently support syntax like `for<'device, 'sender> ... where 'device: 'sender`. So we just + // use a single lifetime. However, the unified lifetime makes the drop-checker think that dropping the channel + // could be unsafe. We use ManuallyDrop to disable the drop and make the drop-checker happy. None of the types + // here do any clean-up in their Drop impls so we don't have to worry about any sort of leaks. + let service_event_channel: ManuallyDrop< + Channel>, EVENT_CHANNEL_SIZE>, + > = ManuallyDrop::new(Channel::new()); + let service_receiver = service_event_channel.dyn_receiver(); + + let power_policy_registration = ArrayRegistration { + psus: [&device0, &device1], + service_senders: [service_event_channel.dyn_sender()], + }; + let power_policy = - power_policy_service::service::Service::new(psu_registration.as_slice(), &service_context, Default::default()); + power_policy_service::service::Service::new(power_policy_registration, &service_context, Default::default()); with_timeout( timeout, @@ -121,10 +153,10 @@ where power_policy_task( &completion_signal, power_policy, - EventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), + ArrayEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { - test(&device0, &device0_signal, &device1, &device1_signal).await; + test(service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; completion_signal.signal(()); }, ), @@ -132,3 +164,61 @@ where .await .unwrap(); } + +pub async fn assert_consumer_disconnected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, +) { + let ServiceEvent::ConsumerDisconnected(device) = receiver.receive().await else { + panic!("Expected ConsumerDisconnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); +} + +pub async fn assert_consumer_connected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, + expected_capability: ConsumerPowerCapability, +) { + let ServiceEvent::ConsumerConnected(device, capability) = receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); + assert_eq!(capability, expected_capability); +} + +pub async fn assert_provider_disconnected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, +) { + let ServiceEvent::ProviderDisconnected(device) = receiver.receive().await else { + panic!("Expected ProviderDisconnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); +} + +pub async fn assert_provider_connected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, + expected_capability: ProviderPowerCapability, +) { + let ServiceEvent::ProviderConnected(device, capability) = receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); + assert_eq!(capability, expected_capability); +} + +pub async fn assert_unconstrained<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_state: UnconstrainedState, +) { + let ServiceEvent::Unconstrained(state) = receiver.receive().await else { + panic!("Expected Unconstrained event"); + }; + assert_eq!(state, expected_state); +} + +pub fn assert_no_event<'a>(receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>) { + assert!(receiver.try_receive().is_err()); +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 94feac60..9fd5ac29 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,5 +1,6 @@ #![allow(clippy::unwrap_used)] -use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::info; @@ -8,27 +9,32 @@ use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability} mod common; use common::LOW_POWER; -use power_policy_interface::psu::event::EventData; +use power_policy_interface::service::event::Event as ServiceEvent; +use crate::common::DeviceType; +use crate::common::assert_no_event; use crate::common::{ - DEFAULT_TIMEOUT, HIGH_POWER, - mock::{FnCall, Mock}, - run_test, + DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, 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: &Mutex>>, +async fn test_single<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, device0_signal: &Signal, - _device1: &Mutex>>, + _device1: &DeviceType<'a>, _device1_signal: &Signal, ) { info!("Running test_single"); // Test initial connection { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -41,6 +47,16 @@ async fn test_single( ) ); device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } // Test detach { @@ -52,20 +68,29 @@ async fn test_single( Err(TimeoutError) ); device0_signal.reset(); + + assert_consumer_disconnected(service_receiver, device0).await; } + + assert_no_event(service_receiver); } /// Test swapping to a higher powered device. -async fn test_swap_higher( - device0: &Mutex>>, +async fn test_swap_higher<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, device0_signal: &Signal, - device1: &Mutex>>, + device1: &DeviceType<'a>, device1_signal: &Signal, ) { info!("Running test_swap_higher"); // Device0 connection at low power { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -78,10 +103,24 @@ async fn test_swap_higher( ) ); device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } // Device1 connection at high power { - device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -100,6 +139,19 @@ async fn test_swap_higher( ) ); device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } // Test detach device1, should reconnect device0 { @@ -122,16 +174,153 @@ async fn test_swap_higher( ) ); device0_signal.reset(); + + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } + + assert_no_event(service_receiver); +} + +/// Test a disconnect initiated by the current consumer. +async fn test_disconnect<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_disconnect"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + // Device1 connection at high power + { + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + + // Test disconnect device1, should reconnect device0 + { + device1.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Consume the disconnect event generated by `simulate_disconnect` + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + + assert_no_event(service_receiver); } #[tokio::test] -async fn run_all_tests() { +async fn run_test_swap_higher() { run_test(DEFAULT_TIMEOUT, test_swap_higher).await; } -/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. #[tokio::test] async fn run_test_single() { run_test(DEFAULT_TIMEOUT, test_single).await; } + +#[tokio::test] +async fn run_test_disconnect() { + run_test(DEFAULT_TIMEOUT, test_disconnect).await; +} diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs new file mode 100644 index 00000000..c5a78911 --- /dev/null +++ b/power-policy-service/tests/provider.rs @@ -0,0 +1,282 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; +use embassy_time::{Duration, TimeoutError, with_timeout}; +use embedded_services::GlobalRawMutex; +use embedded_services::info; +use power_policy_interface::capability::ProviderFlags; +use power_policy_interface::capability::ProviderPowerCapability; + +mod common; + +use common::LOW_POWER; +use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::common::DeviceType; +use crate::common::HIGH_POWER; +use crate::common::assert_no_event; +use crate::common::{DEFAULT_TIMEOUT, assert_provider_connected, assert_provider_disconnected, mock::FnCall, run_test}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test the basic provider flow with a single device. +async fn test_single<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_single"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + // Test detach + { + device0.lock().await.simulate_detach().await; + + // 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(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + assert_no_event(service_receiver); +} + +/// Test provider flow involving multiple devices and upgrading a provider's power capability. +async fn test_upgrade<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_upgrade"); + { + // Connect device0 at high power, default service config should allow this + device0.lock().await.simulate_provider_connection(HIGH_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + { + // Connect device1 at low power, default service config should allow this + device1.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + { + // Attempt to upgrade device1 to high power, power policy should reject this since device0 is already connected at high power + // Power policy will instead allow us to connect at low power + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + { + // Detach device0, this should allow us to upgrade device1 to high power + device0.lock().await.simulate_detach().await; + + // 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(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + { + // Attempt to upgrade device1 to high power should now succeed + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + assert_no_event(service_receiver); +} + +/// Test the provider disconnect flow +async fn test_disconnect<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_disconnect"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + // Test disconnect + { + device0.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + assert_no_event(service_receiver); +} + +#[tokio::test] +async fn run_test_single() { + run_test(DEFAULT_TIMEOUT, test_single).await; +} + +#[tokio::test] +async fn run_test_upgrade() { + run_test(DEFAULT_TIMEOUT, test_upgrade).await; +} + +#[tokio::test] +async fn run_test_disconnect() { + run_test(DEFAULT_TIMEOUT, test_disconnect).await; +} diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs new file mode 100644 index 00000000..2bea4385 --- /dev/null +++ b/power-policy-service/tests/unconstrained.rs @@ -0,0 +1,171 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; +use embassy_time::TimeoutError; +use embassy_time::{Duration, with_timeout}; +use embedded_services::GlobalRawMutex; +use embedded_services::info; +use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability}; + +mod common; + +use common::LOW_POWER; +use power_policy_interface::service::UnconstrainedState; +use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::common::DeviceType; +use crate::common::HIGH_POWER; +use crate::common::{ + DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_no_event, assert_unconstrained, + mock::FnCall, run_test, +}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test unconstrained consumer flow with multiple devices. +async fn test_unconstrained<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_unconstrained"); + { + // Connect device0, without unconstrained, + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + // Should not have any unconstrained events + assert!(service_receiver.try_receive().is_err()); + } + + { + // Connect device1 with unconstrained at HIGH_POWER to force power policy to select this consumer. + device1 + .lock() + .await + .simulate_consumer_connection(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }, + ) + .await; + + assert_unconstrained( + service_receiver, + UnconstrainedState { + unconstrained: true, + available: 1, + }, + ) + .await; + } + + { + // Test detach device1, unconstrained state should change + device1.lock().await.simulate_detach().await; + + // 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) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + assert_unconstrained( + service_receiver, + UnconstrainedState { + unconstrained: false, + available: 0, + }, + ) + .await; + } + + assert_no_event(service_receiver); +} + +#[tokio::test] +async fn run_test_unconstrained() { + run_test(DEFAULT_TIMEOUT, test_unconstrained).await; +} diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index 8470af87..050131dd 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -1,10 +1,9 @@ -use crate::type_c; -use crate::type_c::ATTN_VDM_LEN; -use crate::type_c::controller::{ - self, AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, +use crate::wrapper::controller::{ATTN_VDM_LEN, DpConfig, DpStatus, PdStateMachineConfig, RetimerFwUpdateState}; +use crate::wrapper::controller::{ + AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, }; -use crate::type_c::event::PortEvent; +use crate::wrapper::event::PortEvent; use ::tps6699x::registers::field_sets::IntEventBus1; use ::tps6699x::registers::{PdCcPullUp, PpExtVbusSw, PpIntVbusSw}; use ::tps6699x::{PORT0, PORT1, TPS66993_NUM_PORTS, TPS66994_NUM_PORTS}; @@ -36,8 +35,8 @@ use tps6699x::command::{ use tps6699x::fw_update::UpdateConfig as FwUpdateConfig; use tps6699x::registers::port_config::TypeCStateMachine; -use crate::type_c::power_capability_from_current; -use crate::type_c::power_capability_try_from_contract; +use crate::util::power_capability_from_current; +use crate::util::power_capability_try_from_contract; type Updater<'a, M, B> = BorrowedUpdaterInProgress>; @@ -409,10 +408,10 @@ impl Controller for Tps6699x<'_, M, B> { async fn get_rt_fw_update_status( &mut self, port: LocalPortId, - ) -> Result> { + ) -> Result> { match self.tps6699x.get_rt_fw_update_status(port).await { - Ok(true) => Ok(type_c::controller::RetimerFwUpdateState::Active), - Ok(false) => Ok(type_c::controller::RetimerFwUpdateState::Inactive), + Ok(true) => Ok(RetimerFwUpdateState::Active), + Ok(false) => Ok(RetimerFwUpdateState::Inactive), Err(e) => Err(e), } } @@ -658,7 +657,7 @@ impl Controller for Tps6699x<'_, M, B> { Ok(()) } - async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { + async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { let dp_status = self.tps6699x.get_dp_status(port).await?; debug!("Port{} DP status: {:#?}", port.0, dp_status); @@ -668,17 +667,13 @@ impl Controller for Tps6699x<'_, M, B> { let cfg_raw: PdDpPinConfig = dp_config.config_pin().into(); let pin_config: DpPinConfig = cfg_raw.into(); - Ok(controller::DpStatus { + Ok(DpStatus { alt_mode_entered, dfp_d_pin_cfg: pin_config, }) } - async fn set_dp_config( - &mut self, - port: LocalPortId, - config: controller::DpConfig, - ) -> Result<(), Error> { + async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), Error> { debug!("Port{} setting DP config: {:#?}", port.0, config); let mut dp_config_reg = self.tps6699x.get_dp_config(port).await?; @@ -717,7 +712,7 @@ impl Controller for Tps6699x<'_, M, B> { async fn set_pd_state_machine_config( &mut self, port: LocalPortId, - config: controller::PdStateMachineConfig, + config: PdStateMachineConfig, ) -> Result<(), Error> { debug!("Port{} setting PD state machine config: {:#?}", port.0, config); @@ -731,7 +726,7 @@ impl Controller for Tps6699x<'_, M, B> { async fn set_type_c_state_machine_config( &mut self, port: LocalPortId, - state: controller::TypeCStateMachineState, + state: TypeCStateMachineState, ) -> Result<(), Error> { debug!("Port{} setting Type-C state machine state: {:#?}", port.0, state); diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index c2bb5340..bdf68bb4 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -2,12 +2,12 @@ pub mod driver; pub mod service; pub mod task; -pub mod type_c; +pub mod util; pub mod wrapper; use core::future::Future; -use type_c::event::{PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged}; +use wrapper::event::{PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged}; /// Enum to contain all port event variants #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/type-c-service/src/service/context.rs b/type-c-service/src/service/context.rs new file mode 100644 index 00000000..2fdc5530 --- /dev/null +++ b/type-c-service/src/service/context.rs @@ -0,0 +1,534 @@ +use embassy_sync::signal::Signal; +use embassy_time::{Duration, with_timeout}; +use embedded_usb_pd::ucsi::{self, lpm}; +use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; + +use crate::service; +use crate::service::event::Event; +use crate::wrapper::controller::{ + AttnVdm, Command, ControllerStatus, Device, DpConfig, DpStatus, InternalCommandData, InternalResponseData, + OtherVdm, PdStateMachineConfig, PortCommand, PortCommandData, PortResponseData, PortStatus, Response, + RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, +}; +use crate::wrapper::controller::{Cached, ControllerId}; +use crate::wrapper::event::{PortEvent, PortPending}; +use embedded_services::{ + GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace, +}; + +/// Default command timeout +/// set to high value since this is intended to prevent an unresponsive device from blocking the service implementation +const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); + +/// Trait for types that contain a controller struct +pub trait DeviceContainer { + /// Get the controller struct + fn get_pd_controller_device(&self) -> &Device<'_>; +} + +impl DeviceContainer for Device<'_> { + fn get_pd_controller_device(&self) -> &Device<'_> { + self + } +} + +pub struct Context { + port_events: Signal, + /// Event broadcaster + broadcaster: broadcaster::Immediate, + /// Controller list + controllers: intrusive_list::IntrusiveList, +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +impl Context { + /// Create new Context + pub const fn new() -> Self { + Self { + port_events: Signal::new(), + broadcaster: broadcaster::Immediate::new(), + controllers: intrusive_list::IntrusiveList::new(), + } + } + + /// Notify that there are pending events on one or more ports + /// Each bit corresponds to a global port ID + pub fn notify_ports(&self, pending: PortPending) { + let raw_pending: u32 = pending.into(); + trace!("Notify ports: {:#x}", raw_pending); + // Early exit if no events + if pending.is_none() { + return; + } + + self.port_events + .signal(if let Some(flags) = self.port_events.try_take() { + flags.union(pending) + } else { + pending + }); + } + + /// Send a command to the given controller with no timeout + pub async fn send_controller_command_no_timeout( + &self, + controller_id: ControllerId, + command: InternalCommandData, + ) -> Result, PdError> { + let node = self + .controllers + .into_iter() + .find(|node| { + if let Some(controller) = node.data::() { + controller.id == controller_id + } else { + false + } + }) + .ok_or(PdError::InvalidController)?; + + match node + .data::() + .ok_or(PdError::InvalidController)? + .execute_command(Command::Controller(command)) + .await + { + Response::Controller(response) => response, + r => { + error!("Invalid response: expected controller, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send a command to the given controller with a timeout + pub async fn send_controller_command( + &self, + controller_id: ControllerId, + command: InternalCommandData, + ) -> Result, PdError> { + match with_timeout( + DEFAULT_TIMEOUT, + self.send_controller_command_no_timeout(controller_id, command), + ) + .await + { + Ok(response) => response, + Err(_) => Err(PdError::Timeout), + } + } + + /// Reset the given controller + pub async fn reset_controller(&self, controller_id: ControllerId) -> Result<(), PdError> { + self.send_controller_command(controller_id, InternalCommandData::Reset) + .await + .map(|_| ()) + } + + fn find_node_by_port(&self, port_id: GlobalPortId) -> Result<&IntrusiveNode, PdError> { + self.controllers + .into_iter() + .find(|node| { + if let Some(controller) = node.data::() { + controller.has_port(port_id) + } else { + false + } + }) + .ok_or(PdError::InvalidPort) + } + + /// Send a command to the given port + pub async fn send_port_command_ucsi_no_timeout( + &self, + port_id: GlobalPortId, + command: lpm::CommandData, + ) -> Result { + let node = self.find_node_by_port(port_id)?; + + match node + .data::() + .ok_or(PdError::InvalidController)? + .execute_command(Command::Lpm(lpm::Command::new(port_id, command))) + .await + { + Response::Ucsi(response) => Ok(response), + r => { + error!("Invalid response: expected LPM, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send a command to the given port with a timeout + pub async fn send_port_command_ucsi( + &self, + port_id: GlobalPortId, + command: lpm::CommandData, + ) -> Result { + match with_timeout( + DEFAULT_TIMEOUT, + self.send_port_command_ucsi_no_timeout(port_id, command), + ) + .await + { + Ok(response) => response, + Err(_) => Err(PdError::Timeout), + } + } + + /// Send a command to the given port with no timeout + pub async fn send_port_command_no_timeout( + &self, + port_id: GlobalPortId, + command: PortCommandData, + ) -> Result { + let node = self.find_node_by_port(port_id)?; + + match node + .data::() + .ok_or(PdError::InvalidController)? + .execute_command(Command::Port(PortCommand { + port: port_id, + data: command, + })) + .await + { + Response::Port(response) => response, + r => { + error!("Invalid response: expected port, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send a command to the given port with a timeout + pub async fn send_port_command( + &self, + port_id: GlobalPortId, + command: PortCommandData, + ) -> Result { + match with_timeout(DEFAULT_TIMEOUT, self.send_port_command_no_timeout(port_id, command)).await { + Ok(response) => response, + Err(_) => Err(PdError::Timeout), + } + } + + /// Get the current port events + pub async fn get_unhandled_events(&self) -> PortPending { + self.port_events.wait().await + } + + /// Get the unhandled events for the given port + pub async fn get_port_event(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::ClearEvents).await? { + PortResponseData::ClearEvents(event) => Ok(event), + r => { + error!("Invalid response: expected clear events, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the current port status + pub async fn get_port_status(&self, port: GlobalPortId, cached: Cached) -> Result { + match self + .send_port_command(port, PortCommandData::PortStatus(cached)) + .await? + { + PortResponseData::PortStatus(status) => Ok(status), + r => { + error!("Invalid response: expected port status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the oldest unhandled PD alert for the given port + pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { + match self.send_port_command(port, PortCommandData::GetPdAlert).await? { + PortResponseData::PdAlert(alert) => Ok(alert), + r => { + error!("Invalid response: expected PD alert, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the retimer fw update status + pub async fn get_rt_fw_update_status(&self, port: GlobalPortId) -> Result { + match self + .send_port_command(port, PortCommandData::RetimerFwUpdateGetState) + .await? + { + PortResponseData::RtFwUpdateStatus(status) => Ok(status), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set the retimer fw update state + pub async fn set_rt_fw_update_state(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::RetimerFwUpdateSetState) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Clear the retimer fw update state + pub async fn clear_rt_fw_update_state(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::RetimerFwUpdateClearState) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set the retimer compliance + pub async fn set_rt_compliance(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetRetimerCompliance) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Reconfigure the retimer for the given port. + pub async fn reconfigure_retimer(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::ReconfigureRetimer) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set the maximum sink voltage for the given port. + /// + /// See [`PortCommandData::SetMaxSinkVoltage`] for details on the `max_voltage_mv` parameter. + pub async fn set_max_sink_voltage(&self, port: GlobalPortId, max_voltage_mv: Option) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetMaxSinkVoltage(max_voltage_mv)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Clear the dead battery flag for the given port. + pub async fn clear_dead_battery_flag(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::ClearDeadBatteryFlag) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Get current controller status + pub async fn get_controller_status( + &self, + controller_id: ControllerId, + ) -> Result, PdError> { + match self + .send_controller_command(controller_id, InternalCommandData::Status) + .await? + { + InternalResponseData::Status(status) => Ok(status), + r => { + error!("Invalid response: expected controller status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Set unconstrained power for the given port + pub async fn set_unconstrained_power(&self, port: GlobalPortId, unconstrained: bool) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetUnconstrainedPower(unconstrained)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Sync controller state + pub async fn sync_controller_state(&self, controller_id: ControllerId) -> Result<(), PdError> { + match self + .send_controller_command(controller_id, InternalCommandData::SyncState) + .await? + { + InternalResponseData::Complete => Ok(()), + r => { + error!("Invalid response: expected controller status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the other vdm for the given port + pub async fn get_other_vdm(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::GetOtherVdm).await? { + PortResponseData::OtherVdm(vdm) => Ok(vdm), + r => { + error!("Invalid response: expected other VDM, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the attention vdm for the given port + pub async fn get_attn_vdm(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::GetAttnVdm).await? { + PortResponseData::AttnVdm(vdm) => Ok(vdm), + r => { + error!("Invalid response: expected attention VDM, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send VDM to the given port + pub async fn send_vdm(&self, port: GlobalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { + match self.send_port_command(port, PortCommandData::SendVdm(tx_vdm)).await? { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set USB control configuration for the given port + pub async fn set_usb_control(&self, port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetUsbControl(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Get DisplayPort status for the given port + pub async fn get_dp_status(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::GetDpStatus).await? { + PortResponseData::DpStatus(status) => Ok(status), + r => { + error!("Invalid response: expected DP status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Set DisplayPort configuration for the given port + pub async fn set_dp_config(&self, port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetDpConfig(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Execute PD Data Reset for the given port + pub async fn execute_drst(&self, port: GlobalPortId) -> Result<(), PdError> { + match self.send_port_command(port, PortCommandData::ExecuteDrst).await? { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set Thunderbolt configuration for the given port + pub async fn set_tbt_config(&self, port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetTbtConfig(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set PD state-machine configuration for the given port + pub async fn set_pd_state_machine_config( + &self, + port: GlobalPortId, + config: PdStateMachineConfig, + ) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetPdStateMachineConfig(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set Type-C state-machine configuration for the given port + pub async fn set_type_c_state_machine_config( + &self, + port: GlobalPortId, + state: TypeCStateMachineState, + ) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetTypeCStateMachineConfig(state)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Execute the given UCSI command + pub async fn execute_ucsi_command( + &self, + command: lpm::GlobalCommand, + ) -> Result, PdError> { + match self + .send_port_command(command.port(), PortCommandData::ExecuteUcsiCommand(command.operation())) + .await? + { + PortResponseData::UcsiResponse(response) => response, + _ => Err(PdError::InvalidResponse), + } + } + + /// Register a message receiver for type-C messages + pub async fn register_message_receiver( + &self, + receiver: &'static broadcaster::Receiver<'_, service::event::Event>, + ) -> intrusive_list::Result<()> { + self.broadcaster.register_receiver(receiver) + } + + /// Broadcast a type-C message to all subscribers + pub async fn broadcast_message(&self, message: service::event::Event) { + self.broadcaster.broadcast(message).await; + } + + /// Register a PD controller + pub fn register_controller(&self, controller: &'static impl DeviceContainer) -> Result<(), intrusive_list::Error> { + self.controllers.push(controller.get_pd_controller_device()) + } + + /// Get total number of ports on the system + pub(super) fn get_num_ports(&self) -> usize { + self.controllers + .iter_only::() + .fold(0, |acc, controller| acc + controller.num_ports()) + } +} diff --git a/type-c-service/src/service/controller.rs b/type-c-service/src/service/controller.rs deleted file mode 100644 index 74debf8c..00000000 --- a/type-c-service/src/service/controller.rs +++ /dev/null @@ -1,61 +0,0 @@ -use embedded_services::{debug, error}; - -use super::*; -use crate::type_c::{ - ControllerId, - external::{self, ControllerCommandData}, -}; - -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ - /// Process external controller status command - pub(super) async fn process_external_controller_status( - &self, - controller: ControllerId, - ) -> external::Response<'static> { - let status = self.context.get_controller_status(self.controllers, controller).await; - if let Err(e) = status { - error!("Error getting controller status: {:#?}", e); - } - external::Response::Controller(status.map(external::ControllerResponseData::ControllerStatus)) - } - - /// Process external controller sync state command - pub(super) async fn process_external_controller_sync_state( - &self, - controller: ControllerId, - ) -> external::Response<'static> { - let status = self.context.sync_controller_state(self.controllers, controller).await; - if let Err(e) = status { - error!("Error getting controller sync state: {:#?}", e); - } - external::Response::Controller(status.map(|_| external::ControllerResponseData::Complete)) - } - - /// Process external controller reset command - pub(super) async fn process_external_controller_reset( - &self, - controller: ControllerId, - ) -> external::Response<'static> { - let status = self.context.reset_controller(self.controllers, controller).await; - if let Err(e) = status { - error!("Error resetting controller: {:#?}", e); - } - external::Response::Controller(status.map(|_| external::ControllerResponseData::Complete)) - } - - /// Process external controller commands - pub(super) async fn process_external_controller_command( - &self, - command: &external::ControllerCommand, - ) -> external::Response<'static> { - debug!("Processing external controller command: {:#?}", command); - match command.data { - ControllerCommandData::ControllerStatus => self.process_external_controller_status(command.id).await, - ControllerCommandData::SyncState => self.process_external_controller_sync_state(command.id).await, - ControllerCommandData::Reset => self.process_external_controller_reset(command.id).await, - } - } -} diff --git a/type-c-service/src/type_c/comms.rs b/type-c-service/src/service/event.rs similarity index 88% rename from type-c-service/src/type_c/comms.rs rename to type-c-service/src/service/event.rs index 9a998cd3..800d1555 100644 --- a/type-c-service/src/type_c/comms.rs +++ b/type-c-service/src/service/event.rs @@ -5,7 +5,7 @@ use embedded_usb_pd::GlobalPortId; /// Message generated when a debug acessory is connected or disconnected #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DebugAccessoryMessage { +pub struct DebugAccessory { /// Port pub port: GlobalPortId, /// Connected @@ -25,9 +25,9 @@ pub struct UsciChangeIndicator { /// Top-level comms message #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CommsMessage { +pub enum Event { /// Debug accessory message - DebugAccessory(DebugAccessoryMessage), + DebugAccessory(DebugAccessory), /// UCSI CCI message UcsiCci(UsciChangeIndicator), } diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 68124cc5..797ea81a 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -1,24 +1,22 @@ -use embassy_futures::select::{Either3, select3}; +use embassy_futures::select::{Either, select}; use embassy_sync::{ mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber}, }; -use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, ipc::deferred, sync::Lockable, trace}; +use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, sync::Lockable, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; use power_policy_interface::psu; -use crate::type_c::{ - self, Cached, comms, - controller::PortStatus, - event::{PortNotificationSingle, PortStatusChanged}, - external, -}; +use crate::wrapper::controller::{Cached, PortStatus}; + +use crate::wrapper::event::{PortNotificationSingle, PortStatusChanged}; use crate::{PortEventStreamer, PortEventVariant}; pub mod config; -mod controller; +pub mod context; +pub mod event; pub mod pd; mod port; mod power; @@ -47,9 +45,7 @@ where PSU::Inner: psu::Psu, { /// Type-C context - context: &'a type_c::controller::Context, - /// Controller intrusive list - controllers: &'a intrusive_list::IntrusiveList, + pub(crate) context: &'a context::Context, /// Current state state: Mutex, /// Config @@ -72,6 +68,7 @@ where // This is present instead of just using [`power_policy::CommsMessage`] to allow for // supporting variants like `ConsumerConnected(GlobalPortId, ConsumerPowerCapability)` // But there's currently not a way to do look-ups between power policy device IDs and GlobalPortIds +#[derive(Copy, Clone)] pub enum PowerPolicyEvent { /// Unconstrained state changed Unconstrained(power_policy_interface::service::UnconstrainedState), @@ -82,13 +79,12 @@ pub enum PowerPolicyEvent { } /// Type-C service events -pub enum Event<'a> { +#[derive(Copy, Clone)] +pub enum Event { /// Port event PortStatusChanged(GlobalPortId, PortStatusChanged, PortStatus), /// A controller notified of an event that occurred. PortNotification(GlobalPortId, PortNotificationSingle), - /// External command - ExternalCommand(deferred::Request<'a, GlobalRawMutex, external::Command, external::Response<'static>>), /// Power policy event PowerPolicy(PowerPolicyEvent), } @@ -111,7 +107,6 @@ where config, _power_policy_event_publisher: power_policy_publisher.into(), power_policy_event_subscriber: Mutex::new(power_policy_subscriber), - controllers: controller_list, } } @@ -154,7 +149,7 @@ where } self.context - .broadcast_message(comms::CommsMessage::DebugAccessory(comms::DebugAccessoryMessage { + .broadcast_message(event::Event::DebugAccessory(event::DebugAccessory { port: port_id, connected: status.is_connected(), })) @@ -167,33 +162,13 @@ where Ok(()) } - /// Process external commands - async fn process_external_command(&self, command: &external::Command) -> external::Response<'static> { - match command { - external::Command::Controller(command) => self.process_external_controller_command(command).await, - external::Command::Port(command) => self.process_external_port_command(command, self.controllers).await, - external::Command::Ucsi(command) => { - external::Response::Ucsi(self.process_ucsi_command(self.controllers, command).await) - } - } - } - /// Wait for the next event - pub async fn wait_next(&self) -> Result, Error> { + pub async fn wait_next(&self) -> Result { loop { - match select3( - self.wait_port_flags(), - self.context.wait_external_command(), - self.wait_power_policy_event(), - ) - .await - { - Either3::First(mut stream) => { + match select(self.wait_port_flags(), self.wait_power_policy_event()).await { + Either::First(mut stream) => { if let Some((port_id, event)) = stream - .next(|port_id| { - self.context - .get_port_event(self.controllers, GlobalPortId(port_id as u8)) - }) + .next(|port_id| self.context.get_port_event(GlobalPortId(port_id as u8))) .await? { let port_id = GlobalPortId(port_id as u8); @@ -201,10 +176,7 @@ where match event { PortEventVariant::StatusChanged(status_event) => { // Return a port status changed event - let status = self - .context - .get_port_status(self.controllers, port_id, Cached(true)) - .await?; + let status = self.context.get_port_status(port_id, Cached(true)).await?; return Ok(Event::PortStatusChanged(port_id, status_event, status)); } PortEventVariant::Notification(notification) => { @@ -217,16 +189,13 @@ where self.state.lock().await.port_event_streaming_state = None; } } - Either3::Second(request) => { - return Ok(Event::ExternalCommand(request)); - } - Either3::Third(event) => return Ok(event), + Either::Second(event) => return Ok(event), } } } /// Process the given event - pub async fn process_event(&self, event: Event<'_>) -> Result<(), Error> { + pub async fn process_event(&self, event: Event) -> Result<(), Error> { match event { Event::PortStatusChanged(port, event_kind, status) => { trace!("Port{}: Processing port status changed", port.0); @@ -237,12 +206,6 @@ where info!("Port{}: Got port notification: {:?}", port.0, notification); Ok(()) } - Event::ExternalCommand(request) => { - trace!("Processing external command"); - let response = self.process_external_command(&request.command).await; - request.respond(response); - Ok(()) - } Event::PowerPolicy(event) => { trace!("Processing power policy event"); self.process_power_policy_event(&event).await @@ -256,7 +219,17 @@ where self.process_event(event).await } +<<<<<<< HEAD pub(crate) fn controllers(&self) -> &'a intrusive_list::IntrusiveList { self.controllers +======= + /// Register the Type-C service with the power policy service + pub fn register_comms( + &'static self, + _power_policy_context: &power_policy_service::service::context::Context, + ) -> Result<(), intrusive_list::Error> { + // TODO + Ok(()) +>>>>>>> d141e7e (type-c-service: Clean up module structure) } } diff --git a/type-c-service/src/service/pd.rs b/type-c-service/src/service/pd.rs index 2070dbbc..b0d940ee 100644 --- a/type-c-service/src/service/pd.rs +++ b/type-c-service/src/service/pd.rs @@ -1,6 +1,6 @@ //! Power Delivery (PD) related functionality. -use embedded_services::{intrusive_list, sync::Lockable}; +use embedded_services::sync::Lockable; use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; use power_policy_interface::psu; @@ -13,11 +13,7 @@ where /// Get the oldest unhandled PD alert for the given port. /// /// Returns [`None`] if no alerts are pending. - pub async fn get_pd_alert( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result, PdError> { - self.context.get_pd_alert(controllers, port).await + pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { + self.context.get_pd_alert(port).await } } diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs index ccf1bdc4..b1aa047e 100644 --- a/type-c-service/src/service/port.rs +++ b/type-c-service/src/service/port.rs @@ -1,15 +1,6 @@ -use embedded_services::{debug, error}; -use embedded_usb_pd::GlobalPortId; - use super::*; use crate::PortEventStreamer; -use crate::type_c::controller::SendVdm; -use crate::type_c::{ - controller::{DpConfig, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, - external, -}; - impl<'a, PSU: Lockable> Service<'a, PSU> where PSU::Inner: psu::Psu, @@ -26,301 +17,4 @@ where PortEventStreamer::new(self.context.get_unhandled_events().await.into_iter()) } } - - /// Process external port commands - pub(super) async fn process_external_port_command( - &self, - command: &external::PortCommand, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - debug!("Processing external port command: {:#?}", command); - match command.data { - external::PortCommandData::PortStatus(cached) => { - self.process_external_port_status(command.port, cached, controllers) - .await - } - external::PortCommandData::RetimerFwUpdateGetState => { - self.process_get_rt_fw_update_status(command.port, controllers).await - } - external::PortCommandData::RetimerFwUpdateSetState => { - self.process_set_rt_fw_update_state(command.port, controllers).await - } - external::PortCommandData::RetimerFwUpdateClearState => { - self.process_clear_rt_fw_update_state(command.port, controllers).await - } - external::PortCommandData::SetRetimerCompliance => { - self.process_set_rt_compliance(command.port, controllers).await - } - external::PortCommandData::ReconfigureRetimer => { - self.process_reconfigure_retimer(command.port, controllers).await - } - external::PortCommandData::SetMaxSinkVoltage { max_voltage_mv } => { - self.process_set_max_sink_voltage(command.port, max_voltage_mv, controllers) - .await - } - external::PortCommandData::ClearDeadBatteryFlag => { - self.process_clear_dead_battery_flag(command.port, controllers).await - } - external::PortCommandData::SendVdm(tx_vdm) => { - self.process_send_vdm(command.port, tx_vdm, controllers).await - } - external::PortCommandData::SetUsbControl(config) => { - self.process_set_usb_control(command.port, config, controllers).await - } - external::PortCommandData::GetDpStatus => self.process_get_dp_status(command.port, controllers).await, - external::PortCommandData::SetDpConfig(config) => { - self.process_set_dp_config(command.port, config, controllers).await - } - external::PortCommandData::ExecuteDrst => self.process_execute_drst(command.port, controllers).await, - external::PortCommandData::SetTbtConfig(config) => { - self.process_set_tbt_config(command.port, config, controllers).await - } - external::PortCommandData::SetPdStateMachineConfig(config) => { - self.process_set_pd_state_machine_config(command.port, config, controllers) - .await - } - external::PortCommandData::SetTypeCStateMachineConfig(state) => { - self.process_set_type_c_state_machine_config(command.port, state, controllers) - .await - } - } - } - - /// Process external port status command - pub(super) async fn process_external_port_status( - &self, - port_id: GlobalPortId, - cached: Cached, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.get_port_status(controllers, port_id, cached).await; - if let Err(e) = status { - error!("Error getting port status: {:#?}", e); - } - external::Response::Port(status.map(external::PortResponseData::PortStatus)) - } - - /// Process get retimer fw update status commands - pub(super) async fn process_get_rt_fw_update_status( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.get_rt_fw_update_status(controllers, port_id).await; - if let Err(e) = status { - error!("Error getting retimer fw update status: {:#?}", e); - } - - external::Response::Port(status.map(external::PortResponseData::RetimerFwUpdateGetState)) - } - - /// Process set retimer fw update state commands - pub(super) async fn process_set_rt_fw_update_state( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_rt_fw_update_state(controllers, port_id).await; - if let Err(e) = status { - error!("Error setting retimer fw update state: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process clear retimer fw update state commands - pub(super) async fn process_clear_rt_fw_update_state( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.clear_rt_fw_update_state(controllers, port_id).await; - if let Err(e) = status { - error!("Error clear retimer fw update state: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set retimer compliance - pub(super) async fn process_set_rt_compliance( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_rt_compliance(controllers, port_id).await; - if let Err(e) = status { - error!("Error set retimer compliance: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - async fn process_reconfigure_retimer( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.reconfigure_retimer(controllers, port_id).await; - if let Err(e) = status { - error!("Error reconfiguring retimer: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - async fn process_set_max_sink_voltage( - &self, - port_id: GlobalPortId, - max_voltage_mv: Option, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self - .context - .set_max_sink_voltage(controllers, port_id, max_voltage_mv) - .await; - if let Err(e) = status { - error!("Error setting max voltage: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - async fn process_clear_dead_battery_flag( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.clear_dead_battery_flag(controllers, port_id).await; - if let Err(e) = status { - error!("Error clearing dead battery flag: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process send vdm commands - /// Process send vdm commands - async fn process_send_vdm( - &self, - port_id: GlobalPortId, - tx_vdm: SendVdm, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.send_vdm(controllers, port_id, tx_vdm).await; - if let Err(e) = status { - error!("Error sending VDM data: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set USB control commands - async fn process_set_usb_control( - &self, - port_id: GlobalPortId, - config: UsbControlConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_usb_control(controllers, port_id, config).await; - if let Err(e) = status { - error!("Error setting USB control: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process get DisplayPort status commands - async fn process_get_dp_status( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.get_dp_status(controllers, port_id).await; - if let Err(e) = status { - error!("Error getting DP status: {:#?}", e); - } - - external::Response::Port(status.map(external::PortResponseData::GetDpStatus)) - } - - /// Process set DisplayPort config commands - async fn process_set_dp_config( - &self, - port_id: GlobalPortId, - config: DpConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_dp_config(controllers, port_id, config).await; - if let Err(e) = status { - error!("Error setting DP config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process execute DisplayPort reset commands - async fn process_execute_drst( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.execute_drst(controllers, port_id).await; - if let Err(e) = status { - error!("Error executing DP reset: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set Thunderbolt configuration command - async fn process_set_tbt_config( - &self, - port_id: GlobalPortId, - config: TbtConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_tbt_config(controllers, port_id, config).await; - if let Err(e) = status { - error!("Error setting TBT config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set PD state-machine configuration command - async fn process_set_pd_state_machine_config( - &self, - port_id: GlobalPortId, - config: PdStateMachineConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self - .context - .set_pd_state_machine_config(controllers, port_id, config) - .await; - if let Err(e) = status { - error!("Error setting PD state-machine config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set Type-C state-machine configuration command - async fn process_set_type_c_state_machine_config( - &self, - port_id: GlobalPortId, - state: TypeCStateMachineState, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self - .context - .set_type_c_state_machine_config(controllers, port_id, state) - .await; - if let Err(e) = status { - error!("Error setting Type-C state-machine config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } } diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index ba16485e..68e0ec5c 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -8,7 +8,7 @@ where PSU::Inner: psu::Psu, { /// Wait for a power policy event - pub(super) async fn wait_power_policy_event(&self) -> Event<'_> { + pub(super) async fn wait_power_policy_event(&self) -> Event { loop { match self.power_policy_event_subscriber.lock().await.next_message().await { WaitResult::Lagged(lagged) => { @@ -35,9 +35,9 @@ where /// Set the unconstrained state for all ports pub(super) async fn set_unconstrained_all(&self, unconstrained: bool) -> Result<(), Error> { - for port_index in 0..self.context.get_num_ports(self.controllers) { + for port_index in 0..self.context.get_num_ports() { self.context - .set_unconstrained_power(self.controllers, GlobalPortId(port_index as u8), unconstrained) + .set_unconstrained_power(GlobalPortId(port_index as u8), unconstrained) .await?; } Ok(()) @@ -60,7 +60,7 @@ where self.set_unconstrained_all(true).await?; } else { // Only one unconstrained device is present, see if that's one of our ports - let num_ports = self.context.get_num_ports(self.controllers); + let num_ports = self.context.get_num_ports(); let unconstrained_port = state .port_status .iter() @@ -77,11 +77,7 @@ where ); for port_index in 0..num_ports { self.context - .set_unconstrained_power( - self.controllers, - GlobalPortId(port_index as u8), - port_index != unconstrained_index, - ) + .set_unconstrained_power(GlobalPortId(port_index as u8), port_index != unconstrained_index) .await?; } } else { diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index c07c90ce..84b6813b 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -10,6 +10,18 @@ use embedded_usb_pd::{PdError, PowerRole}; use super::*; +/// UCSI command response +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UcsiResponse { + /// Notify the OPM, the function call + pub notify_opm: bool, + /// Response CCI + pub cci: GlobalCci, + /// UCSI response data + pub data: Result, PdError>, +} + /// UCSI state #[derive(Default)] pub(super) struct State { @@ -47,10 +59,11 @@ where } /// PPM get capabilities implementation - fn process_get_capabilities(&self, controllers: &intrusive_list::IntrusiveList) -> ppm::ResponseData { + fn process_get_capabilities(&self) -> ppm::ResponseData { debug!("Get PPM capabilities: {:?}", self.config.ucsi_capabilities); let mut capabilities = self.config.ucsi_capabilities; - capabilities.num_connectors = external::get_num_ports(controllers) as u8; + // TODO: implement this when the refactoring stabilizes + capabilities.num_connectors = 0; ppm::ResponseData::GetCapability(capabilities) } @@ -58,14 +71,13 @@ where &self, state: &mut State, command: &ucsi::ppm::Command, - controllers: &intrusive_list::IntrusiveList, ) -> Result, PdError> { match command { ppm::Command::SetNotificationEnable(enable) => { self.process_set_notification_enable(state, enable.notification_enable); Ok(None) } - ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities(controllers))), + ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities())), _ => Ok(None), // Other commands are currently no-ops } } @@ -101,7 +113,6 @@ where &self, state: &mut super::State, command: &ucsi::lpm::GlobalCommand, - controllers: &intrusive_list::IntrusiveList, ) -> Result, PdError> { debug!("Processing LPM command: {:?}", command); match command.operation() { @@ -110,11 +121,11 @@ where if let Some(capabilities) = &self.config.ucsi_port_capabilities { Ok(Some(lpm::ResponseData::GetConnectorCapability(*capabilities))) } else { - self.context.execute_ucsi_command(controllers, *command).await + self.context.execute_ucsi_command(*command).await } } lpm::CommandData::GetConnectorStatus => { - let mut response = self.context.execute_ucsi_command(controllers, *command).await; + let mut response = self.context.execute_ucsi_command(*command).await; if let Ok(Some(lpm::ResponseData::GetConnectorStatus(lpm::get_connector_status::ResponseData { status_change: ref mut states_change, status: @@ -134,7 +145,7 @@ where response } - _ => self.context.execute_ucsi_command(controllers, *command).await, + _ => self.context.execute_ucsi_command(*command).await, } } @@ -156,7 +167,7 @@ where if let Some(next_port) = state.pending_ports.front() { debug!("ACK_CCI processed, next pending port: {:?}", next_port); self.context - .broadcast_message(comms::CommsMessage::UcsiCci(comms::UsciChangeIndicator { + .broadcast_message(event::Event::UcsiCci(event::UsciChangeIndicator { port: *next_port, // False here because the OPM gets notified by the CCI, don't need a separate notification notify_opm: false, @@ -173,14 +184,10 @@ where } /// Process an external UCSI command - pub(super) async fn process_ucsi_command( - &self, - controllers: &intrusive_list::IntrusiveList, - command: &GlobalCommand, - ) -> external::UcsiResponse { + pub async fn process_ucsi_command(&self, command: &GlobalCommand) -> UcsiResponse { let state = &mut self.state.lock().await; let mut next_input = Some(PpmInput::Command(command)); - let mut response: external::UcsiResponse = external::UcsiResponse { + let mut response = UcsiResponse { notify_opm: false, cci: Cci::default(), data: Ok(None), @@ -194,7 +201,7 @@ where state.ucsi.ppm_state_machine.consume(next_input) } else { error!("Unexpected end of state machine processing"); - return external::UcsiResponse { + return UcsiResponse { notify_opm: true, cci: Cci::new_error(), data: Err(PdError::InvalidMode), @@ -205,7 +212,7 @@ where Ok(output) => output, Err(e @ InvalidTransition { .. }) => { error!("PPM state machine transition failed: {:#?}", e); - return external::UcsiResponse { + return UcsiResponse { notify_opm: true, cci: Cci::new_error(), data: Err(PdError::Failed), @@ -221,12 +228,12 @@ where match command { ucsi::GlobalCommand::PpmCommand(ppm_command) => { response.data = self - .process_ppm_command(&mut state.ucsi, ppm_command, controllers) + .process_ppm_command(&mut state.ucsi, ppm_command) .map(|inner| inner.map(ResponseData::Ppm)); } ucsi::GlobalCommand::LpmCommand(lpm_command) => { response.data = self - .process_lpm_command(state, lpm_command, controllers) + .process_lpm_command(state, lpm_command) .await .map(|inner| inner.map(ResponseData::Lpm)); } @@ -352,7 +359,7 @@ where let notify_opm = state.pending_ports.is_empty(); if state.pending_ports.push_back(port_id).is_ok() { self.context - .broadcast_message(comms::CommsMessage::UcsiCci(comms::UsciChangeIndicator { + .broadcast_message(event::Event::UcsiCci(event::UsciChangeIndicator { port: port_id, notify_opm, })) diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index ca4770e5..b20be28e 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,7 +1,7 @@ //! VDM (Vendor Defined Messages) related functionality. -use crate::type_c::controller::{AttnVdm, OtherVdm}; -use embedded_services::{intrusive_list, sync::Lockable}; +use crate::wrapper::controller::{AttnVdm, OtherVdm}; +use embedded_services::sync::Lockable; use embedded_usb_pd::{GlobalPortId, PdError}; use power_policy_interface::psu; @@ -12,20 +12,12 @@ where PSU::Inner: psu::Psu, { /// Get the other vdm for the given port - pub async fn get_other_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - ) -> Result { - self.context.get_other_vdm(controllers, port_id).await + pub async fn get_other_vdm(&self, port_id: GlobalPortId) -> Result { + self.context.get_other_vdm(port_id).await } /// Get the attention vdm for the given port - pub async fn get_attn_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - ) -> Result { - self.context.get_attn_vdm(controllers, port_id).await + pub async fn get_attn_vdm(&self, port_id: GlobalPortId) -> Result { + self.context.get_attn_vdm(port_id).await } } diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index afd52d88..d5050abb 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -27,7 +27,7 @@ pub async fn task_closure< PSU::Inner: psu::Psu, S: event::Sender, V: crate::wrapper::FwOfferValidator, - D::Inner: crate::type_c::controller::Controller, + D::Inner: crate::wrapper::controller::Controller, { info!("Starting type-c task"); @@ -35,7 +35,7 @@ pub async fn task_closure< // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 for controller_wrapper in wrappers { - if controller_wrapper.register(service.controllers(), cfu_client).is_err() { + if controller_wrapper.register(service.context, cfu_client).is_err() { error!("Failed to register a controller"); return; } @@ -57,7 +57,7 @@ pub async fn task<'a, M, D, PSU: Lockable, S, V, const N: usize>( PSU::Inner: psu::Psu, S: event::Sender, V: crate::wrapper::FwOfferValidator, - ::Inner: crate::type_c::controller::Controller, + ::Inner: crate::wrapper::controller::Controller, { task_closure(service, wrappers, cfu_client, |service: &Service<'_, PSU>| async { if let Err(e) = service.process_next_event().await { diff --git a/type-c-service/src/type_c/controller.rs b/type-c-service/src/type_c/controller.rs deleted file mode 100644 index 6f20fd8f..00000000 --- a/type-c-service/src/type_c/controller.rs +++ /dev/null @@ -1,1380 +0,0 @@ -//! PD controller related code -use core::future::Future; - -use embassy_sync::signal::Signal; -use embassy_time::{Duration, with_timeout}; -use embedded_usb_pd::ucsi::{self, lpm}; -use embedded_usb_pd::{ - DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, - ado::Ado, - pdinfo::{AltMode, PowerPathStatus}, - type_c::ConnectionState, -}; - -use super::{ATTN_VDM_LEN, ControllerId, OTHER_VDM_LEN, external}; -use crate::type_c::Cached; -use crate::type_c::comms::CommsMessage; -use crate::type_c::event::{PortEvent, PortPending}; -use embedded_services::ipc::deferred; -use embedded_services::{ - GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace, -}; - -/// maximum number of data objects in a VDM -pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each - -/// Port status -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortStatus { - /// Current available source contract - pub available_source_contract: Option, - /// Current available sink contract - pub available_sink_contract: Option, - /// Current connection state - pub connection_state: Option, - /// Port partner supports dual-power roles - pub dual_power: bool, - /// plug orientation - pub plug_orientation: PlugOrientation, - /// power role - pub power_role: PowerRole, - /// data role - pub data_role: DataRole, - /// Active alt-modes - pub alt_mode: AltMode, - /// Power path status - pub power_path: PowerPathStatus, - /// EPR mode active - pub epr: bool, - /// Port partner is unconstrained - pub unconstrained_power: bool, -} - -impl PortStatus { - /// Create a new blank port status - /// Needed because default() is not const - pub const fn new() -> Self { - Self { - available_source_contract: None, - available_sink_contract: None, - connection_state: None, - dual_power: false, - plug_orientation: PlugOrientation::CC1, - power_role: PowerRole::Sink, - data_role: DataRole::Dfp, - alt_mode: AltMode::none(), - power_path: PowerPathStatus::none(), - epr: false, - unconstrained_power: false, - } - } - - /// Check if the port is connected - pub fn is_connected(&self) -> bool { - matches!( - self.connection_state, - Some(ConnectionState::Attached) - | Some(ConnectionState::DebugAccessory) - | Some(ConnectionState::AudioAccessory) - ) - } - - /// Check if a debug accessory is connected - pub fn is_debug_accessory(&self) -> bool { - matches!(self.connection_state, Some(ConnectionState::DebugAccessory)) - } -} - -impl Default for PortStatus { - fn default() -> Self { - Self::new() - } -} - -/// Other Vdm data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OtherVdm { - /// Other VDM data - pub data: [u8; OTHER_VDM_LEN], -} - -impl Default for OtherVdm { - fn default() -> Self { - Self { - data: [0; OTHER_VDM_LEN], - } - } -} - -impl From for [u8; OTHER_VDM_LEN] { - fn from(vdm: OtherVdm) -> Self { - vdm.data - } -} - -impl From<[u8; OTHER_VDM_LEN]> for OtherVdm { - fn from(data: [u8; OTHER_VDM_LEN]) -> Self { - Self { data } - } -} - -/// Attention Vdm data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AttnVdm { - /// Attention VDM data - pub data: [u8; ATTN_VDM_LEN], -} - -/// DisplayPort pin configuration -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpPinConfig { - /// 4L DP connection using USBC-USBC cable (Pin Assignment C) - pub pin_c: bool, - /// 2L USB + 2L DP connection using USBC-USBC cable (Pin Assignment D) - pub pin_d: bool, - /// 4L DP connection using USBC-DP cable (Pin Assignment E) - pub pin_e: bool, -} - -/// DisplayPort status data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpStatus { - /// DP alt-mode entered - pub alt_mode_entered: bool, - /// Get DP DFP pin config - pub dfp_d_pin_cfg: DpPinConfig, -} - -/// DisplayPort configuration data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpConfig { - /// DP alt-mode enabled - pub enable: bool, - /// Set DP DFP pin config - pub dfp_d_pin_cfg: DpPinConfig, -} - -impl Default for AttnVdm { - fn default() -> Self { - Self { - data: [0; ATTN_VDM_LEN], - } - } -} - -impl From for [u8; ATTN_VDM_LEN] { - fn from(vdm: AttnVdm) -> Self { - vdm.data - } -} - -impl From<[u8; ATTN_VDM_LEN]> for AttnVdm { - fn from(data: [u8; ATTN_VDM_LEN]) -> Self { - Self { data } - } -} - -/// Send VDM data -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SendVdm { - /// initiating a VDM sequence - pub initiator: bool, - /// VDO count - pub vdo_count: u8, - /// VDO data - pub vdo_data: [u32; MAX_NUM_DATA_OBJECTS], -} - -impl SendVdm { - /// Create a new blank port status - pub const fn new() -> Self { - Self { - initiator: false, - vdo_count: 0, - vdo_data: [0; MAX_NUM_DATA_OBJECTS], - } - } -} - -impl Default for SendVdm { - fn default() -> Self { - Self::new() - } -} - -/// USB control configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct UsbControlConfig { - /// Enable USB2 data path - pub usb2_enabled: bool, - /// Enable USB3 data path - pub usb3_enabled: bool, - /// Enable USB4 data path - pub usb4_enabled: bool, -} - -impl Default for UsbControlConfig { - fn default() -> Self { - Self { - usb2_enabled: true, - usb3_enabled: true, - usb4_enabled: true, - } - } -} - -/// Thunderbolt control configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] -pub struct TbtConfig { - /// Enable Thunderbolt - pub tbt_enabled: bool, -} - -/// PD state-machine configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] -pub struct PdStateMachineConfig { - /// Enable or disable the PD state-machine - pub enabled: bool, -} - -/// TypeC State Machine -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum TypeCStateMachineState { - /// Sink state machine only - Sink, - /// Source state machine only - Source, - /// DRP state machine - Drp, - /// Disabled - Disabled, -} - -/// Port-specific command data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortCommandData { - /// Get port status - PortStatus(Cached), - /// Get and clear events - ClearEvents, - /// Get retimer fw update state - RetimerFwUpdateGetState, - /// Set retimer fw update state - RetimerFwUpdateSetState, - /// Clear retimer fw update state - RetimerFwUpdateClearState, - /// Set retimer compliance - SetRetimerCompliance, - /// Reconfigure retimer - ReconfigureRetimer, - /// Get oldest unhandled PD alert - GetPdAlert, - /// Set the maximum sink voltage in mV for the given port - SetMaxSinkVoltage(Option), - /// Set unconstrained power - SetUnconstrainedPower(bool), - /// Clear the dead battery flag for the given port - ClearDeadBatteryFlag, - /// Get other VDM - GetOtherVdm, - /// Get attention VDM - GetAttnVdm, - /// Send VDM - SendVdm(SendVdm), - /// Set USB control configuration - SetUsbControl(UsbControlConfig), - /// Get DisplayPort status - GetDpStatus, - /// Set DisplayPort configuration - SetDpConfig(DpConfig), - /// Execute DisplayPort reset - ExecuteDrst, - /// Set Thunderbolt configuration - SetTbtConfig(TbtConfig), - /// Set PD state-machine configuration - SetPdStateMachineConfig(PdStateMachineConfig), - /// Set Type-C state-machine configuration - SetTypeCStateMachineConfig(TypeCStateMachineState), - /// Execute the UCSI command - ExecuteUcsiCommand(lpm::CommandData), -} - -/// Port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortCommand { - /// Port ID - pub port: GlobalPortId, - /// Command data - pub data: PortCommandData, -} - -/// PD controller command-specific data -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RetimerFwUpdateState { - /// Retimer FW Update Inactive - Inactive, - /// Revimer FW Update Active - Active, -} - -/// Port-specific response data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortResponseData { - /// Command completed with no error - Complete, - /// Port status - PortStatus(PortStatus), - /// ClearEvents - ClearEvents(PortEvent), - /// Retimer Fw Update status - RtFwUpdateStatus(RetimerFwUpdateState), - /// PD alert - PdAlert(Option), - /// Get other VDM - OtherVdm(OtherVdm), - /// Get attention VDM - AttnVdm(AttnVdm), - /// Get DisplayPort status - DpStatus(DpStatus), - /// UCSI response - UcsiResponse(Result, PdError>), -} - -impl PortResponseData { - /// Helper function to convert to a result - pub fn complete_or_err(self) -> Result<(), PdError> { - match self { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } -} - -/// Port-specific command response -pub type PortResponse = Result; - -/// PD controller command-specific data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InternalCommandData { - /// Reset the PD controller - Reset, - /// Get controller status - Status, - /// Sync controller state - SyncState, -} - -/// PD controller command -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Command { - /// Controller specific command - Controller(InternalCommandData), - /// Port command - Port(PortCommand), - /// UCSI command passthrough - Lpm(lpm::GlobalCommand), -} - -/// Controller-specific response data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InternalResponseData<'a> { - /// Command complete - Complete, - /// Controller status - Status(ControllerStatus<'a>), -} - -/// Response for controller-specific commands -pub type InternalResponse<'a> = Result, PdError>; - -/// PD controller command response -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Response<'a> { - /// Controller response - Controller(InternalResponse<'a>), - /// UCSI response passthrough - Ucsi(ucsi::GlobalResponse), - /// Port response - Port(PortResponse), -} - -/// Controller status -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerStatus<'a> { - /// Current controller mode - pub mode: &'a str, - /// True if we did not have to boot from a backup FW bank - pub valid_fw_bank: bool, - /// FW version 0 - pub fw_version0: u32, - /// FW version 1 - pub fw_version1: u32, -} - -/// PD controller -pub struct Device<'a> { - node: intrusive_list::Node, - id: ControllerId, - ports: &'a [GlobalPortId], - num_ports: usize, - command: deferred::Channel>, -} - -impl intrusive_list::NodeContainer for Device<'static> { - fn get_node(&self) -> &intrusive_list::Node { - &self.node - } -} - -impl<'a> Device<'a> { - /// Create a new PD controller struct - pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { - Self { - node: intrusive_list::Node::uninit(), - id, - ports, - num_ports: ports.len(), - command: deferred::Channel::new(), - } - } - - /// Get the controller ID - pub fn id(&self) -> ControllerId { - self.id - } - - /// Send a command to this controller - pub async fn execute_command(&self, command: Command) -> Response<'_> { - self.command.execute(command).await - } - - /// Check if this controller has the given port - pub fn has_port(&self, port: GlobalPortId) -> bool { - self.lookup_local_port(port).is_ok() - } - - /// Covert a local port ID to a global port ID - pub fn lookup_global_port(&self, port: LocalPortId) -> Result { - Ok(*self.ports.get(port.0 as usize).ok_or(PdError::InvalidParams)?) - } - - /// Convert a global port ID to a local port ID - pub fn lookup_local_port(&self, port: GlobalPortId) -> Result { - self.ports - .iter() - .position(|p| *p == port) - .map(|p| LocalPortId(p as u8)) - .ok_or(PdError::InvalidParams) - } - - /// Create a command handler for this controller - /// - /// DROP SAFETY: Direct call to deferred channel primitive - pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, Command, Response<'static>> { - self.command.receive().await - } - - /// Notify that there are pending events on one or more ports - pub fn notify_ports(&self, ctx: &Context, pending: PortPending) { - ctx.notify_ports(pending); - } - - /// Number of ports on this controller - pub fn num_ports(&self) -> usize { - self.num_ports - } - - /// Slice of global ports on the Device - pub fn ports(&self) -> &'a [GlobalPortId] { - self.ports - } -} - -/// Trait for types that contain a controller struct -pub trait DeviceContainer { - /// Get the controller struct - fn get_pd_controller_device(&self) -> &Device<'_>; -} - -impl DeviceContainer for Device<'_> { - fn get_pd_controller_device(&self) -> &Device<'_> { - self - } -} - -/// PD controller trait that device drivers may use to integrate with internal messaging system -pub trait Controller { - /// Type of error returned by the bus - type BusError; - - /// Wait for a port event to occur - /// # Implementation guide - /// This function should be drop safe. - /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. - fn wait_port_event(&mut self) -> impl Future>>; - /// Returns and clears current events for the given port - /// # Implementation guide - /// This function should be drop safe. - /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. - fn clear_port_events( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Returns the port status - fn get_port_status(&mut self, port: LocalPortId) - -> impl Future>>; - - /// Reset the controller - fn reset_controller(&mut self) -> impl Future>>; - - /// Returns the retimer fw update state - fn get_rt_fw_update_status( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Set retimer fw update state - fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>>; - /// Clear retimer fw update state - fn clear_rt_fw_update_state( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Set retimer compliance - fn set_rt_compliance(&mut self, port: LocalPortId) -> impl Future>>; - - /// Reconfigure the retimer for the given port. - fn reconfigure_retimer(&mut self, port: LocalPortId) -> impl Future>>; - - /// Clear the dead battery flag for the given port. - fn clear_dead_battery_flag(&mut self, port: LocalPortId) - -> impl Future>>; - - /// Enable or disable sink path - fn enable_sink_path( - &mut self, - port: LocalPortId, - enable: bool, - ) -> impl Future>>; - /// Get current controller status - fn get_controller_status( - &mut self, - ) -> impl Future, Error>>; - /// Get current PD alert - fn get_pd_alert(&mut self, port: LocalPortId) -> impl Future, Error>>; - /// Set the maximum sink voltage for the given port - /// - /// This may trigger a renegotiation - fn set_max_sink_voltage( - &mut self, - port: LocalPortId, - voltage_mv: Option, - ) -> impl Future>>; - /// Set port unconstrained status - fn set_unconstrained_power( - &mut self, - port: LocalPortId, - unconstrained: bool, - ) -> impl Future>>; - - // TODO: remove all these once we migrate to a generic FW update trait - // https://github.com/OpenDevicePartnership/embedded-services/issues/242 - /// Get current FW version - fn get_active_fw_version(&mut self) -> impl Future>>; - /// Start a firmware update - fn start_fw_update(&mut self) -> impl Future>>; - /// Abort a firmware update - fn abort_fw_update(&mut self) -> impl Future>>; - /// Finalize a firmware update - fn finalize_fw_update(&mut self) -> impl Future>>; - /// Write firmware update contents - fn write_fw_contents( - &mut self, - offset: usize, - data: &[u8], - ) -> impl Future>>; - /// Get the Rx Other VDM data for the given port - fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>>; - /// Get the Rx Attention VDM data for the given port - fn get_attn_vdm(&mut self, port: LocalPortId) -> impl Future>>; - /// Send a VDM to the given port - fn send_vdm( - &mut self, - port: LocalPortId, - tx_vdm: SendVdm, - ) -> impl Future>>; - - /// Set USB control configuration for the given port - fn set_usb_control( - &mut self, - port: LocalPortId, - config: UsbControlConfig, - ) -> impl Future>>; - - /// Get DisplayPort status for the given port - fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>>; - /// Set DisplayPort configuration for the given port - fn set_dp_config( - &mut self, - port: LocalPortId, - config: DpConfig, - ) -> impl Future>>; - /// Execute PD Data Reset for the given port - fn execute_drst(&mut self, port: LocalPortId) -> impl Future>>; - - /// Set Thunderbolt configuration for the given port - fn set_tbt_config( - &mut self, - port: LocalPortId, - config: TbtConfig, - ) -> impl Future>>; - - /// Set PD state-machine configuration for the given port - fn set_pd_state_machine_config( - &mut self, - port: LocalPortId, - config: PdStateMachineConfig, - ) -> impl Future>>; - - /// Set Type-C state-machine configuration for the given port - fn set_type_c_state_machine_config( - &mut self, - port: LocalPortId, - state: TypeCStateMachineState, - ) -> impl Future>>; - - /// Execute the given UCSI command - fn execute_ucsi_command( - &mut self, - command: lpm::LocalCommand, - ) -> impl Future, Error>>; -} - -/// Internal context for managing PD controllers -pub struct Context { - port_events: Signal, - /// Channel for receiving commands to the type-C service - external_command: deferred::Channel>, - /// Event broadcaster - broadcaster: broadcaster::Immediate, -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} - -impl Context { - /// Create new Context - pub const fn new() -> Self { - Self { - port_events: Signal::new(), - external_command: deferred::Channel::new(), - broadcaster: broadcaster::Immediate::new(), - } - } - - /// Notify that there are pending events on one or more ports - /// Each bit corresponds to a global port ID - pub fn notify_ports(&self, pending: PortPending) { - let raw_pending: u32 = pending.into(); - trace!("Notify ports: {:#x}", raw_pending); - // Early exit if no events - if pending.is_none() { - return; - } - - self.port_events - .signal(if let Some(flags) = self.port_events.try_take() { - flags.union(pending) - } else { - pending - }); - } - - /// Send a command to the given controller with no timeout - pub async fn send_controller_command_no_timeout( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - command: InternalCommandData, - ) -> Result, PdError> { - let node = controllers - .into_iter() - .find(|node| { - if let Some(controller) = node.data::() { - controller.id == controller_id - } else { - false - } - }) - .ok_or(PdError::InvalidController)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Controller(command)) - .await - { - Response::Controller(response) => response, - r => { - error!("Invalid response: expected controller, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given controller with a timeout - pub async fn send_controller_command( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - command: InternalCommandData, - ) -> Result, PdError> { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_controller_command_no_timeout(controllers, controller_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - - /// Reset the given controller - pub async fn reset_controller( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - ) -> Result<(), PdError> { - self.send_controller_command(controllers, controller_id, InternalCommandData::Reset) - .await - .map(|_| ()) - } - - fn find_node_by_port( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - ) -> Result<&IntrusiveNode, PdError> { - controllers - .into_iter() - .find(|node| { - if let Some(controller) = node.data::() { - controller.has_port(port_id) - } else { - false - } - }) - .ok_or(PdError::InvalidPort) - } - - /// Send a command to the given port - pub async fn send_port_command_ucsi_no_timeout( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: lpm::CommandData, - ) -> Result { - let node = self.find_node_by_port(controllers, port_id)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Lpm(lpm::Command::new(port_id, command))) - .await - { - Response::Ucsi(response) => Ok(response), - r => { - error!("Invalid response: expected LPM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given port with a timeout - pub async fn send_port_command_ucsi( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: lpm::CommandData, - ) -> Result { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_port_command_ucsi_no_timeout(controllers, port_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - - /// Send a command to the given port with no timeout - pub async fn send_port_command_no_timeout( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: PortCommandData, - ) -> Result { - let node = self.find_node_by_port(controllers, port_id)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Port(PortCommand { - port: port_id, - data: command, - })) - .await - { - Response::Port(response) => response, - r => { - error!("Invalid response: expected port, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given port with a timeout - pub async fn send_port_command( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: PortCommandData, - ) -> Result { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_port_command_no_timeout(controllers, port_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - - /// Get the current port events - pub async fn get_unhandled_events(&self) -> PortPending { - self.port_events.wait().await - } - - /// Get the unhandled events for the given port - pub async fn get_port_event( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::ClearEvents) - .await? - { - PortResponseData::ClearEvents(event) => Ok(event), - r => { - error!("Invalid response: expected clear events, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the current port status - pub async fn get_port_status( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - cached: Cached, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::PortStatus(cached)) - .await? - { - PortResponseData::PortStatus(status) => Ok(status), - r => { - error!("Invalid response: expected port status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the oldest unhandled PD alert for the given port - pub async fn get_pd_alert( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result, PdError> { - match self - .send_port_command(controllers, port, PortCommandData::GetPdAlert) - .await? - { - PortResponseData::PdAlert(alert) => Ok(alert), - r => { - error!("Invalid response: expected PD alert, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the retimer fw update status - pub async fn get_rt_fw_update_status( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateGetState) - .await? - { - PortResponseData::RtFwUpdateStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer fw update state - pub async fn set_rt_fw_update_state( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateSetState) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the retimer fw update state - pub async fn clear_rt_fw_update_state( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateClearState) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer compliance - pub async fn set_rt_compliance( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetRetimerCompliance) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Reconfigure the retimer for the given port. - pub async fn reconfigure_retimer( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::ReconfigureRetimer) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the maximum sink voltage for the given port. - /// - /// See [`PortCommandData::SetMaxSinkVoltage`] for details on the `max_voltage_mv` parameter. - pub async fn set_max_sink_voltage( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - max_voltage_mv: Option, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetMaxSinkVoltage(max_voltage_mv)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the dead battery flag for the given port. - pub async fn clear_dead_battery_flag( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::ClearDeadBatteryFlag) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get current controller status - pub async fn get_controller_status( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - ) -> Result, PdError> { - match self - .send_controller_command(controllers, controller_id, InternalCommandData::Status) - .await? - { - InternalResponseData::Status(status) => Ok(status), - r => { - error!("Invalid response: expected controller status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Set unconstrained power for the given port - pub async fn set_unconstrained_power( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - unconstrained: bool, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetUnconstrainedPower(unconstrained)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Sync controller state - pub async fn sync_controller_state( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - ) -> Result<(), PdError> { - match self - .send_controller_command(controllers, controller_id, InternalCommandData::SyncState) - .await? - { - InternalResponseData::Complete => Ok(()), - r => { - error!("Invalid response: expected controller status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Wait for an external command - pub async fn wait_external_command( - &self, - ) -> deferred::Request<'_, GlobalRawMutex, external::Command, external::Response<'static>> { - self.external_command.receive().await - } - - /// Get the number of ports on the system - pub fn get_num_ports(&self, controllers: &intrusive_list::IntrusiveList) -> usize { - get_num_ports(controllers) - } - - /// Get the other vdm for the given port - pub async fn get_other_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::GetOtherVdm) - .await? - { - PortResponseData::OtherVdm(vdm) => Ok(vdm), - r => { - error!("Invalid response: expected other VDM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the attention vdm for the given port - pub async fn get_attn_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::GetAttnVdm) - .await? - { - PortResponseData::AttnVdm(vdm) => Ok(vdm), - r => { - error!("Invalid response: expected attention VDM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send VDM to the given port - pub async fn send_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - tx_vdm: SendVdm, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SendVdm(tx_vdm)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set USB control configuration for the given port - pub async fn set_usb_control( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: UsbControlConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetUsbControl(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get DisplayPort status for the given port - pub async fn get_dp_status( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::GetDpStatus) - .await? - { - PortResponseData::DpStatus(status) => Ok(status), - r => { - error!("Invalid response: expected DP status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Set DisplayPort configuration for the given port - pub async fn set_dp_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: DpConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetDpConfig(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute PD Data Reset for the given port - pub async fn execute_drst( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::ExecuteDrst) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Thunderbolt configuration for the given port - pub async fn set_tbt_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: TbtConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetTbtConfig(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set PD state-machine configuration for the given port - pub async fn set_pd_state_machine_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: PdStateMachineConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetPdStateMachineConfig(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Type-C state-machine configuration for the given port - pub async fn set_type_c_state_machine_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - state: TypeCStateMachineState, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetTypeCStateMachineConfig(state)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute the given UCSI command - pub async fn execute_ucsi_command( - &self, - controllers: &intrusive_list::IntrusiveList, - command: lpm::GlobalCommand, - ) -> Result, PdError> { - match self - .send_port_command( - controllers, - command.port(), - PortCommandData::ExecuteUcsiCommand(command.operation()), - ) - .await? - { - PortResponseData::UcsiResponse(response) => response, - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute an external port command - pub(super) async fn execute_external_port_command( - &self, - command: external::Command, - ) -> Result { - match self.external_command.execute(command).await { - external::Response::Port(response) => response, - r => { - error!("Invalid response: expected external port, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Execute an external UCSI command - pub(super) async fn execute_external_ucsi_command(&self, command: ucsi::GlobalCommand) -> external::UcsiResponse { - match self.external_command.execute(external::Command::Ucsi(command)).await { - external::Response::Ucsi(response) => response, - r => { - error!("Invalid response: expected external UCSI, got {:?}", r); - external::UcsiResponse { - // Always notify OPM of an error - notify_opm: true, - cci: ucsi::cci::GlobalCci::new_error(), - data: Err(PdError::InvalidResponse), - } - } - } - } - - /// Execute an external controller command - pub(super) async fn execute_external_controller_command( - &self, - command: external::Command, - ) -> Result, PdError> { - match self.external_command.execute(command).await { - external::Response::Controller(response) => response, - r => { - error!("Invalid response: expected external controller, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Register a message receiver for type-C messages - pub async fn register_message_receiver( - &self, - receiver: &'static broadcaster::Receiver<'_, CommsMessage>, - ) -> intrusive_list::Result<()> { - self.broadcaster.register_receiver(receiver) - } - - /// Broadcast a type-C message to all subscribers - pub async fn broadcast_message(&self, message: CommsMessage) { - self.broadcaster.broadcast(message).await; - } -} - -/// Default command timeout -/// set to high value since this is intended to prevent an unresponsive device from blocking the service implementation -const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); - -/// Register a PD controller -pub fn register_controller( - controllers: &intrusive_list::IntrusiveList, - controller: &'static impl DeviceContainer, -) -> Result<(), intrusive_list::Error> { - controllers.push(controller.get_pd_controller_device()) -} - -pub(super) fn lookup_controller( - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, -) -> Result<&'static Device<'static>, PdError> { - controllers - .into_iter() - .filter_map(|node| node.data::()) - .find(|controller| controller.id == controller_id) - .ok_or(PdError::InvalidController) -} - -/// Get total number of ports on the system -pub(super) fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { - controllers - .iter_only::() - .fold(0, |acc, controller| acc + controller.num_ports()) -} diff --git a/type-c-service/src/type_c/external.rs b/type-c-service/src/type_c/external.rs deleted file mode 100644 index ef253db2..00000000 --- a/type-c-service/src/type_c/external.rs +++ /dev/null @@ -1,495 +0,0 @@ -//! Message definitions for external type-C commands -use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError, ucsi}; - -use embedded_services::intrusive_list; - -use super::{ - ControllerId, - controller::{ControllerStatus, DpConfig, DpStatus, PortStatus, RetimerFwUpdateState, SendVdm, lookup_controller}, -}; - -use crate::type_c::{ - Cached, - controller::{Context, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, -}; - -/// Data for controller-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ControllerCommandData { - /// Get controller status - ControllerStatus, - /// Sync controller state - SyncState, - /// Controller reset - Reset, -} - -/// Controller-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerCommand { - /// Controller ID - pub id: ControllerId, - /// Command data - pub data: ControllerCommandData, -} - -/// Response data for controller-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ControllerResponseData<'a> { - /// Command complete - Complete, - /// Get controller status - ControllerStatus(ControllerStatus<'a>), -} - -/// Controller-specific command response -pub type ControllerResponse<'a> = Result, PdError>; - -/// Data for port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortCommandData { - /// Get port status. The `bool` argument indicates whether to use cached data or force a fetch of register values. - PortStatus(Cached), - /// Get retimer fw update status - RetimerFwUpdateGetState, - /// Set retimer fw update status - RetimerFwUpdateSetState, - /// Clear retimer fw update status - RetimerFwUpdateClearState, - /// Set retimer compliance - SetRetimerCompliance, - /// Reconfigure retimer - ReconfigureRetimer, - /// Set max sink voltage to a specific value. - SetMaxSinkVoltage { - /// The maximum voltage to set, in millivolts. - /// If [`None`], the port will be set to its default maximum voltage. - max_voltage_mv: Option, - }, - /// Clear the dead battery flag for the given port. - ClearDeadBatteryFlag, - /// Set USB control - SetUsbControl(UsbControlConfig), - /// Send VDM - SendVdm(SendVdm), - /// Get DisplayPort status - GetDpStatus, - /// Set DisplayPort configuration - SetDpConfig(DpConfig), - /// Execute DisplayPort reset - ExecuteDrst, - /// Set Thunderbolt configuration - SetTbtConfig(TbtConfig), - /// Set PD state-machine configuration - SetPdStateMachineConfig(PdStateMachineConfig), - /// Set Type-C state-machine configuration - SetTypeCStateMachineConfig(TypeCStateMachineState), -} - -/// Port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortCommand { - /// Port ID - pub port: GlobalPortId, - /// Command data - pub data: PortCommandData, -} - -/// Response data for port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortResponseData { - /// Command completed with no error - Complete, - /// Get port status - PortStatus(PortStatus), - /// Get retimer fw update status - RetimerFwUpdateGetState(RetimerFwUpdateState), - /// Get DisplayPort status - GetDpStatus(DpStatus), -} - -/// Port-specific command response -pub type PortResponse = Result; - -/// External commands for type-C service -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Command { - /// Port command - Port(PortCommand), - /// Controller command - Controller(ControllerCommand), - /// UCSI command - Ucsi(ucsi::GlobalCommand), -} - -/// UCSI command response -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct UcsiResponse { - /// Notify the OPM, the function call - pub notify_opm: bool, - /// Response CCI - pub cci: ucsi::cci::GlobalCci, - /// UCSI response data - pub data: Result, PdError>, -} - -/// Alias to help simplify conversion into a result -pub type UcsiResponseResult = Result; - -impl From for UcsiResponseResult { - fn from(value: UcsiResponse) -> Self { - match value.data { - Ok(data) => Ok(ucsi::GlobalResponse { cci: value.cci, data }), - Err(err) => Err(err), - } - } -} - -/// External command response for type-C service -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Response<'a> { - /// Port command response - Port(PortResponse), - /// Controller command response - Controller(ControllerResponse<'a>), - /// UCSI command response - Ucsi(UcsiResponse), -} - -impl Context { - /// Get the status of the given port. - /// - /// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. - pub async fn get_port_status_external(&self, port: GlobalPortId, cached: Cached) -> Result { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::PortStatus(cached), - })) - .await? - { - PortResponseData::PortStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get the status of the given port by its controller and local port ID. - /// - /// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. - pub async fn get_controller_port_status_external( - &self, - controllers: &intrusive_list::IntrusiveList, - controller: ControllerId, - port: LocalPortId, - cached: Cached, - ) -> Result { - let global_port = controller_port_to_global_id(controllers, controller, port)?; - self.get_port_status_external(global_port, cached).await - } - - /// Reset the given controller. - pub async fn reset_controller_external(&self, controller_id: ControllerId) -> Result<(), PdError> { - match self - .execute_external_controller_command(Command::Controller(ControllerCommand { - id: controller_id, - data: ControllerCommandData::Reset, - })) - .await? - { - ControllerResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get the status of the given controller - #[allow(unreachable_patterns)] - pub async fn get_controller_status_external(&self, id: ControllerId) -> Result, PdError> { - match self - .execute_external_controller_command(Command::Controller(ControllerCommand { - id, - data: ControllerCommandData::ControllerStatus, - })) - .await? - { - ControllerResponseData::ControllerStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get the retimer fw update status of the given port - pub async fn port_get_rt_fw_update_status_external( - &self, - port: GlobalPortId, - ) -> Result { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateGetState, - })) - .await? - { - PortResponseData::RetimerFwUpdateGetState(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer fw update state of the given port - pub async fn port_set_rt_fw_update_state_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateSetState, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the retimer fw update state of the given port - pub async fn port_clear_rt_fw_update_state_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateClearState, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer comliance state of the given port - pub async fn port_set_rt_compliance_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetRetimerCompliance, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Trigger a sync of the controller state - pub async fn sync_controller_state_external(&self, id: ControllerId) -> Result<(), PdError> { - match self - .execute_external_controller_command(Command::Controller(ControllerCommand { - id, - data: ControllerCommandData::SyncState, - })) - .await? - { - ControllerResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the maximum voltage for the given port to a specific value. - /// - /// See [`PortCommandData::SetMaxSinkVoltage::max_voltage_mv`] for details on the `max_voltage_mv` parameter. - pub async fn set_max_sink_voltage_external( - &self, - port: GlobalPortId, - max_voltage_mv: Option, - ) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetMaxSinkVoltage { max_voltage_mv }, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the dead battery flag for the given port. - pub async fn clear_dead_battery_flag_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ClearDeadBatteryFlag, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Reconfigure the retimer for the given port. - pub async fn reconfigure_retimer_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ReconfigureRetimer, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute a UCSI command - pub async fn execute_ucsi_command_external(&self, command: ucsi::GlobalCommand) -> UcsiResponse { - self.execute_external_ucsi_command(command).await - } - - /// Send vdm to the given port - pub async fn send_vdm_external(&self, port: GlobalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SendVdm(tx_vdm), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set USB control configuration - pub async fn set_usb_control_external(&self, port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetUsbControl(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get DisplayPort status for the given port - pub async fn get_dp_status_external(&self, port: GlobalPortId) -> Result { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::GetDpStatus, - })) - .await? - { - PortResponseData::GetDpStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set DisplayPort configuration for the given port - pub async fn set_dp_config_external(&self, port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetDpConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute DisplayPort reset for the given port - pub async fn execute_drst_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ExecuteDrst, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Thunderbolt configuration for the given port - pub async fn set_tbt_config_external(&self, port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetTbtConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set PD state-machine configuration for the given port - pub async fn set_pd_state_machine_config_external( - &self, - port: GlobalPortId, - config: PdStateMachineConfig, - ) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetPdStateMachineConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Type-C state-machine configuration for the given port - pub async fn set_type_c_state_machine_config_external( - &self, - port: GlobalPortId, - state: TypeCStateMachineState, - ) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetTypeCStateMachineConfig(state), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } -} - -/// Get the number of ports on the given controller -pub fn get_controller_num_ports( - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, -) -> Result { - Ok(lookup_controller(controllers, controller_id)?.num_ports()) -} - -/// Convert a (controller ID, local port ID) to a global port ID -pub fn controller_port_to_global_id( - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - port_id: LocalPortId, -) -> Result { - lookup_controller(controllers, controller_id)?.lookup_global_port(port_id) -} - -/// Get number of ports on the system -pub fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { - super::controller::get_num_ports(controllers) -} diff --git a/type-c-service/src/type_c/mod.rs b/type-c-service/src/util.rs similarity index 74% rename from type-c-service/src/type_c/mod.rs rename to type-c-service/src/util.rs index a5100bf1..9eaadeaf 100644 --- a/type-c-service/src/type_c/mod.rs +++ b/type-c-service/src/util.rs @@ -1,22 +1,7 @@ -//! Type-C service +//! Type-C service utility functions and constants. use embedded_usb_pd::pdo::{Common, Contract}; use embedded_usb_pd::type_c; -pub mod comms; -pub mod controller; -pub mod event; -pub mod external; - -/// Controller ID -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerId(pub u8); - -/// Length of the Other VDM data -pub const OTHER_VDM_LEN: usize = 29; -/// Length of the Attention VDM data -pub const ATTN_VDM_LEN: usize = 9; - pub fn power_capability_try_from_contract( contract: Contract, ) -> Option { @@ -61,8 +46,3 @@ pub const POWER_CAPABILITY_5V_3A0: power_policy_interface::capability::PowerCapa voltage_mv: 5000, current_ma: 3000, }; - -/// Newtype to help clarify arguments to port status commands -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Cached(pub bool); diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 68c54916..ab079cd7 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -16,11 +16,8 @@ use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::event; use embedded_usb_pd::{GlobalPortId, ado::Ado}; -use crate::type_c::{ - ControllerId, - controller::PortStatus, - event::{PortEvent, PortStatusChanged}, -}; +use crate::wrapper::controller::{ControllerId, PortStatus}; +use crate::wrapper::event::{PortEvent, PortStatusChanged}; use crate::{ PortEventStreamer, @@ -50,8 +47,8 @@ impl Default for ControllerState { /// Service registration objects pub struct Registration<'a, M: RawMutex> { - pub context: &'a crate::type_c::controller::Context, - pub pd_controller: &'a crate::type_c::controller::Device<'a>, + pub context: &'a crate::service::context::Context, + pub pd_controller: &'a crate::wrapper::controller::Device<'a>, pub cfu_device: &'a CfuDevice, pub power_devices: &'a [&'a Mutex>], } @@ -68,7 +65,7 @@ const MAX_BUFFERED_PD_ALERTS: usize = 4; /// Base storage pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related - context: &'a crate::type_c::controller::Context, + context: &'a crate::service::context::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: CfuDevice, @@ -80,7 +77,7 @@ pub struct Storage<'a, const N: usize, M: RawMutex> { impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( - context: &'a crate::type_c::controller::Context, + context: &'a crate::service::context::Context, controller_id: ControllerId, cfu_id: ComponentId, pd_ports: [GlobalPortId; N], @@ -200,14 +197,9 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, -> { - intermediate: &'a IntermediateStorage<'a, N, M, S>, - pd_controller: crate::type_c::controller::Device<'a>, +pub struct ReferencedStorage<'a, const N: usize, M: RawMutex> { + intermediate: &'a IntermediateStorage<'a, N, M>, + pd_controller: crate::wrapper::controller::Device<'a>, power_devices: [&'a Mutex>; N], } @@ -218,7 +210,7 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender) -> Option { Some(Self { intermediate, - pd_controller: crate::type_c::controller::Device::new( + pd_controller: crate::wrapper::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 beb786e9..d4116c6f 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,7 +1,7 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation -use crate::type_c::controller::Controller; use crate::wrapper::backing::ControllerState; +use crate::wrapper::controller::Controller; use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; diff --git a/type-c-service/src/wrapper/controller.rs b/type-c-service/src/wrapper/controller.rs new file mode 100644 index 00000000..3f872fa5 --- /dev/null +++ b/type-c-service/src/wrapper/controller.rs @@ -0,0 +1,665 @@ +//! PD controller related code +use core::future::Future; + +use embedded_usb_pd::ucsi::{self, lpm}; +use embedded_usb_pd::{ + DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, + ado::Ado, + pdinfo::{AltMode, PowerPathStatus}, + type_c::ConnectionState, +}; + +use crate::wrapper::event::{PortEvent, PortPending}; +use embedded_services::ipc::deferred; +use embedded_services::{GlobalRawMutex, intrusive_list}; + +/// Length of the Other VDM data +pub const OTHER_VDM_LEN: usize = 29; +/// Length of the Attention VDM data +pub const ATTN_VDM_LEN: usize = 9; +/// maximum number of data objects in a VDM +pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each + +/// Newtype to help clarify arguments to port status commands +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Cached(pub bool); + +/// Controller ID +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControllerId(pub u8); + +/// Port status +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PortStatus { + /// Current available source contract + pub available_source_contract: Option, + /// Current available sink contract + pub available_sink_contract: Option, + /// Current connection state + pub connection_state: Option, + /// Port partner supports dual-power roles + pub dual_power: bool, + /// plug orientation + pub plug_orientation: PlugOrientation, + /// power role + pub power_role: PowerRole, + /// data role + pub data_role: DataRole, + /// Active alt-modes + pub alt_mode: AltMode, + /// Power path status + pub power_path: PowerPathStatus, + /// EPR mode active + pub epr: bool, + /// Port partner is unconstrained + pub unconstrained_power: bool, +} + +impl PortStatus { + /// Create a new blank port status + /// Needed because default() is not const + pub const fn new() -> Self { + Self { + available_source_contract: None, + available_sink_contract: None, + connection_state: None, + dual_power: false, + plug_orientation: PlugOrientation::CC1, + power_role: PowerRole::Sink, + data_role: DataRole::Dfp, + alt_mode: AltMode::none(), + power_path: PowerPathStatus::none(), + epr: false, + unconstrained_power: false, + } + } + + /// Check if the port is connected + pub fn is_connected(&self) -> bool { + matches!( + self.connection_state, + Some(ConnectionState::Attached) + | Some(ConnectionState::DebugAccessory) + | Some(ConnectionState::AudioAccessory) + ) + } + + /// Check if a debug accessory is connected + pub fn is_debug_accessory(&self) -> bool { + matches!(self.connection_state, Some(ConnectionState::DebugAccessory)) + } +} + +impl Default for PortStatus { + fn default() -> Self { + Self::new() + } +} + +/// Other Vdm data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OtherVdm { + /// Other VDM data + pub data: [u8; OTHER_VDM_LEN], +} + +impl Default for OtherVdm { + fn default() -> Self { + Self { + data: [0; OTHER_VDM_LEN], + } + } +} + +impl From for [u8; OTHER_VDM_LEN] { + fn from(vdm: OtherVdm) -> Self { + vdm.data + } +} + +impl From<[u8; OTHER_VDM_LEN]> for OtherVdm { + fn from(data: [u8; OTHER_VDM_LEN]) -> Self { + Self { data } + } +} + +/// Attention Vdm data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AttnVdm { + /// Attention VDM data + pub data: [u8; ATTN_VDM_LEN], +} + +/// DisplayPort pin configuration +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpPinConfig { + /// 4L DP connection using USBC-USBC cable (Pin Assignment C) + pub pin_c: bool, + /// 2L USB + 2L DP connection using USBC-USBC cable (Pin Assignment D) + pub pin_d: bool, + /// 4L DP connection using USBC-DP cable (Pin Assignment E) + pub pin_e: bool, +} + +/// DisplayPort status data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpStatus { + /// DP alt-mode entered + pub alt_mode_entered: bool, + /// Get DP DFP pin config + pub dfp_d_pin_cfg: DpPinConfig, +} + +/// DisplayPort configuration data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpConfig { + /// DP alt-mode enabled + pub enable: bool, + /// Set DP DFP pin config + pub dfp_d_pin_cfg: DpPinConfig, +} + +impl Default for AttnVdm { + fn default() -> Self { + Self { + data: [0; ATTN_VDM_LEN], + } + } +} + +impl From for [u8; ATTN_VDM_LEN] { + fn from(vdm: AttnVdm) -> Self { + vdm.data + } +} + +impl From<[u8; ATTN_VDM_LEN]> for AttnVdm { + fn from(data: [u8; ATTN_VDM_LEN]) -> Self { + Self { data } + } +} + +/// Send VDM data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SendVdm { + /// initiating a VDM sequence + pub initiator: bool, + /// VDO count + pub vdo_count: u8, + /// VDO data + pub vdo_data: [u32; MAX_NUM_DATA_OBJECTS], +} + +impl SendVdm { + /// Create a new blank port status + pub const fn new() -> Self { + Self { + initiator: false, + vdo_count: 0, + vdo_data: [0; MAX_NUM_DATA_OBJECTS], + } + } +} + +impl Default for SendVdm { + fn default() -> Self { + Self::new() + } +} + +/// USB control configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct UsbControlConfig { + /// Enable USB2 data path + pub usb2_enabled: bool, + /// Enable USB3 data path + pub usb3_enabled: bool, + /// Enable USB4 data path + pub usb4_enabled: bool, +} + +impl Default for UsbControlConfig { + fn default() -> Self { + Self { + usb2_enabled: true, + usb3_enabled: true, + usb4_enabled: true, + } + } +} + +/// Thunderbolt control configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Default, Copy, PartialEq)] +pub struct TbtConfig { + /// Enable Thunderbolt + pub tbt_enabled: bool, +} + +/// PD state-machine configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Default, Copy, PartialEq)] +pub struct PdStateMachineConfig { + /// Enable or disable the PD state-machine + pub enabled: bool, +} + +/// TypeC State Machine +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TypeCStateMachineState { + /// Sink state machine only + Sink, + /// Source state machine only + Source, + /// DRP state machine + Drp, + /// Disabled + Disabled, +} + +/// Port-specific command data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PortCommandData { + /// Get port status + PortStatus(Cached), + /// Get and clear events + ClearEvents, + /// Get retimer fw update state + RetimerFwUpdateGetState, + /// Set retimer fw update state + RetimerFwUpdateSetState, + /// Clear retimer fw update state + RetimerFwUpdateClearState, + /// Set retimer compliance + SetRetimerCompliance, + /// Reconfigure retimer + ReconfigureRetimer, + /// Get oldest unhandled PD alert + GetPdAlert, + /// Set the maximum sink voltage in mV for the given port + SetMaxSinkVoltage(Option), + /// Set unconstrained power + SetUnconstrainedPower(bool), + /// Clear the dead battery flag for the given port + ClearDeadBatteryFlag, + /// Get other VDM + GetOtherVdm, + /// Get attention VDM + GetAttnVdm, + /// Send VDM + SendVdm(SendVdm), + /// Set USB control configuration + SetUsbControl(UsbControlConfig), + /// Get DisplayPort status + GetDpStatus, + /// Set DisplayPort configuration + SetDpConfig(DpConfig), + /// Execute DisplayPort reset + ExecuteDrst, + /// Set Thunderbolt configuration + SetTbtConfig(TbtConfig), + /// Set PD state-machine configuration + SetPdStateMachineConfig(PdStateMachineConfig), + /// Set Type-C state-machine configuration + SetTypeCStateMachineConfig(TypeCStateMachineState), + /// Execute the UCSI command + ExecuteUcsiCommand(lpm::CommandData), +} + +/// Port-specific commands +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PortCommand { + /// Port ID + pub port: GlobalPortId, + /// Command data + pub data: PortCommandData, +} + +/// PD controller command-specific data +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RetimerFwUpdateState { + /// Retimer FW Update Inactive + Inactive, + /// Revimer FW Update Active + Active, +} + +/// Port-specific response data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PortResponseData { + /// Command completed with no error + Complete, + /// Port status + PortStatus(PortStatus), + /// ClearEvents + ClearEvents(PortEvent), + /// Retimer Fw Update status + RtFwUpdateStatus(RetimerFwUpdateState), + /// PD alert + PdAlert(Option), + /// Get other VDM + OtherVdm(OtherVdm), + /// Get attention VDM + AttnVdm(AttnVdm), + /// Get DisplayPort status + DpStatus(DpStatus), + /// UCSI response + UcsiResponse(Result, PdError>), +} + +impl PortResponseData { + /// Helper function to convert to a result + pub fn complete_or_err(self) -> Result<(), PdError> { + match self { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } +} + +/// Port-specific command response +pub type PortResponse = Result; + +/// PD controller command-specific data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InternalCommandData { + /// Reset the PD controller + Reset, + /// Get controller status + Status, + /// Sync controller state + SyncState, +} + +/// PD controller command +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// Controller specific command + Controller(InternalCommandData), + /// Port command + Port(PortCommand), + /// UCSI command passthrough + Lpm(lpm::GlobalCommand), +} + +/// Controller-specific response data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InternalResponseData<'a> { + /// Command complete + Complete, + /// Controller status + Status(ControllerStatus<'a>), +} + +/// Response for controller-specific commands +pub type InternalResponse<'a> = Result, PdError>; + +/// PD controller command response +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Response<'a> { + /// Controller response + Controller(InternalResponse<'a>), + /// UCSI response passthrough + Ucsi(ucsi::GlobalResponse), + /// Port response + Port(PortResponse), +} + +/// Controller status +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControllerStatus<'a> { + /// Current controller mode + pub mode: &'a str, + /// True if we did not have to boot from a backup FW bank + pub valid_fw_bank: bool, + /// FW version 0 + pub fw_version0: u32, + /// FW version 1 + pub fw_version1: u32, +} + +/// PD controller +pub struct Device<'a> { + node: intrusive_list::Node, + pub(crate) id: ControllerId, + ports: &'a [GlobalPortId], + num_ports: usize, + command: deferred::Channel>, +} + +impl intrusive_list::NodeContainer for Device<'static> { + fn get_node(&self) -> &intrusive_list::Node { + &self.node + } +} + +impl<'a> Device<'a> { + /// Create a new PD controller struct + pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { + Self { + node: intrusive_list::Node::uninit(), + id, + ports, + num_ports: ports.len(), + command: deferred::Channel::new(), + } + } + + /// Get the controller ID + pub fn id(&self) -> ControllerId { + self.id + } + + /// Send a command to this controller + pub async fn execute_command(&self, command: Command) -> Response<'_> { + self.command.execute(command).await + } + + /// Check if this controller has the given port + pub fn has_port(&self, port: GlobalPortId) -> bool { + self.lookup_local_port(port).is_ok() + } + + /// Covert a local port ID to a global port ID + pub fn lookup_global_port(&self, port: LocalPortId) -> Result { + Ok(*self.ports.get(port.0 as usize).ok_or(PdError::InvalidParams)?) + } + + /// Convert a global port ID to a local port ID + pub fn lookup_local_port(&self, port: GlobalPortId) -> Result { + self.ports + .iter() + .position(|p| *p == port) + .map(|p| LocalPortId(p as u8)) + .ok_or(PdError::InvalidParams) + } + + /// Create a command handler for this controller + /// + /// DROP SAFETY: Direct call to deferred channel primitive + pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, Command, Response<'static>> { + self.command.receive().await + } + + /// Notify that there are pending events on one or more ports + pub fn notify_ports(&self, ctx: &crate::service::context::Context, pending: PortPending) { + ctx.notify_ports(pending); + } + + /// Number of ports on this controller + pub fn num_ports(&self) -> usize { + self.num_ports + } + + /// Slice of global ports on the Device + pub fn ports(&self) -> &'a [GlobalPortId] { + self.ports + } +} + +/// PD controller trait that device drivers may use to integrate with internal messaging system +pub trait Controller { + /// Type of error returned by the bus + type BusError; + + /// Wait for a port event to occur + /// # Implementation guide + /// This function should be drop safe. + /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. + fn wait_port_event(&mut self) -> impl Future>>; + /// Returns and clears current events for the given port + /// # Implementation guide + /// This function should be drop safe. + /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. + fn clear_port_events( + &mut self, + port: LocalPortId, + ) -> impl Future>>; + /// Returns the port status + fn get_port_status(&mut self, port: LocalPortId) + -> impl Future>>; + + /// Reset the controller + fn reset_controller(&mut self) -> impl Future>>; + + /// Returns the retimer fw update state + fn get_rt_fw_update_status( + &mut self, + port: LocalPortId, + ) -> impl Future>>; + /// Set retimer fw update state + fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>>; + /// Clear retimer fw update state + fn clear_rt_fw_update_state( + &mut self, + port: LocalPortId, + ) -> impl Future>>; + /// Set retimer compliance + fn set_rt_compliance(&mut self, port: LocalPortId) -> impl Future>>; + + /// Reconfigure the retimer for the given port. + fn reconfigure_retimer(&mut self, port: LocalPortId) -> impl Future>>; + + /// Clear the dead battery flag for the given port. + fn clear_dead_battery_flag(&mut self, port: LocalPortId) + -> impl Future>>; + + /// Enable or disable sink path + fn enable_sink_path( + &mut self, + port: LocalPortId, + enable: bool, + ) -> impl Future>>; + /// Get current controller status + fn get_controller_status( + &mut self, + ) -> impl Future, Error>>; + /// Get current PD alert + fn get_pd_alert(&mut self, port: LocalPortId) -> impl Future, Error>>; + /// Set the maximum sink voltage for the given port + /// + /// This may trigger a renegotiation + fn set_max_sink_voltage( + &mut self, + port: LocalPortId, + voltage_mv: Option, + ) -> impl Future>>; + /// Set port unconstrained status + fn set_unconstrained_power( + &mut self, + port: LocalPortId, + unconstrained: bool, + ) -> impl Future>>; + + // TODO: remove all these once we migrate to a generic FW update trait + // https://github.com/OpenDevicePartnership/embedded-services/issues/242 + /// Get current FW version + fn get_active_fw_version(&mut self) -> impl Future>>; + /// Start a firmware update + fn start_fw_update(&mut self) -> impl Future>>; + /// Abort a firmware update + fn abort_fw_update(&mut self) -> impl Future>>; + /// Finalize a firmware update + fn finalize_fw_update(&mut self) -> impl Future>>; + /// Write firmware update contents + fn write_fw_contents( + &mut self, + offset: usize, + data: &[u8], + ) -> impl Future>>; + /// Get the Rx Other VDM data for the given port + fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>>; + /// Get the Rx Attention VDM data for the given port + fn get_attn_vdm(&mut self, port: LocalPortId) -> impl Future>>; + /// Send a VDM to the given port + fn send_vdm( + &mut self, + port: LocalPortId, + tx_vdm: SendVdm, + ) -> impl Future>>; + + /// Set USB control configuration for the given port + fn set_usb_control( + &mut self, + port: LocalPortId, + config: UsbControlConfig, + ) -> impl Future>>; + + /// Get DisplayPort status for the given port + fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>>; + /// Set DisplayPort configuration for the given port + fn set_dp_config( + &mut self, + port: LocalPortId, + config: DpConfig, + ) -> impl Future>>; + /// Execute PD Data Reset for the given port + fn execute_drst(&mut self, port: LocalPortId) -> impl Future>>; + + /// Set Thunderbolt configuration for the given port + fn set_tbt_config( + &mut self, + port: LocalPortId, + config: TbtConfig, + ) -> impl Future>>; + + /// Set PD state-machine configuration for the given port + fn set_pd_state_machine_config( + &mut self, + port: LocalPortId, + config: PdStateMachineConfig, + ) -> impl Future>>; + + /// Set Type-C state-machine configuration for the given port + fn set_type_c_state_machine_config( + &mut self, + port: LocalPortId, + state: TypeCStateMachineState, + ) -> impl Future>>; + + /// Execute the given UCSI command + fn execute_ucsi_command( + &mut self, + command: lpm::LocalCommand, + ) -> impl Future, Error>>; +} diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 0bf3165a..eb32c72a 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,5 +1,5 @@ use super::{ControllerWrapper, FwOfferValidator}; -use crate::type_c::controller::Controller; +use crate::wrapper::controller::Controller; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, sync::Lockable, trace}; diff --git a/type-c-service/src/type_c/event.rs b/type-c-service/src/wrapper/event.rs similarity index 100% rename from type-c-service/src/type_c/event.rs rename to type-c-service/src/wrapper/event.rs diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index c49d3147..84ae11ab 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -2,7 +2,7 @@ use embedded_services::{GlobalRawMutex, ipc::deferred}; use embedded_usb_pd::{LocalPortId, ado::Ado}; -use crate::type_c::{ +use crate::wrapper::{ controller::{self, DpStatus, PortStatus}, event::{PortNotificationSingle, PortStatusChanged}, }; @@ -102,7 +102,7 @@ pub struct OutputControllerCommand<'a> { pub mod vdm { //! Events and output for vendor-defined messaging. use super::LocalPortId; - use crate::type_c::controller::{AttnVdm, OtherVdm}; + use crate::wrapper::controller::{AttnVdm, OtherVdm}; /// The kind of output from processing a vendor-defined message. #[derive(Copy, Clone, Debug)] diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index a724e92f..a9579cda 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -19,8 +19,6 @@ use core::array::from_fn; use core::future::pending; use core::ops::DerefMut; -use crate::type_c::controller::{self, Controller, PortStatus}; -use crate::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; use crate::wrapper::backing::{ControllerState, PortState}; use cfu_service::CfuClient; use embassy_futures::select::{Either, Either5, select, select_array, select5}; @@ -42,13 +40,18 @@ use crate::{PortEventStreamer, PortEventVariant}; pub mod backing; mod cfu; pub mod config; +pub mod controller; mod dp; +pub mod event; pub mod message; mod pd; mod power; pub mod proxy; mod vdm; +use controller::{Controller, PortStatus}; +use event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; + /// Base interval for checking for FW update timeouts and recovery attempts pub const DEFAULT_FW_UPDATE_TICK_INTERVAL_MS: u64 = 5000; /// Default number of ticks before we consider a firmware update to have timed out @@ -593,16 +596,18 @@ where /// Register all devices with their respective services pub fn register( &'static self, - controllers: &intrusive_list::IntrusiveList, + service_context: &crate::service::context::Context, cfu_client: &CfuClient, ) -> Result<(), Error<::BusError>> { - controller::register_controller(controllers, self.registration.pd_controller).map_err(|_| { - error!( - "Controller{}: Failed to register PD controller", - self.registration.pd_controller.id().0 - ); - Error::Pd(PdError::Failed) - })?; + service_context + .register_controller(self.registration.pd_controller) + .map_err(|_| { + error!( + "Controller{}: Failed to register PD controller", + self.registration.pd_controller.id().0 + ); + Error::Pd(PdError::Failed) + })?; //TODO: Remove when we have a more general framework in place cfu_client.register_device(self.registration.cfu_device).map_err(|_| { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index a842bb2b..80fb8bd1 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,6 +1,6 @@ -use crate::type_c::Cached; -use crate::type_c::controller::{InternalResponseData, Response}; use crate::wrapper::backing::ControllerState; +use crate::wrapper::controller::Cached; +use crate::wrapper::controller::{InternalResponseData, Response}; use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs index a14dc82a..170fd55d 100644 --- a/type-c-service/src/wrapper/proxy.rs +++ b/type-c-service/src/wrapper/proxy.rs @@ -1,5 +1,6 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embedded_services::named::Named; use power_policy_interface::psu::{CommandData as PolicyCommandData, InternalResponseData as PolicyResponseData, Psu}; pub struct PowerProxyChannel { @@ -112,7 +113,9 @@ impl<'a> Psu for PowerProxyDevice<'a> { fn state_mut(&mut self) -> &mut power_policy_interface::psu::State { &mut self.psu_state } +} +impl<'a> Named for PowerProxyDevice<'a> { fn name(&self) -> &'static str { self.name } diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index baa08850..f4fd8387 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -4,7 +4,7 @@ use embedded_usb_pd::{Error, LocalPortId, PdError}; use crate::wrapper::message::vdm::OutputKind; -use crate::type_c::{ +use crate::wrapper::{ controller::Controller, event::{PortPending, VdmNotification}, };