diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 84f273f84..e8f756e7f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -33,7 +33,7 @@ concurrency: name: check env: # Crates that require std and won't build on embedded-targets - STD_CRATES: "fw-update-interface-mocks" + STD_EXCLUDED_CRATES: "--exclude fw-update-interface-mocks --exclude type-c-interface-mocks" jobs: fmt: @@ -121,7 +121,7 @@ jobs: run: cargo hack $COMMON_HACK_ARGS clippy --locked --target ${{ matrix.target }} - name: cargo hack if: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} - run: cargo hack $COMMON_HACK_ARGS clippy --exclude $STD_CRATES --locked --target ${{ matrix.target }} + run: cargo hack $COMMON_HACK_ARGS clippy $STD_EXCLUDED_CRATES --locked --target ${{ matrix.target }} deny: # cargo-deny checks licenses, advisories, sources, and bans for @@ -219,8 +219,8 @@ jobs: - name: cargo +${{ matrix.msrv }} check if: ${{ matrix.target != 'x86_64-unknown-linux-gnu' }} run: | - cargo check -F log --locked --workspace --exclude $STD_CRATES --target ${{ matrix.target }} - cargo check -F defmt --locked --workspace --exclude $STD_CRATES --target ${{ matrix.target }} + cargo check -F log --locked --workspace $STD_EXCLUDED_CRATES --target ${{ matrix.target }} + cargo check -F defmt --locked --workspace $STD_EXCLUDED_CRATES --target ${{ matrix.target }} check-arm-examples: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 3c5e577dd..f8b452e90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -862,7 +862,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#21d0e228d21ddc6ccaeffc01d98ef9a5b87941ef" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#0061a1e94a25c8db33ac1e8a0bb5a6b638fe2cd7" dependencies = [ "aquamarine", "bincode", @@ -2259,20 +2259,28 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", - "embassy-sync", - "embassy-time", "embedded-services", "embedded-usb-pd", "log", "power-policy-interface", ] +[[package]] +name = "type-c-interface-mocks" +version = "0.1.0" +dependencies = [ + "embedded-services", + "embedded-usb-pd", + "type-c-interface", +] + [[package]] name = "type-c-service" version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -2280,13 +2288,17 @@ dependencies = [ "embedded-hal-async", "embedded-services", "embedded-usb-pd", + "env_logger", "fw-update-interface", "heapless 0.8.0", "log", + "paste", "power-policy-interface", + "power-policy-service", "tokio", "tps6699x", "type-c-interface", + "type-c-interface-mocks", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7073ed90d..ffa41d7d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "fw-update-interface", "fw-update-interface-mocks", "mctp-rs", + "type-c-interface-mocks", ] exclude = ["examples/*"] @@ -114,6 +115,7 @@ thermal-service-relay = { path = "./thermal-service-relay" } time-alarm-service-interface = { path = "./time-alarm-service-interface" } time-alarm-service-relay = { path = "./time-alarm-service-relay" } type-c-interface = { path = "./type-c-interface" } +type-c-interface-mocks = { path = "./type-c-interface-mocks" } syn = "2.0" tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x", branch = "v0.2.0" } tokio = { version = "1.42.0" } diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 24458c167..2881b96c5 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -676,7 +676,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#21d0e228d21ddc6ccaeffc01d98ef9a5b87941ef" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#0061a1e94a25c8db33ac1e8a0bb5a6b638fe2cd7" dependencies = [ "aquamarine", "bincode", @@ -1510,8 +1510,6 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", - "embassy-sync", - "embassy-time", "embedded-services", "embedded-usb-pd", "power-policy-interface", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 755124076..78263df0f 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -8,26 +8,20 @@ use embassy_imxrt::gpio::{Input, Inverter, Pull}; use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::{self as _, Delay}; use embedded_services::GlobalRawMutex; -use embedded_services::event::MapSender; +use embedded_services::event::{MapSender, NoopSender}; use embedded_services::{error, info}; -use embedded_usb_pd::{GlobalPortId, LocalPortId}; +use embedded_usb_pd::LocalPortId; use power_policy_interface::psu; use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; -use type_c_interface::controller::ControllerId; -use type_c_interface::port::Device; -use type_c_interface::port::PortRegistration; use type_c_interface::port::event::PortEventBitfield; -use type_c_interface::service::event::PortEvent as ServicePortEvent; -use type_c_service::bridge::Bridge; -use type_c_service::bridge::event_receiver::EventReceiver as BridgeEventReceiver; use type_c_service::controller::Port; use type_c_service::controller::event_receiver::{ EventReceiver as PortEventReceiver, InterruptReceiver as _, PortEventSplitter, @@ -36,16 +30,11 @@ use type_c_service::controller::macros::PortComponents; use type_c_service::controller::state::SharedState; use type_c_service::define_controller_port_static_cell_channel; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; -use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; +use type_c_service::service::Service; +use type_c_service::service::registration::PortData; extern crate rt685s_evk_example; -const CHANNEL_CAPACITY: usize = 4; - -const CONTROLLER0_ID: ControllerId = ControllerId(0); -const PORT0_ID: GlobalPortId = GlobalPortId(0); -const PORT1_ID: GlobalPortId = GlobalPortId(1); - bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; }); @@ -57,6 +46,7 @@ type PortType = Mutex< 'static, Tps6699xMutex<'static>, SharedStateType, + DynamicSender<'static, type_c_interface::service::event::PortEventData>, DynamicSender<'static, power_policy_interface::psu::event::EventData>, DynamicSender<'static, type_c_service::controller::event::Loopback>, >, @@ -88,7 +78,20 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static>; +const PORT_COUNT: usize = 2; +type PortReceiverType = DynamicReceiver<'static, type_c_interface::service::event::PortEventData>; +type TypeCServiceEventReceiverType = type_c_service::service::event_receiver::ArrayEventReceiver< + 'static, + PORT_COUNT, + PortType, + PortReceiverType, + PowerPolicyReceiverType, +>; + +type TypeCServiceSenderType = NoopSender; +type TypeCRegistrationType = + type_c_service::service::registration::ArrayRegistration<'static, PortType, PORT_COUNT, TypeCServiceSenderType, 1>; +type TypeCServiceType = type_c_service::service::Service<'static, TypeCRegistrationType>; type PortEventReceiverType = PortEventReceiver< 'static, SharedStateType, @@ -96,18 +99,6 @@ type PortEventReceiverType = PortEventReceiver< DynamicReceiver<'static, type_c_service::controller::event::Loopback>, >; -#[embassy_executor::task] -async fn bridge_task( - mut event_receiver: BridgeEventReceiver, - mut bridge: Bridge<'static, Tps6699xMutex<'static>>, -) -> ! { - loop { - let event = event_receiver.wait_next().await; - let output = bridge.process_event(event).await; - event_receiver.finalize(output); - } -} - #[embassy_executor::task(pool_size = 2)] async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { port.lock().await.sync_state().await.unwrap(); @@ -147,8 +138,8 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Mutex, - event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, + service: &'static Mutex, + event_receiver: TypeCServiceEventReceiverType, ) { type_c_service::task::task(service, event_receiver).await; } @@ -193,9 +184,6 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); - info!("Spawining PD controller task"); static CONTROLLER_MUTEX: StaticCell> = StaticCell::new(); let controller_mutex = CONTROLLER_MUTEX.init(Mutex::new(tps6699x_drv::tps66994( @@ -205,45 +193,14 @@ async fn main(spawner: Spawner) { "tps6699x_0", ))); - static PORT0_CHANNEL: Channel = Channel::new(); - static PORT1_CHANNEL: Channel = Channel::new(); - - static PORT_REGISTRATION: StaticCell<[PortRegistration; 2]> = StaticCell::new(); - let port_registration = PORT_REGISTRATION.init([ - PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }, - PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }, - ]); - - static PD_REGISTRATION: StaticCell> = StaticCell::new(); - let pd_registration = PD_REGISTRATION.init(Device::new(CONTROLLER0_ID, port_registration)); - - controller_context.register_controller(pd_registration).unwrap(); - define_controller_port_static_cell_channel!(pub(self), port0, GlobalRawMutex, Tps6699xMutex<'static>); let PortComponents { port: port0, power_policy_receiver: policy_receiver0, event_receiver: event_receiver0, interrupt_sender: port0_interrupt_sender, - } = port0::create( - "PD0", - LocalPortId(0), - PORT0_ID, - Default::default(), - controller_mutex, - controller_context, - ); - - let bridge_receiver = BridgeEventReceiver::new(pd_registration); - let bridge = Bridge::new(controller_mutex, pd_registration); + type_c_receiver: type_c_receiver0, + } = port0::create("PD0", LocalPortId(0), Default::default(), controller_mutex); define_controller_port_static_cell_channel!(pub(self), port1, GlobalRawMutex, Tps6699xMutex<'static>); let PortComponents { @@ -251,14 +208,8 @@ async fn main(spawner: Spawner) { power_policy_receiver: policy_receiver1, event_receiver: event_receiver1, interrupt_sender: port1_interrupt_sender, - } = port1::create( - "PD1", - LocalPortId(0), - PORT1_ID, - Default::default(), - controller_mutex, - controller_context, - ); + type_c_receiver: type_c_receiver1, + } = port1::create("PD1", LocalPortId(1), Default::default(), controller_mutex); let port_event_splitter = PortEventSplitter::new([port0_interrupt_sender, port1_interrupt_sender]); @@ -286,14 +237,32 @@ async fn main(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); - let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::new( + Default::default(), + TypeCRegistrationType { + ports: [port0, port1], + service_senders: [NoopSender], + port_data: [ + PortData { + local_port: Some(LocalPortId(0)), + }, + PortData { + local_port: Some(LocalPortId(1)), + }, + ], + }, + ))); info!("Spawining type-c service task"); spawner.spawn( type_c_service_task( type_c_service, - ServiceEventReceiver::new(controller_context, power_policy_subscriber), + TypeCServiceEventReceiverType::new( + [port0, port1], + [type_c_receiver0, type_c_receiver1], + power_policy_subscriber, + ), ) .expect("Failed to create type-c service task"), ); @@ -307,7 +276,6 @@ async fn main(spawner: Spawner) { .expect("Failed to create power policy task"), ); - spawner.spawn(bridge_task(bridge_receiver, bridge).expect("Failed to create bridge task")); spawner.spawn(port_task(event_receiver0, port0).expect("Failed to create controller0 task")); spawner.spawn(port_task(event_receiver1, port1).expect("Failed to create controller1 task")); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 622336de8..018894887 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -11,7 +11,7 @@ use embassy_imxrt::gpio::{Input, Inverter, Pull}; use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; @@ -20,20 +20,15 @@ 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; -use embedded_services::event::MapSender; +use embedded_services::event::{MapSender, NoopSender}; use embedded_services::{error, info}; -use embedded_usb_pd::{GlobalPortId, LocalPortId}; +use embedded_usb_pd::LocalPortId; use power_policy_interface::psu; use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; -use type_c_interface::controller::ControllerId; use type_c_interface::port::event::PortEventBitfield; -use type_c_interface::port::{Device, PortRegistration}; -use type_c_interface::service::event::PortEvent as ServicePortEvent; -use type_c_service::bridge::Bridge; -use type_c_service::bridge::event_receiver::EventReceiver as BridgeEventReceiver; use type_c_service::controller::Port; use type_c_service::controller::event_receiver::{ EventReceiver as PortEventReceiver, InterruptReceiver as _, PortEventSplitter, @@ -42,12 +37,11 @@ use type_c_service::controller::macros::PortComponents; use type_c_service::controller::state::SharedState as PortSharedState; use type_c_service::define_controller_port_static_cell_channel; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; -use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; +use type_c_service::service::Service; +use type_c_service::service::registration::PortData; extern crate rt685s_evk_example; -const CHANNEL_CAPACITY: usize = 4; - bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; }); @@ -68,6 +62,7 @@ type PortType = Mutex< 'static, Tps6699xMutex<'static>, PortSharedStateType, + DynamicSender<'static, type_c_interface::service::event::PortEventData>, DynamicSender<'static, power_policy_interface::psu::event::EventData>, DynamicSender<'static, type_c_service::controller::event::Loopback>, >, @@ -99,33 +94,32 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static>; +const PORT_COUNT: usize = 2; +type PortReceiverType = DynamicReceiver<'static, type_c_interface::service::event::PortEventData>; +type TypeCServiceEventReceiverType = type_c_service::service::event_receiver::ArrayEventReceiver< + 'static, + PORT_COUNT, + PortType, + PortReceiverType, + PowerPolicyReceiverType, +>; + +type TypeCServiceSenderType = NoopSender; +type TypeCRegistrationType = + type_c_service::service::registration::ArrayRegistration<'static, PortType, PORT_COUNT, TypeCServiceSenderType, 1>; +type TypeCServiceType = type_c_service::service::Service<'static, TypeCRegistrationType>; type PortEventReceiverType = PortEventReceiver< 'static, PortSharedStateType, DynamicReceiver<'static, PortEventBitfield>, DynamicReceiver<'static, type_c_service::controller::event::Loopback>, >; + type CfuUpdaterSharedStateType = Mutex; type CfuUpdaterType<'a> = cfu_service::basic::Updater<'a, Tps6699xMutex<'a>, CfuUpdaterSharedStateType, CfuCustomization>; -const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; -const PORT0_ID: GlobalPortId = GlobalPortId(0); -const PORT1_ID: GlobalPortId = GlobalPortId(1); - -#[embassy_executor::task] -async fn bridge_task( - mut event_receiver: BridgeEventReceiver, - mut bridge: Bridge<'static, Tps6699xMutex<'static>>, -) -> ! { - loop { - let event = event_receiver.wait_next().await; - let output = bridge.process_event(event).await; - event_receiver.finalize(output); - } -} #[embassy_executor::task(pool_size = 2)] async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { @@ -259,8 +253,8 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Mutex, - event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, + service: &'static Mutex, + event_receiver: TypeCServiceEventReceiverType, ) { type_c_service::task::task(service, event_receiver).await; } @@ -305,9 +299,6 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); - info!("Spawining PD controller task"); static CONTROLLER_MUTEX: StaticCell> = StaticCell::new(); let controller_mutex = CONTROLLER_MUTEX.init(Mutex::new(tps6699x_drv::tps66994( @@ -317,27 +308,6 @@ async fn main(spawner: Spawner) { "tps6699x_0", ))); - static PORT0_CHANNEL: Channel = Channel::new(); - static PORT1_CHANNEL: Channel = Channel::new(); - static PORT_REGISTRATION: StaticCell<[PortRegistration; 2]> = StaticCell::new(); - let port_registration = PORT_REGISTRATION.init([ - PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }, - PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }, - ]); - - static PD_REGISTRATION: StaticCell> = StaticCell::new(); - let pd_registration = PD_REGISTRATION.init(Device::new(CONTROLLER0_ID, port_registration)); - - controller_context.register_controller(pd_registration).unwrap(); - // Create controller CFU device and updater static CFU_DEVICE: StaticCell = StaticCell::new(); let cfu_device = CFU_DEVICE.init(CfuDevice::new(CONTROLLER0_CFU_ID)); @@ -355,8 +325,6 @@ async fn main(spawner: Spawner) { CONTROLLER0_CFU_ID, CfuCustomization, ); - let bridge_receiver = BridgeEventReceiver::new(pd_registration); - let bridge = Bridge::new(controller_mutex, pd_registration); // Create CFU client static CFU_CLIENT: OnceLock = OnceLock::new(); @@ -369,14 +337,8 @@ async fn main(spawner: Spawner) { power_policy_receiver: policy_receiver0, event_receiver: event_receiver0, interrupt_sender: port0_interrupt_sender, - } = port0::create( - "PD0", - LocalPortId(0), - PORT0_ID, - Default::default(), - controller_mutex, - controller_context, - ); + type_c_receiver: type_c_receiver0, + } = port0::create("PD0", LocalPortId(0), Default::default(), controller_mutex); define_controller_port_static_cell_channel!(pub(self),port1, GlobalRawMutex, Tps6699xMutex<'static>); let PortComponents { @@ -384,14 +346,8 @@ async fn main(spawner: Spawner) { power_policy_receiver: policy_receiver1, event_receiver: event_receiver1, interrupt_sender: port1_interrupt_sender, - } = port1::create( - "PD1", - LocalPortId(0), - PORT1_ID, - Default::default(), - controller_mutex, - controller_context, - ); + type_c_receiver: type_c_receiver1, + } = port1::create("PD1", LocalPortId(1), Default::default(), controller_mutex); let port_event_splitter = PortEventSplitter::new([port0_interrupt_sender, port1_interrupt_sender]); @@ -419,14 +375,32 @@ async fn main(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); - let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::new( + Default::default(), + TypeCRegistrationType { + ports: [port0, port1], + port_data: [ + PortData { + local_port: Some(LocalPortId(0)), + }, + PortData { + local_port: Some(LocalPortId(1)), + }, + ], + service_senders: [NoopSender], + }, + ))); info!("Spawining type-c service task"); spawner.spawn( type_c_service_task( type_c_service, - ServiceEventReceiver::new(controller_context, power_policy_subscriber), + TypeCServiceEventReceiverType::new( + [port0, port1], + [type_c_receiver0, type_c_receiver1], + power_policy_subscriber, + ), ) .expect("Failed to spawn type-c service task"), ); @@ -440,7 +414,6 @@ async fn main(spawner: Spawner) { .expect("Failed to create power policy task"), ); - spawner.spawn(bridge_task(bridge_receiver, bridge).expect("Failed to create bridge task")); spawner.spawn(port_task(event_receiver0, port0).expect("Failed to create controller0 task")); spawner.spawn(port_task(event_receiver1, port1).expect("Failed to create controller1 task")); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 65e837717..ffae23a4c 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "device-driver" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa3d97b2acf349b9d52c75470e2ccfc7224c49597ec12c2fb0e28826e910495" +checksum = "c2e4547bd66511372d2a38ac3c1b2892c7ebf83cf0d5411c3406e496c85a1d96" dependencies = [ "embedded-io 0.6.1", "embedded-io-async 0.6.1", @@ -641,7 +641,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#1a8e79d3a2ac0d2837a34b045087cf0863146f7d" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#0061a1e94a25c8db33ac1e8a0bb5a6b638fe2cd7" dependencies = [ "aquamarine", "bincode", @@ -1388,7 +1388,7 @@ dependencies = [ [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x?branch=v0.2.0#c908a50747e8fcce831d4e53026072b5b6916a7b" +source = "git+https://github.com/OpenDevicePartnership/tps6699x?branch=v0.2.0#abe5568183bfe5fb2ea81806dded6cb60f3f9b58" dependencies = [ "bincode", "bitfield 0.19.4", @@ -1470,8 +1470,6 @@ name = "type-c-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "embassy-sync", - "embassy-time", "embedded-services", "embedded-usb-pd", "log", diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 5bca75b3c..a1b11088e 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -63,10 +63,6 @@ path = "src/lib/lib.rs" name = "debug" path = "src/bin/debug.rs" -[[bin]] -name = "type-c-basic" -path = "src/bin/type_c/basic.rs" - [[bin]] name = "type-c-service" path = "src/bin/type_c/service.rs" diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs deleted file mode 100644 index 516e56069..000000000 --- a/examples/std/src/bin/type_c/basic.rs +++ /dev/null @@ -1,127 +0,0 @@ -use embassy_executor::{Executor, Spawner}; -use embassy_sync::channel::Channel; -use embassy_time::Timer; -use embedded_services::GlobalRawMutex; - -use embedded_usb_pd::{GlobalPortId, PdError as Error}; -use log::*; -use static_cell::StaticCell; -use type_c_interface::controller::ControllerId; -use type_c_interface::port::{self, PortRegistration}; -use type_c_interface::service::context::{Context, DeviceContainer}; -use type_c_interface::service::event::PortEvent as ServicePortEvent; - -const CONTROLLER0_ID: ControllerId = ControllerId(0); -const PORT0_ID: GlobalPortId = GlobalPortId(0); -const PORT1_ID: GlobalPortId = GlobalPortId(1); -const CHANNEL_CAPACITY: usize = 4; - -mod test_controller { - use type_c_interface::port::PortRegistration; - - use super::*; - - pub struct Controller<'a> { - pub controller: port::Device<'a>, - } - - impl DeviceContainer for Controller<'_> { - fn get_pd_controller_device(&self) -> &port::Device<'_> { - &self.controller - } - } - - impl<'a> Controller<'a> { - pub fn new(id: ControllerId, ports: &'a [PortRegistration]) -> Self { - Self { - controller: port::Device::new(id, ports), - } - } - - async fn process_controller_command( - &self, - command: port::InternalCommandData, - ) -> Result { - match command { - port::InternalCommandData::Reset => { - info!("Reset controller"); - Ok(port::InternalResponseData::Complete) - } - port::InternalCommandData::SyncState => { - info!("Sync controller state"); - Ok(port::InternalResponseData::Complete) - } - } - } - - async fn process_port_command(&self, command: port::PortCommand) -> Result { - info!("Port command for port {}", command.port.0); - Ok(port::PortResponseData::Complete) - } - - pub async fn process(&self) { - let request = self.controller.receive().await; - let response = match request.command { - port::Command::Controller(command) => { - port::Response::Controller(self.process_controller_command(command).await) - } - port::Command::Port(command) => port::Response::Port(self.process_port_command(command).await), - }; - - request.respond(response); - } - } -} - -#[embassy_executor::task] -async fn controller_task(controller_context: &'static Context) { - static PORT0_CHANNEL: Channel = Channel::new(); - static PORT1_CHANNEL: Channel = Channel::new(); - - static PORTS: StaticCell<[PortRegistration; 2]> = StaticCell::new(); - let ports = PORTS.init([ - PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }, - PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }, - ]); - - static CONTROLLER: StaticCell = StaticCell::new(); - let controller = CONTROLLER.init(test_controller::Controller::new(CONTROLLER0_ID, ports.as_slice())); - controller_context.register_controller(controller).unwrap(); - - loop { - controller.process().await; - } -} - -#[embassy_executor::task] -async fn task(spawner: Spawner) { - embedded_services::init().await; - - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(Context::new()); - - info!("Starting controller task"); - spawner.spawn(controller_task(controller_context).expect("Failed to create controller task")); - // Wait for controller to be registered - Timer::after_secs(1).await; - - controller_context.reset_controller(CONTROLLER0_ID).await.unwrap(); -} - -fn main() { - env_logger::builder().filter_level(log::LevelFilter::Info).init(); - - static EXECUTOR: StaticCell = StaticCell::new(); - let executor = EXECUTOR.init(Executor::new()); - executor.run(|spawner| { - spawner.spawn(task(spawner).expect("Failed to create task")); - }); -} diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 008c20e48..f10bd4eb9 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,13 +1,13 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embedded_services::GlobalRawMutex; -use embedded_services::event::MapSender; +use embedded_services::event::{MapSender, NoopSender}; +use embedded_usb_pd::LocalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; -use embedded_usb_pd::{GlobalPortId, LocalPortId}; use log::*; use power_policy_interface::charger::mock::ChargerType; use power_policy_interface::psu; @@ -16,25 +16,17 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller::Port; use std_examples::type_c::mock_controller::{self, InterruptReceiver}; -use type_c_interface::controller::ControllerId; use type_c_interface::port::event::PortEventBitfield; -use type_c_interface::port::{Device, PortRegistration}; -use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_interface::service::event::PortEventData as ServicePortEventData; -use type_c_service::bridge::Bridge; -use type_c_service::bridge::event_receiver::EventReceiver as BridgeEventReceiver; use type_c_service::controller::event_receiver::InterruptReceiver as _; use type_c_service::controller::event_receiver::{EventReceiver as PortEventReceiver, PortEventSplitter}; use type_c_service::controller::macros::PortComponents; use type_c_service::controller::state::SharedState; use type_c_service::define_controller_port_static_cell_channel; +use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; use type_c_service::util::power_capability_from_current; -const CHANNEL_CAPACITY: usize = 4; -const CONTROLLER0_ID: ControllerId = ControllerId(0); -const PORT0_ID: GlobalPortId = GlobalPortId(0); const DELAY_MS: u64 = 1000; type ControllerType = Mutex>; @@ -59,7 +51,20 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static>; +const PORT_COUNT: usize = 1; +type PortReceiverType = DynamicReceiver<'static, type_c_interface::service::event::PortEventData>; +type TypeCServiceEventReceiverType = type_c_service::service::event_receiver::ArrayEventReceiver< + 'static, + PORT_COUNT, + PortType, + PortReceiverType, + PowerPolicyReceiverType, +>; +type TypeCServiceSenderType = NoopSender; +type TypeCRegistrationType = + type_c_service::service::registration::ArrayRegistration<'static, PortType, PORT_COUNT, TypeCServiceSenderType, 1>; + +type ServiceType = type_c_service::service::Service<'static, TypeCRegistrationType>; type SharedStateType = Mutex; type PortEventReceiverType = PortEventReceiver< 'static, @@ -95,48 +100,15 @@ async fn interrupt_splitter_task( } } -#[embassy_executor::task] -async fn bridge_task( - mut event_receiver: BridgeEventReceiver, - mut bridge: Bridge<'static, Mutex>>, -) -> ! { - loop { - let event = event_receiver.wait_next().await; - let output = bridge.process_event(event).await; - event_receiver.finalize(output); - } -} - #[embassy_executor::task] async fn task(spawner: Spawner) { embedded_services::init().await; - // Create power policy service - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(type_c_interface::service::context::Context::new()); - static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); static CONTROLLER: StaticCell = StaticCell::new(); - let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - - static PORT_CHANNEL: Channel = Channel::new(); - - static PORT_REGISTRATION: StaticCell<[PortRegistration; 1]> = StaticCell::new(); - let port_registration = PORT_REGISTRATION.init([PortRegistration { - id: PORT0_ID, - sender: PORT_CHANNEL.dyn_sender(), - receiver: PORT_CHANNEL.dyn_receiver(), - }]); - - static PD_REGISTRATION: StaticCell> = StaticCell::new(); - let pd_registration = PD_REGISTRATION.init(Device::new(CONTROLLER0_ID, port_registration)); - - controller_context.register_controller(pd_registration).unwrap(); - - let bridge_receiver = BridgeEventReceiver::new(pd_registration); - let bridge = Bridge::new(controller, pd_registration); + let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state, "Controller0"))); define_controller_port_static_cell_channel!(pub(self), port, GlobalRawMutex, Mutex>); let PortComponents { @@ -144,14 +116,8 @@ async fn task(spawner: Spawner) { power_policy_receiver, event_receiver, interrupt_sender: port_interrupt_sender, - } = port::create( - "PD0", - LocalPortId(0), - PORT0_ID, - Default::default(), - controller, - controller_context, - ); + type_c_receiver, + } = port::create("PD0", LocalPortId(0), Default::default(), controller); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -178,7 +144,16 @@ async fn task(spawner: Spawner) { ))); static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); - let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Config::default(), controller_context))); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::new( + Config::default(), + type_c_service::service::registration::ArrayRegistration { + ports: [port], + service_senders: [NoopSender], + port_data: [type_c_service::service::registration::PortData { + local_port: Some(LocalPortId(0)), + }], + }, + ))); // Spin up power policy service spawner.spawn( @@ -188,12 +163,11 @@ async fn task(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - ServiceEventReceiver::new(controller_context, power_policy_subscriber), + TypeCServiceEventReceiverType::new([port], [type_c_receiver], power_policy_subscriber), ) .expect("Failed to create type-c service task"), ); - spawner.spawn(bridge_task(bridge_receiver, bridge).expect("Failed to create bridge task")); spawner.spawn(port_task(event_receiver, port).expect("Failed to create controller task")); spawner.spawn( @@ -239,7 +213,7 @@ async fn power_policy_psu_task( #[embassy_executor::task] async fn type_c_service_task( service: &'static Mutex, - event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, + event_receiver: TypeCServiceEventReceiverType, ) { info!("Starting type-c task"); type_c_service::task::task(service, event_receiver).await; diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 0262d5fd8..c93921f0c 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -6,7 +6,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embedded_services::IntrusiveList; -use embedded_services::event::MapSender; +use embedded_services::event::{MapSender, NoopSender}; use embedded_services::{GlobalRawMutex, event}; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -24,26 +24,16 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller::{self, InterruptReceiver, Port}; use type_c_interface::controller::ControllerId; use type_c_interface::port::event::PortEventBitfield; -use type_c_interface::port::{Device, PortRegistration}; -use type_c_interface::service::context::Context; use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventData as ServicePortEventData}; -use type_c_service::bridge::Bridge; -use type_c_service::bridge::event_receiver::EventReceiver as BridgeEventReceiver; use type_c_service::controller::event::Event as PortEvent; use type_c_service::controller::event_receiver::InterruptReceiver as _; use type_c_service::controller::event_receiver::{EventReceiver as PortEventReceiver, PortEventSplitter}; use type_c_service::controller::macros::PortComponents; use type_c_service::controller::state::SharedState; use type_c_service::define_controller_port_static_cell_channel; +use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; - -const CHANNEL_CAPACITY: usize = 4; -const NUM_PD_CONTROLLERS: usize = 2; -const CONTROLLER0_ID: ControllerId = ControllerId(0); -const CONTROLLER1_ID: ControllerId = ControllerId(1); -const PORT0_ID: GlobalPortId = GlobalPortId(0); -const PORT1_ID: GlobalPortId = GlobalPortId(1); +use type_c_service::service::registration::PortData; type ControllerType = Mutex>; type PortType = Mutex>; @@ -67,7 +57,19 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static>; +const PORT_COUNT: usize = 2; +type TypeCServiceSenderType = NoopSender; +type PortReceiverType = DynamicReceiver<'static, type_c_interface::service::event::PortEventData>; +type TypeCServiceEventReceiverType = type_c_service::service::event_receiver::ArrayEventReceiver< + 'static, + PORT_COUNT, + PortType, + PortReceiverType, + PowerPolicyReceiverType, +>; +type TypeCRegistrationType = + type_c_service::service::registration::ArrayRegistration<'static, PortType, PORT_COUNT, TypeCServiceSenderType, 1>; +type TypeCServiceType = Service<'static, TypeCRegistrationType>; type SharedStateType = Mutex; type PortEventReceiverType = PortEventReceiver< 'static, @@ -77,7 +79,7 @@ type PortEventReceiverType = PortEventReceiver< >; #[embassy_executor::task] -async fn opm_task(_context: &'static Context, _state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { +async fn opm_task(_state: [&'static mock_controller::ControllerState; PORT_COUNT]) { // TODO: migrate this logic to an integration test when we move away from 'static lifetimes. /*const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, @@ -208,18 +210,6 @@ async fn opm_task(_context: &'static Context, _state: [&'static mock_controller: }*/ } -#[embassy_executor::task(pool_size = 2)] -async fn bridge_task( - mut event_receiver: BridgeEventReceiver, - mut bridge: Bridge<'static, Mutex>>, -) -> ! { - loop { - let event = event_receiver.wait_next().await; - let output = bridge.process_event(event).await; - event_receiver.finalize(output); - } -} - #[embassy_executor::task(pool_size = 2)] async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { loop { @@ -252,8 +242,8 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Mutex, - event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, + service: &'static Mutex, + event_receiver: TypeCServiceEventReceiverType, ) { type_c_service::task::task(service, event_receiver).await; } @@ -264,26 +254,10 @@ async fn task(spawner: Spawner) { embedded_services::init().await; - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(Context::new()); - static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); static CONTROLLER0: StaticCell = StaticCell::new(); - let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); - - static PORT_CHANNEL0: Channel = Channel::new(); - static PORT_REGISTRATION0: StaticCell<[PortRegistration; 1]> = StaticCell::new(); - let port_registration0 = PORT_REGISTRATION0.init([PortRegistration { - id: PORT0_ID, - sender: PORT_CHANNEL0.dyn_sender(), - receiver: PORT_CHANNEL0.dyn_receiver(), - }]); - - static PD_REGISTRATION0: StaticCell> = StaticCell::new(); - let pd_registration0 = PD_REGISTRATION0.init(Device::new(CONTROLLER0_ID, port_registration0)); - - controller_context.register_controller(pd_registration0).unwrap(); + let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0, "Controller0"))); define_controller_port_static_cell_channel!(pub(self), port0, GlobalRawMutex, Mutex>); let PortComponents { @@ -291,35 +265,13 @@ async fn task(spawner: Spawner) { power_policy_receiver: policy_receiver0, event_receiver: event_receiver0, interrupt_sender: port0_interrupt_sender, - } = port0::create( - "PD0", - LocalPortId(0), - PORT0_ID, - Default::default(), - controller0, - controller_context, - ); - - let bridge_receiver0 = BridgeEventReceiver::new(pd_registration0); - let bridge0 = Bridge::new(controller0, pd_registration0); + type_c_receiver: type_c_receiver0, + } = port0::create("PD0", LocalPortId(0), Default::default(), controller0); static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); static CONTROLLER1: StaticCell = StaticCell::new(); - let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); - - static PORT1_CHANNEL: Channel = Channel::new(); - static PORT_REGISTRATION1: StaticCell<[PortRegistration; 1]> = StaticCell::new(); - let port_registration1 = PORT_REGISTRATION1.init([PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }]); - - static PD_REGISTRATION1: StaticCell> = StaticCell::new(); - let pd_registration1 = PD_REGISTRATION1.init(Device::new(CONTROLLER1_ID, port_registration1)); - - controller_context.register_controller(pd_registration1).unwrap(); + let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1, "Controller1"))); define_controller_port_static_cell_channel!(pub(self), port1, GlobalRawMutex, Mutex>); let PortComponents { @@ -327,17 +279,8 @@ async fn task(spawner: Spawner) { power_policy_receiver: policy_receiver1, event_receiver: event_receiver1, interrupt_sender: port1_interrupt_sender, - } = port1::create( - "PD1", - LocalPortId(0), - PORT1_ID, - Default::default(), - controller1, - controller_context, - ); - - let bridge_receiver1 = BridgeEventReceiver::new(pd_registration1); - let bridge1 = Bridge::new(controller1, pd_registration1); + type_c_receiver: type_c_receiver1, + } = port1::create("PD1", LocalPortId(0), Default::default(), controller1); // Create power policy service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -364,8 +307,8 @@ async fn task(spawner: Spawner) { ))); // Create type-c service - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); - let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create( + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::new( Config { ucsi_capabilities: UcsiCapabilities { num_connectors: 2, @@ -390,7 +333,18 @@ async fn task(spawner: Spawner) { ), ..Default::default() }, - controller_context, + TypeCRegistrationType { + ports: [port0, port1], + service_senders: [NoopSender], + port_data: [ + PortData { + local_port: Some(LocalPortId(0)), + }, + PortData { + local_port: Some(LocalPortId(1)), + }, + ], + }, ))); spawner.spawn( @@ -404,12 +358,14 @@ async fn task(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - ServiceEventReceiver::new(controller_context, power_policy_subscriber), + TypeCServiceEventReceiverType::new( + [port0, port1], + [type_c_receiver0, type_c_receiver1], + power_policy_subscriber, + ), ) .expect("Failed to create type-c service task"), ); - spawner.spawn(bridge_task(bridge_receiver0, bridge0).expect("Failed to create bridge0 task")); - spawner.spawn(bridge_task(bridge_receiver1, bridge1).expect("Failed to create bridge1 task")); spawner.spawn(port_task(event_receiver0, port0).expect("Failed to create wrapper0 task")); spawner.spawn( interrupt_splitter_task( @@ -426,7 +382,7 @@ async fn task(spawner: Spawner) { ) .expect("Failed to create interrupt splitter 1 task"), ); - spawner.spawn(opm_task(controller_context, [state0, state1]).expect("Failed to create opm task")); + spawner.spawn(opm_task([state0, state1]).expect("Failed to create opm task")); } fn main() { diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 8b1191463..a4e08acae 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,5 +1,4 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::channel::Channel; use embassy_sync::channel::DynamicReceiver; use embassy_sync::channel::DynamicSender; use embassy_sync::mutex::Mutex; @@ -7,7 +6,8 @@ use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embedded_services::GlobalRawMutex; use embedded_services::event::MapSender; -use embedded_usb_pd::{GlobalPortId, LocalPortId}; +use embedded_services::event::NoopSender; +use embedded_usb_pd::LocalPortId; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::charger::mock::ChargerType; @@ -17,30 +17,14 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller::Port; use std_examples::type_c::mock_controller::{self, InterruptReceiver}; -use type_c_interface::controller::ControllerId; -use type_c_interface::port::Device; -use type_c_interface::port::PortRegistration; use type_c_interface::port::event::PortEventBitfield; -use type_c_interface::service::event::PortEvent as ServicePortEvent; -use type_c_service::bridge::Bridge; -use type_c_service::bridge::event_receiver::EventReceiver as BridgeEventReceiver; use type_c_service::controller::event_receiver::InterruptReceiver as _; use type_c_service::controller::event_receiver::{EventReceiver as PortEventReceiver, PortEventSplitter}; use type_c_service::controller::macros::PortComponents; use type_c_service::controller::state::SharedState; use type_c_service::define_controller_port_static_cell_channel; -use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; - -const CHANNEL_CAPACITY: usize = 4; - -const CONTROLLER0_ID: ControllerId = ControllerId(0); -const PORT0_ID: GlobalPortId = GlobalPortId(0); - -const CONTROLLER1_ID: ControllerId = ControllerId(1); -const PORT1_ID: GlobalPortId = GlobalPortId(1); - -const CONTROLLER2_ID: ControllerId = ControllerId(2); -const PORT2_ID: GlobalPortId = GlobalPortId(2); +use type_c_service::service::Service; +use type_c_service::service::registration::PortData; const DELAY_MS: u64 = 1000; @@ -66,7 +50,19 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static>; +const PORT_COUNT: usize = 3; +type TypeCServiceSenderType = NoopSender; +type PortReceiverType = DynamicReceiver<'static, type_c_interface::service::event::PortEventData>; +type TypeCServiceEventReceiverType = type_c_service::service::event_receiver::ArrayEventReceiver< + 'static, + PORT_COUNT, + PortType, + PortReceiverType, + PowerPolicyReceiverType, +>; +type TypeCRegistrationType = + type_c_service::service::registration::ArrayRegistration<'static, PortType, PORT_COUNT, TypeCServiceSenderType, 1>; +type TypeCServiceType = Service<'static, TypeCRegistrationType>; type SharedStateType = Mutex; type PortEventReceiverType = PortEventReceiver< 'static, @@ -75,18 +71,6 @@ type PortEventReceiverType = PortEventReceiver< DynamicReceiver<'static, type_c_service::controller::event::Loopback>, >; -#[embassy_executor::task(pool_size = 3)] -async fn bridge_task( - mut event_receiver: BridgeEventReceiver, - mut bridge: Bridge<'static, Mutex>>, -) -> ! { - loop { - let event = event_receiver.wait_next().await; - let output = bridge.process_event(event).await; - event_receiver.finalize(output); - } -} - #[embassy_executor::task(pool_size = 3)] async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { loop { @@ -113,27 +97,10 @@ async fn interrupt_splitter_task( async fn task(spawner: Spawner) { embedded_services::init().await; - // Create power policy service - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); - static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); static CONTROLLER0: StaticCell = StaticCell::new(); - let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); - - static PORT_CHANNEL0: Channel = Channel::new(); - static PORT_REGISTRATION0: StaticCell<[PortRegistration; 1]> = StaticCell::new(); - let port_registration0 = PORT_REGISTRATION0.init([PortRegistration { - id: PORT0_ID, - sender: PORT_CHANNEL0.dyn_sender(), - receiver: PORT_CHANNEL0.dyn_receiver(), - }]); - - static PD_REGISTRATION0: StaticCell> = StaticCell::new(); - let pd_registration0 = PD_REGISTRATION0.init(Device::new(CONTROLLER0_ID, port_registration0)); - - controller_context.register_controller(pd_registration0).unwrap(); + let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0, "Controller0"))); define_controller_port_static_cell_channel!(pub(self), port0, GlobalRawMutex, Mutex>); let PortComponents { @@ -141,34 +108,13 @@ async fn task(spawner: Spawner) { power_policy_receiver: policy_receiver0, event_receiver: event_receiver0, interrupt_sender: port0_interrupt_sender, - } = port0::create( - "PD0", - LocalPortId(0), - PORT0_ID, - Default::default(), - controller0, - controller_context, - ); - let bridge_receiver0 = BridgeEventReceiver::new(pd_registration0); - let bridge0 = Bridge::new(controller0, pd_registration0); + type_c_receiver: type_c_receiver0, + } = port0::create("PD0", LocalPortId(0), Default::default(), controller0); static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); static CONTROLLER1: StaticCell = StaticCell::new(); - let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); - - static PORT1_CHANNEL: Channel = Channel::new(); - static PORT_REGISTRATION1: StaticCell<[PortRegistration; 1]> = StaticCell::new(); - let port_registration1 = PORT_REGISTRATION1.init([PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }]); - - static PD_REGISTRATION1: StaticCell> = StaticCell::new(); - let pd_registration1 = PD_REGISTRATION1.init(Device::new(CONTROLLER1_ID, port_registration1)); - - controller_context.register_controller(pd_registration1).unwrap(); + let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1, "Controller1"))); define_controller_port_static_cell_channel!(pub(self), port1, GlobalRawMutex, Mutex>); let PortComponents { @@ -176,34 +122,13 @@ async fn task(spawner: Spawner) { power_policy_receiver: policy_receiver1, event_receiver: event_receiver1, interrupt_sender: port1_interrupt_sender, - } = port1::create( - "PD1", - LocalPortId(0), - PORT1_ID, - Default::default(), - controller1, - controller_context, - ); - let bridge_receiver1 = BridgeEventReceiver::new(pd_registration1); - let bridge1 = Bridge::new(controller1, pd_registration1); + type_c_receiver: type_c_receiver1, + } = port1::create("PD1", LocalPortId(0), Default::default(), controller1); static STATE2: StaticCell = StaticCell::new(); let state2 = STATE2.init(mock_controller::ControllerState::new()); static CONTROLLER2: StaticCell = StaticCell::new(); - let controller2 = CONTROLLER2.init(Mutex::new(mock_controller::Controller::new(state2))); - - static PORT2_CHANNEL: Channel = Channel::new(); - static PORT_REGISTRATION2: StaticCell<[PortRegistration; 1]> = StaticCell::new(); - let port_registration2 = PORT_REGISTRATION2.init([PortRegistration { - id: PORT2_ID, - sender: PORT2_CHANNEL.dyn_sender(), - receiver: PORT2_CHANNEL.dyn_receiver(), - }]); - - static PD_REGISTRATION2: StaticCell> = StaticCell::new(); - let pd_registration2 = PD_REGISTRATION2.init(Device::new(CONTROLLER2_ID, port_registration2)); - - controller_context.register_controller(pd_registration2).unwrap(); + let controller2 = CONTROLLER2.init(Mutex::new(mock_controller::Controller::new(state2, "Controller2"))); define_controller_port_static_cell_channel!(pub(self), port2, GlobalRawMutex, Mutex>); let PortComponents { @@ -211,16 +136,8 @@ async fn task(spawner: Spawner) { power_policy_receiver: policy_receiver2, event_receiver: event_receiver2, interrupt_sender: port2_interrupt_sender, - } = port2::create( - "PD2", - LocalPortId(0), - PORT2_ID, - Default::default(), - controller2, - controller_context, - ); - let bridge_receiver2 = BridgeEventReceiver::new(pd_registration2); - let bridge2 = Bridge::new(controller2, pd_registration2); + type_c_receiver: type_c_receiver2, + } = port2::create("PD2", LocalPortId(0), Default::default(), controller2); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< @@ -246,8 +163,25 @@ async fn task(spawner: Spawner) { ))); // Create type-c service - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); - let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::new( + Default::default(), + TypeCRegistrationType { + ports: [port0, port1, port2], + service_senders: [NoopSender], + port_data: [ + PortData { + local_port: Some(LocalPortId(0)), + }, + PortData { + local_port: Some(LocalPortId(1)), + }, + PortData { + local_port: Some(LocalPortId(2)), + }, + ], + }, + ))); spawner.spawn( power_policy_task( @@ -262,14 +196,15 @@ async fn task(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - ServiceEventReceiver::new(controller_context, power_policy_subscriber), + TypeCServiceEventReceiverType::new( + [port0, port1, port2], + [type_c_receiver0, type_c_receiver1, type_c_receiver2], + power_policy_subscriber, + ), ) .expect("Failed to create type-c service task"), ); - spawner.spawn(bridge_task(bridge_receiver0, bridge0).expect("Failed to create bridge0 task")); - spawner.spawn(bridge_task(bridge_receiver1, bridge1).expect("Failed to create bridge1 task")); - spawner.spawn(bridge_task(bridge_receiver2, bridge2).expect("Failed to create bridge2 task")); spawner.spawn(port_task(event_receiver0, port0).expect("Failed to create controller0 task")); spawner.spawn( interrupt_splitter_task( @@ -354,8 +289,8 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Mutex, - event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, + service: &'static Mutex, + event_receiver: TypeCServiceEventReceiverType, ) { info!("Starting type-c task"); type_c_service::task::task(service, event_receiver).await; diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 5acab89d4..206857cd0 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -2,6 +2,7 @@ use std::num::NonZeroU8; use embassy_sync::{channel, mutex::Mutex, signal::Signal}; use embedded_services::GlobalRawMutex; +use embedded_services::named::Named; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; @@ -107,11 +108,12 @@ impl Default for ControllerState { pub struct Controller<'a> { state: &'a ControllerState, + name: &'static str, } impl<'a> Controller<'a> { - pub fn new(state: &'a ControllerState) -> Self { - Self { state } + pub fn new(state: &'a ControllerState, name: &'static str) -> Self { + Self { state, name } } /// Function to demonstrate calling functions directly on the controller @@ -133,6 +135,12 @@ impl type_c_service::controller::event_receiver::InterruptReceiv } } +impl Named for Controller<'_> { + fn name(&self) -> &'static str { + self.name + } +} + impl type_c_interface::controller::Controller for Controller<'_> { async fn reset_controller(&mut self) -> Result<(), PdError> { debug!("Reset controller"); @@ -316,6 +324,7 @@ pub type Port<'a> = type_c_service::controller::Port< 'a, Mutex>, Mutex, + channel::DynamicSender<'a, type_c_interface::service::event::PortEventData>, channel::DynamicSender<'a, power_policy_interface::psu::event::EventData>, channel::DynamicSender<'a, type_c_service::controller::event::Loopback>, >; diff --git a/type-c-interface-mocks/Cargo.toml b/type-c-interface-mocks/Cargo.toml new file mode 100644 index 000000000..254a74dd3 --- /dev/null +++ b/type-c-interface-mocks/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "type-c-interface-mocks" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +embedded-services = { workspace = true } +embedded-usb-pd = { workspace = true } +type-c-interface = { workspace = true } + +[lints] +workspace = true diff --git a/type-c-interface-mocks/src/controller/mod.rs b/type-c-interface-mocks/src/controller/mod.rs new file mode 100644 index 000000000..1af2e3747 --- /dev/null +++ b/type-c-interface-mocks/src/controller/mod.rs @@ -0,0 +1,85 @@ +//! Mock controller implementations for testing + +use std::collections::VecDeque; + +use embedded_services::named::Named; +use embedded_usb_pd::{PdError, ado::Ado}; +use type_c_interface::control::{ + dp::DpStatus, + pd::PortStatus, + vdm::{AttnVdm, OtherVdm}, +}; + +pub mod pd; +pub mod ucsi; + +/// Contains a controller function call and its arguments +pub enum FnCall { + Pd(pd::FnCall), + Ucsi(ucsi::FnCall), +} + +/// Mock PD controller for use in tests +pub struct Mock { + name: &'static str, + /// Recorded function calls + pub fn_calls: VecDeque, + /// Next result to return for [`type_c_interface::controller::pd::Pd::get_port_status`] + pub next_result_get_port_status: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::clear_dead_battery_flag`] + pub next_result_clear_dead_battery_flag: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::enable_sink_path`] + pub next_result_enable_sink_path: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::get_pd_alert`] + pub next_result_get_pd_alert: Option, PdError>>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::set_unconstrained_power`] + pub next_result_set_unconstrained_power: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::get_other_vdm`] + pub next_result_get_other_vdm: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::get_attn_vdm`] + pub next_result_get_attn_vdm: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::send_vdm`] + pub next_result_send_vdm: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::execute_drst`] + pub next_result_execute_drst: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::get_dp_status`] + pub next_result_get_dp_status: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::set_dp_config`] + pub next_result_set_dp_config: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::set_tbt_config`] + pub next_result_set_tbt_config: Option>, + /// Next result to return for [`type_c_interface::controller::pd::Pd::set_usb_control`] + pub next_result_set_usb_control: Option>, + /// Next result to return for [`type_c_interface::ucsi::Lpm::execute_lpm_command`] + pub next_result_execute_lpm_command: Option, PdError>>, +} + +impl Mock { + /// Create a new mock with the given name + pub fn new(name: &'static str) -> Self { + Self { + fn_calls: VecDeque::new(), + name, + next_result_get_port_status: None, + next_result_clear_dead_battery_flag: None, + next_result_enable_sink_path: None, + next_result_get_pd_alert: None, + next_result_set_unconstrained_power: None, + next_result_get_other_vdm: None, + next_result_get_attn_vdm: None, + next_result_send_vdm: None, + next_result_execute_drst: None, + next_result_get_dp_status: None, + next_result_set_dp_config: None, + next_result_set_tbt_config: None, + next_result_set_usb_control: None, + next_result_execute_lpm_command: None, + } + } +} + +impl Named for Mock { + fn name(&self) -> &'static str { + self.name + } +} diff --git a/type-c-interface-mocks/src/controller/pd.rs b/type-c-interface-mocks/src/controller/pd.rs new file mode 100644 index 000000000..040c5e08b --- /dev/null +++ b/type-c-interface-mocks/src/controller/pd.rs @@ -0,0 +1,111 @@ +//! Mock implementation of [`type_c_interface::controller::pd::Pd`] + +use embedded_usb_pd::{LocalPortId, PdError, ado::Ado}; +use type_c_interface::{ + control::{ + dp::{DpConfig, DpStatus}, + pd::PortStatus, + tbt::TbtConfig, + usb::UsbControlConfig, + vdm::{AttnVdm, OtherVdm, SendVdm}, + }, + controller::pd::Pd, +}; + +use super::FnCall as ControllerFnCall; +use super::Mock; + +/// Contains a [`Pd`] function call and its arguments +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FnCall { + GetPortStatus(LocalPortId), + ClearDeadBatteryFlag(LocalPortId), + EnableSinkPath(LocalPortId, bool), + GetPdAlert(LocalPortId), + SetUnconstrainedPower(LocalPortId, bool), + GetOtherVdm(LocalPortId), + GetAttnVdm(LocalPortId), + SendVdm(LocalPortId, SendVdm), + ExecuteDrst(LocalPortId), + GetDpStatus(LocalPortId), + SetDpConfig(LocalPortId, DpConfig), + SetTbtConfig(LocalPortId, TbtConfig), + SetUsbControl(LocalPortId, UsbControlConfig), +} + +impl Pd for Mock { + async fn get_port_status(&mut self, port: LocalPortId) -> Result { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::GetPortStatus(port))); + self.next_result_get_port_status.unwrap_or(Ok(Default::default())) + } + + async fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::ClearDeadBatteryFlag(port))); + self.next_result_clear_dead_battery_flag.unwrap_or(Ok(())) + } + + async fn enable_sink_path(&mut self, port: LocalPortId, enable: bool) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::EnableSinkPath(port, enable))); + self.next_result_enable_sink_path.unwrap_or(Ok(())) + } + + async fn get_pd_alert(&mut self, port: LocalPortId) -> Result, PdError> { + self.fn_calls.push_back(ControllerFnCall::Pd(FnCall::GetPdAlert(port))); + self.next_result_get_pd_alert.unwrap_or(Ok(None)) + } + + async fn set_unconstrained_power(&mut self, port: LocalPortId, unconstrained: bool) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::SetUnconstrainedPower(port, unconstrained))); + self.next_result_set_unconstrained_power.unwrap_or(Ok(())) + } + + async fn get_other_vdm(&mut self, port: LocalPortId) -> Result { + self.fn_calls.push_back(ControllerFnCall::Pd(FnCall::GetOtherVdm(port))); + self.next_result_get_other_vdm + .expect("next_result_get_other_vdm not set") + } + + async fn get_attn_vdm(&mut self, port: LocalPortId) -> Result { + self.fn_calls.push_back(ControllerFnCall::Pd(FnCall::GetAttnVdm(port))); + self.next_result_get_attn_vdm.expect("next_result_get_attn_vdm not set") + } + + async fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::SendVdm(port, tx_vdm))); + self.next_result_send_vdm.unwrap_or(Ok(())) + } + + async fn execute_drst(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.fn_calls.push_back(ControllerFnCall::Pd(FnCall::ExecuteDrst(port))); + self.next_result_execute_drst.unwrap_or(Ok(())) + } + + async fn get_dp_status(&mut self, port: LocalPortId) -> Result { + self.fn_calls.push_back(ControllerFnCall::Pd(FnCall::GetDpStatus(port))); + self.next_result_get_dp_status + .expect("next_result_get_dp_status not set") + } + + async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::SetDpConfig(port, config))); + self.next_result_set_dp_config.unwrap_or(Ok(())) + } + + async fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::SetTbtConfig(port, config))); + self.next_result_set_tbt_config.unwrap_or(Ok(())) + } + + async fn set_usb_control(&mut self, port: LocalPortId, config: UsbControlConfig) -> Result<(), PdError> { + self.fn_calls + .push_back(ControllerFnCall::Pd(FnCall::SetUsbControl(port, config))); + self.next_result_set_usb_control.unwrap_or(Ok(())) + } +} diff --git a/type-c-interface-mocks/src/controller/ucsi.rs b/type-c-interface-mocks/src/controller/ucsi.rs new file mode 100644 index 000000000..80fd4e049 --- /dev/null +++ b/type-c-interface-mocks/src/controller/ucsi.rs @@ -0,0 +1,20 @@ +use embedded_usb_pd::PdError; +use embedded_usb_pd::ucsi::lpm; +use type_c_interface::ucsi::Lpm as UcsiLpm; + +use super::FnCall as ControllerFnCall; +use super::Mock; + +/// Contains a [`UcsiLpm`] function call and its arguments +pub enum FnCall { + ExecuteLpm(lpm::LocalCommand), +} + +impl UcsiLpm for Mock { + async fn execute_lpm_command(&mut self, command: lpm::LocalCommand) -> Result, PdError> { + self.fn_calls + .push_back(ControllerFnCall::Ucsi(FnCall::ExecuteLpm(command))); + self.next_result_execute_lpm_command + .expect("next_result_execute_lpm_command not set") + } +} diff --git a/type-c-interface-mocks/src/lib.rs b/type-c-interface-mocks/src/lib.rs new file mode 100644 index 000000000..27362229d --- /dev/null +++ b/type-c-interface-mocks/src/lib.rs @@ -0,0 +1,3 @@ +#![allow(clippy::expect_used)] + +pub mod controller; diff --git a/type-c-interface/Cargo.toml b/type-c-interface/Cargo.toml index 8b11bc79c..27857480f 100644 --- a/type-c-interface/Cargo.toml +++ b/type-c-interface/Cargo.toml @@ -10,8 +10,6 @@ ignored = ["log"] [dependencies] bitfield.workspace = true -embassy-sync.workspace = true -embassy-time.workspace = true log = { workspace = true, optional = true } defmt = { workspace = true, optional = true } embedded-services.workspace = true @@ -25,16 +23,8 @@ workspace = true default = [] defmt = [ "dep:defmt", - "embassy-sync/defmt", - "embassy-time/defmt", "embedded-services/defmt", "embedded-usb-pd/defmt", "power-policy-interface/defmt", ] -log = [ - "dep:log", - "embassy-sync/log", - "embassy-time/log", - "embedded-services/log", - "power-policy-interface/log", -] +log = ["dep:log", "embedded-services/log", "power-policy-interface/log"] diff --git a/type-c-interface/src/control/dp.rs b/type-c-interface/src/control/dp.rs index 21c616521..7d79d7905 100644 --- a/type-c-interface/src/control/dp.rs +++ b/type-c-interface/src/control/dp.rs @@ -12,7 +12,7 @@ pub struct DpPinConfig { } /// DisplayPort status data -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DpStatus { /// DP alt-mode entered @@ -22,7 +22,7 @@ pub struct DpStatus { } /// DisplayPort configuration data -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DpConfig { /// DP alt-mode enabled diff --git a/type-c-interface/src/control/pd.rs b/type-c-interface/src/control/pd.rs index a4b9c8a2a..db34072cd 100644 --- a/type-c-interface/src/control/pd.rs +++ b/type-c-interface/src/control/pd.rs @@ -7,7 +7,7 @@ use embedded_usb_pd::{ }; /// Port status -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PortStatus { /// Current available source contract diff --git a/type-c-interface/src/control/retimer.rs b/type-c-interface/src/control/retimer.rs index e3460638d..2e997568c 100644 --- a/type-c-interface/src/control/retimer.rs +++ b/type-c-interface/src/control/retimer.rs @@ -1,7 +1,7 @@ //! Retimer related control types /// Retimer update state -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RetimerFwUpdateState { /// Retimer FW Update Inactive diff --git a/type-c-interface/src/control/tbt.rs b/type-c-interface/src/control/tbt.rs index 192ed6c23..7b9ef2aaa 100644 --- a/type-c-interface/src/control/tbt.rs +++ b/type-c-interface/src/control/tbt.rs @@ -2,7 +2,7 @@ /// Thunderbolt control configuration #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] +#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] pub struct TbtConfig { /// Enable Thunderbolt pub tbt_enabled: bool, diff --git a/type-c-interface/src/control/usb.rs b/type-c-interface/src/control/usb.rs index 237eb947d..ed44ff7c5 100644 --- a/type-c-interface/src/control/usb.rs +++ b/type-c-interface/src/control/usb.rs @@ -2,7 +2,7 @@ /// USB control configuration #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct UsbControlConfig { /// Enable USB2 data path pub usb2_enabled: bool, diff --git a/type-c-interface/src/controller/mod.rs b/type-c-interface/src/controller/mod.rs index d149d5da6..9268ba28f 100644 --- a/type-c-interface/src/controller/mod.rs +++ b/type-c-interface/src/controller/mod.rs @@ -1,5 +1,6 @@ //! Module for PD controller related code +use embedded_services::named::Named; use embedded_usb_pd::PdError; pub mod electrical_disconnect; @@ -15,7 +16,7 @@ pub mod type_c; pub struct ControllerId(pub u8); /// PD controller trait -pub trait Controller { +pub trait Controller: Named { /// Reset the controller fn reset_controller(&mut self) -> impl Future>; } diff --git a/type-c-interface/src/controller/pd.rs b/type-c-interface/src/controller/pd.rs index f909f4847..4e20afd9f 100644 --- a/type-c-interface/src/controller/pd.rs +++ b/type-c-interface/src/controller/pd.rs @@ -1,3 +1,4 @@ +use embedded_services::named::Named; use embedded_usb_pd::{LocalPortId, PdError, ado::Ado}; use crate::control::{ @@ -9,7 +10,7 @@ use crate::control::{ }; /// Trait for basic functionality from the PD spec. -pub trait Pd { +pub trait Pd: Named { /// Returns the port status fn get_port_status(&mut self, port: LocalPortId) -> impl Future>; diff --git a/type-c-interface/src/controller/power.rs b/type-c-interface/src/controller/power.rs index 238ce82ca..c485258d6 100644 --- a/type-c-interface/src/controller/power.rs +++ b/type-c-interface/src/controller/power.rs @@ -1,7 +1,8 @@ +use embedded_services::named::Named; use embedded_usb_pd::{LocalPortId, PdError}; /// System power state related controller functionality -pub trait SystemPowerStateStatus { +pub trait SystemPowerStateStatus: Named { /// Set the system power state on the given port. /// /// This notifies the PD controller of the current system power state, diff --git a/type-c-interface/src/controller/retimer.rs b/type-c-interface/src/controller/retimer.rs index 843a53b76..e676141db 100644 --- a/type-c-interface/src/controller/retimer.rs +++ b/type-c-interface/src/controller/retimer.rs @@ -1,9 +1,10 @@ +use embedded_services::named::Named; use embedded_usb_pd::{LocalPortId, PdError}; use crate::control::retimer::RetimerFwUpdateState; /// Retimer-related functionality -pub trait Retimer { +pub trait Retimer: Named { /// Returns the retimer fw update state fn get_rt_fw_update_status( &mut self, diff --git a/type-c-interface/src/port/event.rs b/type-c-interface/src/port/event.rs index 312680fdd..d46c76b1f 100644 --- a/type-c-interface/src/port/event.rs +++ b/type-c-interface/src/port/event.rs @@ -6,7 +6,8 @@ //! Consequently [`PortNotificationEventBitfield`] implements iterator traits to allow for processing these events as a stream. use bitfield::bitfield; -use crate::port::{AttnVdm, OtherVdm}; +use crate::control::vdm::AttnVdm; +use crate::control::vdm::OtherVdm; bitfield! { /// Raw bitfield of possible port status events diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index ffc8e8bb8..7920f6db1 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -1,11 +1,4 @@ -//! PD controller related code -use embassy_sync::channel::{DynamicReceiver, DynamicSender}; -use embedded_usb_pd::ucsi::lpm; -use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError, ado::Ado}; - -use embedded_services::ipc::deferred; -use embedded_services::{GlobalRawMutex, intrusive_list}; - +//! Type-C port related code pub mod electrical_disconnect; pub mod event; pub mod max_sink_voltage; @@ -13,219 +6,3 @@ pub mod pd; pub mod power; pub mod retimer; pub mod type_c; -use crate::control::dp::{DpConfig, DpStatus}; -use crate::control::pd::PdStateMachineConfig; -use crate::control::retimer::RetimerFwUpdateState; -use crate::control::tbt::TbtConfig; -use crate::control::type_c::TypeCStateMachineState; -use crate::control::usb::UsbControlConfig; -use crate::control::vdm::{AttnVdm, OtherVdm, SendVdm}; -use crate::controller::ControllerId; -use crate::service::event::PortEvent as ServicePortEvent; - -/// Port-specific command data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortCommandData { - /// 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, - /// 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, -} - -/// Port-specific response data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortResponseData { - /// Command completed with no error - Complete, - /// 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, - /// 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), -} - -/// Controller-specific response data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InternalResponseData { - /// Command complete - Complete, -} - -/// Response for controller-specific commands -pub type InternalResponse = Result; - -/// PD controller command response -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Response { - /// Controller response - Controller(InternalResponse), - /// Port response - Port(PortResponse), -} - -/// Per-port registration info -pub struct PortRegistration { - /// Global port ID of the port - pub id: GlobalPortId, - /// Event receiver for the type-C service - pub receiver: DynamicReceiver<'static, ServicePortEvent>, - /// Event sender for the type-C service - pub sender: DynamicSender<'static, ServicePortEvent>, -} - -/// PD controller -pub struct Device<'a> { - node: intrusive_list::Node, - id: ControllerId, - pub ports: &'a [PortRegistration], - 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 [PortRegistration]) -> 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() - } - - /// Convert 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)?.id) - } - - /// Convert a global port ID to a local port ID - pub fn lookup_local_port(&self, port: GlobalPortId) -> Result { - self.ports - .iter() - .position(|descriptor| descriptor.id == 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> { - self.command.receive().await - } - - /// Number of ports on this controller - pub fn num_ports(&self) -> usize { - self.num_ports - } -} diff --git a/type-c-interface/src/port/pd.rs b/type-c-interface/src/port/pd.rs index 2f00ca68d..547f948b5 100644 --- a/type-c-interface/src/port/pd.rs +++ b/type-c-interface/src/port/pd.rs @@ -1,3 +1,4 @@ +use embedded_services::named::Named; use embedded_usb_pd::{PdError, ado::Ado}; use crate::control::{ @@ -9,7 +10,7 @@ use crate::control::{ }; /// Trait for basic functionality from the PD spec. -pub trait Pd { +pub trait Pd: Named { /// Returns the port status fn get_port_status(&mut self) -> impl Future>; diff --git a/type-c-interface/src/port/power.rs b/type-c-interface/src/port/power.rs index 4701328cc..afdb3b103 100644 --- a/type-c-interface/src/port/power.rs +++ b/type-c-interface/src/port/power.rs @@ -1,7 +1,8 @@ +use embedded_services::named::Named; use embedded_usb_pd::PdError; /// System power state related controller functionality -pub trait SystemPowerStateStatus { +pub trait SystemPowerStateStatus: Named { /// Set the system power state on this port. /// /// This notifies the PD controller of the current system power state, diff --git a/type-c-interface/src/port/retimer.rs b/type-c-interface/src/port/retimer.rs index d64962c2c..c79831c92 100644 --- a/type-c-interface/src/port/retimer.rs +++ b/type-c-interface/src/port/retimer.rs @@ -1,9 +1,10 @@ +use embedded_services::named::Named; use embedded_usb_pd::PdError; use crate::control::retimer::RetimerFwUpdateState; /// Retimer-related functionality -pub trait Retimer { +pub trait Retimer: Named { /// Returns the retimer fw update state fn get_rt_fw_update_status(&mut self) -> impl Future>; /// Set retimer fw update state diff --git a/type-c-interface/src/service/context.rs b/type-c-interface/src/service/context.rs deleted file mode 100644 index 4d2217833..000000000 --- a/type-c-interface/src/service/context.rs +++ /dev/null @@ -1,433 +0,0 @@ -use embassy_time::{Duration, with_timeout}; -use embedded_usb_pd::ucsi::lpm; -use embedded_usb_pd::{GlobalPortId, PdError}; - -use crate::control::dp::{DpConfig, DpStatus}; -use crate::control::pd::PdStateMachineConfig; -use crate::control::retimer::RetimerFwUpdateState; -use crate::control::tbt::TbtConfig; -use crate::control::type_c::TypeCStateMachineState; -use crate::control::usb::UsbControlConfig; -use crate::control::vdm::{AttnVdm, OtherVdm, SendVdm}; -use crate::controller::ControllerId; -use crate::port::{ - Command, Device, InternalCommandData, InternalResponseData, PortCommand, PortCommandData, PortResponseData, - Response, -}; -use crate::service; -use crate::service::event::{Event, PortEvent}; -use embedded_services::{IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list}; - -/// 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 - } -} - -/// Type-C service context -/// -/// This struct is going to be merged into the service implementation and removed from here. -pub struct Context { - /// Event broadcaster - broadcaster: broadcaster::Immediate, - /// Controller list - pub controllers: intrusive_list::IntrusiveList, -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} - -impl Context { - /// Create new Context - pub const fn new() -> Self { - Self { - broadcaster: broadcaster::Immediate::new(), - controllers: intrusive_list::IntrusiveList::new(), - } - } - - /// 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 { - 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 { - 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 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 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), - } - } - - /// 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(()), - } - } - - /// 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 fn get_num_ports(&self) -> usize { - self.controllers - .iter_only::() - .fold(0, |acc, controller| acc + controller.num_ports()) - } - - pub async fn send_port_event(&self, event: PortEvent) -> Result<(), PdError> { - let node = self.find_node_by_port(event.port)?; - - node.data::() - .ok_or(PdError::InvalidController)? - .ports - .iter() - .find(|descriptor| descriptor.id == event.port) - .ok_or(PdError::InvalidPort)? - .sender - .send(event) - .await; - Ok(()) - } -} diff --git a/type-c-interface/src/service/event.rs b/type-c-interface/src/service/event.rs index e8ca1bf1c..d0059a2b3 100644 --- a/type-c-interface/src/service/event.rs +++ b/type-c-interface/src/service/event.rs @@ -1,10 +1,14 @@ //! Comms service message definitions +use embedded_services::sync::Lockable; use embedded_usb_pd::{GlobalPortId, ado::Ado}; use crate::{ control::{dp::DpStatus, pd::PortStatus}, - port::event::{PortStatusEventBitfield, VdmData}, + port::{ + event::{PortStatusEventBitfield, VdmData}, + pd::Pd, + }, }; /// Struct containing data for a [`PortEventData::StatusChanged`] event @@ -40,25 +44,23 @@ pub enum PortEventData { /// Struct containing a complete port event #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortEvent { - pub port: GlobalPortId, +pub struct PortEvent<'port, Port: Lockable> { + pub port: &'port Port, pub event: PortEventData, } /// Message generated when a debug accessory is connected or disconnected -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DebugAccessory { - /// Port - pub port: GlobalPortId, +pub struct DebugAccessoryData { /// Connected pub connected: bool, } /// UCSI connector change message -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct UsciChangeIndicator { +pub struct UsciChangeIndicatorData { /// Port pub port: GlobalPortId, /// Notify OPM @@ -66,11 +68,26 @@ pub struct UsciChangeIndicator { } /// Top-level comms message -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event { - /// Debug accessory message - DebugAccessory(DebugAccessory), - /// UCSI CCI message - UcsiCci(UsciChangeIndicator), +pub enum EventData { + DebugAccessory(DebugAccessoryData), + UsciChangeIndicator(UsciChangeIndicatorData), +} + +/// Top-level comms message +#[derive(Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Event<'port, Port: Lockable> { + pub port: &'port Port, + pub event: EventData, +} + +impl<'port, Port: Lockable> Clone for Event<'port, Port> { + fn clone(&self) -> Self { + Self { + port: self.port, + event: self.event, + } + } } diff --git a/type-c-interface/src/service/mod.rs b/type-c-interface/src/service/mod.rs index 23749388f..53f112654 100644 --- a/type-c-interface/src/service/mod.rs +++ b/type-c-interface/src/service/mod.rs @@ -1,2 +1 @@ -pub mod context; pub mod event; diff --git a/type-c-interface/src/ucsi.rs b/type-c-interface/src/ucsi.rs index 2ed23175b..536d5d692 100644 --- a/type-c-interface/src/ucsi.rs +++ b/type-c-interface/src/ucsi.rs @@ -1,7 +1,8 @@ +use embedded_services::named::Named; use embedded_usb_pd::{PdError, ucsi::lpm}; /// UCSI LPM command execution trait -pub trait Lpm { +pub trait Lpm: Named { /// Execute the given LPM command fn execute_lpm_command( &mut self, diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 0582d28aa..fb8321ffb 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -35,6 +35,12 @@ embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } embassy-sync = { workspace = true, features = ["std"] } embassy-futures.workspace = true tokio = { workspace = true, features = ["rt", "macros", "time"] } +log.workspace = true +env_logger = "0.11.8" +type-c-interface-mocks.workspace = true +power-policy-service.workspace = true +paste.workspace = true +critical-section = { workspace = true, features = ["std"] } [features] default = [] @@ -48,6 +54,7 @@ defmt = [ "power-policy-interface/defmt", "type-c-interface/defmt", "fw-update-interface/defmt", + "power-policy-service/defmt", ] log = [ "dep:log", @@ -58,4 +65,5 @@ log = [ "power-policy-interface/log", "type-c-interface/log", "fw-update-interface/log", + "power-policy-service/log", ] diff --git a/type-c-service/src/bridge/event_receiver.rs b/type-c-service/src/bridge/event_receiver.rs deleted file mode 100644 index da1b4be1c..000000000 --- a/type-c-service/src/bridge/event_receiver.rs +++ /dev/null @@ -1,32 +0,0 @@ -use embedded_services::{GlobalRawMutex, ipc::deferred}; -use type_c_interface::port; - -pub type ControllerCommand<'a> = deferred::Request<'a, GlobalRawMutex, port::Command, port::Response>; - -/// Controller command output data -pub struct OutputControllerCommand<'a> { - /// Controller request - pub request: ControllerCommand<'a>, - /// Response - pub response: port::Response, -} - -pub struct EventReceiver { - /// PD controller - pub pd_controller: &'static port::Device<'static>, -} - -impl EventReceiver { - /// Create a new instance - pub fn new(pd_controller: &'static port::Device<'static>) -> Self { - Self { pd_controller } - } - - pub fn wait_next(&mut self) -> impl Future> { - self.pd_controller.receive() - } - - pub fn finalize(&mut self, output: OutputControllerCommand<'static>) { - output.request.respond(output.response); - } -} diff --git a/type-c-service/src/bridge/mod.rs b/type-c-service/src/bridge/mod.rs deleted file mode 100644 index a4e8bf7f8..000000000 --- a/type-c-service/src/bridge/mod.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Temporary bridge between a controller and the type-C service - -use embedded_services::{debug, sync::Lockable}; -use embedded_usb_pd::{PdError, ucsi::lpm}; -use type_c_interface::controller::{ - Controller, - pd::{Pd, StateMachine as PdStateMachine}, - retimer::Retimer, - type_c::StateMachine as TypeCStateMachine, -}; -use type_c_interface::port::{self, InternalResponseData, Response}; -use type_c_interface::ucsi::Lpm as UcsiLpm; - -use crate::bridge::event_receiver::{ControllerCommand, OutputControllerCommand}; -pub mod event_receiver; - -pub struct Bridge<'device, C: Lockable> -where - C::Inner: Controller + Pd + PdStateMachine + Retimer + TypeCStateMachine + UcsiLpm, -{ - controller: &'device C, - registration: &'static port::Device<'static>, -} - -impl<'device, C: Lockable> Bridge<'device, C> -where - C::Inner: Controller + Pd + PdStateMachine + Retimer + TypeCStateMachine + UcsiLpm, -{ - pub fn new(controller: &'device C, registration: &'static port::Device<'static>) -> Self { - Self { - controller, - registration, - } - } - - /// Handle a port command - pub async fn process_port_command(&mut self, command: &port::PortCommand) -> Response { - let local_port = if let Ok(port) = self.registration.lookup_local_port(command.port) { - port - } else { - debug!("Invalid port: {:?}", command.port); - return port::Response::Port(Err(PdError::InvalidPort)); - }; - - let mut controller = self.controller.lock().await; - port::Response::Port(match command.data { - port::PortCommandData::RetimerFwUpdateGetState => controller - .get_rt_fw_update_status(local_port) - .await - .map(port::PortResponseData::RtFwUpdateStatus), - port::PortCommandData::RetimerFwUpdateSetState => controller - .set_rt_fw_update_state(local_port) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::RetimerFwUpdateClearState => controller - .clear_rt_fw_update_state(local_port) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::SetRetimerCompliance => controller - .set_rt_compliance(local_port) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::ReconfigureRetimer => controller - .reconfigure_retimer(local_port) - .await - .map(|_| port::PortResponseData::Complete), - // This command isn't sent by the type-C service, disable it for the transition - port::PortCommandData::SetMaxSinkVoltage(_) => Ok(port::PortResponseData::Complete), - port::PortCommandData::SetUnconstrainedPower(unconstrained) => controller - .set_unconstrained_power(local_port, unconstrained) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::ClearDeadBatteryFlag => controller - .clear_dead_battery_flag(local_port) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::GetOtherVdm => controller.get_other_vdm(local_port).await.map(|vdm| { - debug!("Port{}: Other VDM: {:?}", local_port.0, vdm); - port::PortResponseData::OtherVdm(vdm) - }), - port::PortCommandData::GetAttnVdm => controller.get_attn_vdm(local_port).await.map(|vdm| { - debug!("Port{}: Attention VDM: {:?}", local_port.0, vdm); - port::PortResponseData::AttnVdm(vdm) - }), - port::PortCommandData::SendVdm(tx_vdm) => controller - .send_vdm(local_port, tx_vdm) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::SetUsbControl(config) => controller - .set_usb_control(local_port, config) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::GetDpStatus => controller.get_dp_status(local_port).await.map(|status| { - debug!("Port{}: DP Status: {:?}", local_port.0, status); - port::PortResponseData::DpStatus(status) - }), - port::PortCommandData::SetDpConfig(config) => controller - .set_dp_config(local_port, config) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::ExecuteDrst => controller - .execute_drst(local_port) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::SetTbtConfig(config) => controller - .set_tbt_config(local_port, config) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::SetPdStateMachineConfig(config) => controller - .set_pd_state_machine_config(local_port, config) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::SetTypeCStateMachineConfig(state) => controller - .set_type_c_state_machine_config(local_port, state) - .await - .map(|_| port::PortResponseData::Complete), - port::PortCommandData::ExecuteUcsiCommand(command_data) => Ok(port::PortResponseData::UcsiResponse( - controller - .execute_lpm_command(lpm::Command::new(local_port, command_data)) - .await, - )), - }) - } - - pub async fn process_controller_command(&mut self, command: &port::InternalCommandData) -> Response { - let mut controller = self.controller.lock().await; - match command { - port::InternalCommandData::SyncState => port::Response::Controller(Ok(InternalResponseData::Complete)), - port::InternalCommandData::Reset => { - let result = controller.reset_controller().await; - port::Response::Controller(result.map(|_| InternalResponseData::Complete)) - } - } - } - - /// Handle a PD controller command - pub async fn process_event(&mut self, command: ControllerCommand<'static>) -> OutputControllerCommand<'static> { - let response = match command.command { - port::Command::Port(command) => self.process_port_command(&command).await, - port::Command::Controller(command) => self.process_controller_command(&command).await, - }; - OutputControllerCommand { - request: command, - response, - } - } -} diff --git a/type-c-service/src/controller/electrical_disconnect.rs b/type-c-service/src/controller/electrical_disconnect.rs index d4d59df6b..8ef5499b8 100644 --- a/type-c-service/src/controller/electrical_disconnect.rs +++ b/type-c-service/src/controller/electrical_disconnect.rs @@ -12,10 +12,11 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, > type_c_interface::port::electrical_disconnect::ElectricalDisconnect - for Port<'device, C, Shared, PowerSender, LoopbackSender> + for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn execute_electrical_disconnect(&mut self, reconnect_time_s: Option) -> Result<(), PdError> { self.controller diff --git a/type-c-service/src/controller/macros.rs b/type-c-service/src/controller/macros.rs index 8d27f94e4..0fe30d216 100644 --- a/type-c-service/src/controller/macros.rs +++ b/type-c-service/src/controller/macros.rs @@ -7,6 +7,7 @@ use type_c_interface::port::event::PortEventBitfield; use crate::controller::{event_receiver::EventReceiver, state}; pub const DEFAULT_POWER_POLICY_CHANNEL_SIZE: usize = 2; +pub const DEFAULT_TYPE_C_CHANNEL_SIZE: usize = 2; pub const DEFAULT_LOOPBACK_CHANNEL_SIZE: usize = 1; pub const DEFAULT_INTERRUPT_CHANNEL_SIZE: usize = 4; @@ -15,6 +16,7 @@ pub struct PortComponents< 'a, Port, SharedState: Lockable, + TypeCReceiver: Receiver, PowerPolicyReceveiver: Receiver, LoopbackReceiver: Receiver, InterruptReceiver: Receiver, @@ -22,6 +24,8 @@ pub struct PortComponents< > { /// Port instance pub port: &'a Port, + /// Type-C service event receiver + pub type_c_receiver: TypeCReceiver, /// Power policy event receiver pub power_policy_receiver: PowerPolicyReceveiver, /// Port event receiver @@ -46,6 +50,11 @@ macro_rules! define_controller_port_static_cell_channel { pub type InnerPowerPolicyReceiverType = ::embassy_sync::channel::DynamicReceiver<'static, ::power_policy_interface::psu::event::EventData>; + /// Type alias for the type-c service event sender + pub type InnerTypeCSenderType = ::embassy_sync::channel::DynamicSender<'static, ::type_c_interface::service::event::PortEventData>; + /// Type alias for the type-c service event receiver + pub type InnerTypeCReceiverType = ::embassy_sync::channel::DynamicReceiver<'static, ::type_c_interface::service::event::PortEventData>; + /// Type alias for the loopback sender pub type InnerLoopbackSenderType = ::embassy_sync::channel::DynamicSender<'static, $crate::controller::event::Loopback>; @@ -72,6 +81,8 @@ macro_rules! define_controller_port_static_cell_channel { $controller, // Shared state type InnerSharedStateType, + // Type-C service event sender type + InnerTypeCSenderType, // Power policy event sender type InnerPowerPolicySenderType, // Loopback event sender type @@ -79,6 +90,14 @@ macro_rules! define_controller_port_static_cell_channel { >, >; + /// Channel to send events to the type-c service + static TYPE_C_CHANNEL: ::static_cell::StaticCell< + ::embassy_sync::channel::Channel< + $mutex, + ::type_c_interface::service::event::PortEventData, + { $crate::controller::macros::DEFAULT_TYPE_C_CHANNEL_SIZE }, + >, + > = ::static_cell::StaticCell::new(); /// Channel to send events to the power policy service static POWER_POLICY_CHANNEL: ::static_cell::StaticCell< ::embassy_sync::channel::Channel< @@ -113,14 +132,13 @@ macro_rules! define_controller_port_static_cell_channel { pub fn create( name: &'static str, port: ::embedded_usb_pd::LocalPortId, - global_port: ::embedded_usb_pd::GlobalPortId, config: $crate::controller::config::Config, controller: &'static $controller, - context: &'static type_c_interface::service::context::Context, ) -> $crate::controller::macros::PortComponents< 'static, InnerPortType, InnerSharedStateType, + InnerTypeCReceiverType, InnerPowerPolicyReceiverType, InnerLoopbackReceiverType, InnerInterruptReceiverType, @@ -134,6 +152,10 @@ macro_rules! define_controller_port_static_cell_channel { let power_policy_sender = power_policy_channel.dyn_sender(); let power_policy_receiver = power_policy_channel.dyn_receiver(); + let type_c_channel = TYPE_C_CHANNEL.init(::embassy_sync::channel::Channel::new()); + let type_c_sender = type_c_channel.dyn_sender(); + let type_c_receiver = type_c_channel.dyn_receiver(); + let loopback_channel = LOOPBACK_CHANNEL.init(::embassy_sync::channel::Channel::new()); let loopback_sender = loopback_channel.dyn_sender(); let loopback_receiver = loopback_channel.dyn_receiver(); @@ -146,12 +168,11 @@ macro_rules! define_controller_port_static_cell_channel { name, config, port, - global_port, controller, shared_state, + type_c_sender, power_policy_sender, loopback_sender, - context, ))); let event_receiver = $crate::controller::event_receiver::EventReceiver::new( shared_state, @@ -160,6 +181,7 @@ macro_rules! define_controller_port_static_cell_channel { ); $crate::controller::macros::PortComponents { port, + type_c_receiver, power_policy_receiver, event_receiver, interrupt_sender, diff --git a/type-c-service/src/controller/max_sink_voltage.rs b/type-c-service/src/controller/max_sink_voltage.rs index 8cee77252..78bd4059b 100644 --- a/type-c-service/src/controller/max_sink_voltage.rs +++ b/type-c-service/src/controller/max_sink_voltage.rs @@ -10,9 +10,11 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::port::max_sink_voltage::MaxSinkVoltage for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::port::max_sink_voltage::MaxSinkVoltage + for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn set_max_sink_voltage(&mut self, voltage_mv: Option) -> Result<(), PdError> { self.controller diff --git a/type-c-service/src/controller/mod.rs b/type-c-service/src/controller/mod.rs index bc739d278..e81cff9bf 100644 --- a/type-c-service/src/controller/mod.rs +++ b/type-c-service/src/controller/mod.rs @@ -1,14 +1,12 @@ //! Struct that manages per-port state, interfacing with a controller object that exposes multiple ports. use embedded_services::{debug, error, event::Sender, info, named::Named, sync::Lockable}; -use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError}; +use embedded_usb_pd::{LocalPortId, PdError}; use power_policy_interface::psu::PsuState; use type_c_interface::control::pd::PortStatus; use type_c_interface::controller::pd::Pd; use type_c_interface::port::event::PortEventBitfield; use type_c_interface::port::{event::PortEvent as InterfacePortEvent, event::PortStatusEventBitfield}; -use type_c_interface::service::event::{ - PortEvent as ServicePortEvent, PortEventData as ServicePortEventData, StatusChangedData, -}; +use type_c_interface::service::event::{PortEventData as ServicePortEventData, StatusChangedData}; use crate::controller::event::{Event, Loopback}; use crate::controller::state::SharedState; @@ -30,13 +28,12 @@ pub struct Port< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, > { /// Local port port: LocalPortId, - /// Global port - global_port: GlobalPortId, /// Controller controller: &'device C, /// Per-port PSU state @@ -45,14 +42,14 @@ pub struct Port< name: &'static str, /// Cached port status status: PortStatus, + /// Sender for type-c service events + type_c_sender: TypeCSender, /// Sender for power policy events power_policy_sender: PowerSender, /// Configuration config: config::Config, /// Shared state shared_state: &'device Shared, - /// Type-C service context - context: &'device type_c_interface::service::context::Context, /// Loopback sender loopback_sender: LoopbackSender, } @@ -61,36 +58,35 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> Port<'device, C, Shared, PowerSender, LoopbackSender> +> Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { /// Create new Port instance - // Argument count will be reduced as the last bit of refactoring is done + // TODO: refactor arguments into a registration struct #[allow(clippy::too_many_arguments)] pub fn new( name: &'static str, config: config::Config, port: LocalPortId, - global_port: GlobalPortId, controller: &'device C, shared_state: &'device Shared, + type_c_sender: TypeCSender, power_policy_sender: PowerSender, loopback_sender: LoopbackSender, - context: &'device type_c_interface::service::context::Context, ) -> Self { Self { name, controller, port, - global_port, status: PortStatus::default(), psu_state: power_policy_interface::psu::State::default(), power_policy_sender, config, shared_state, - context, loopback_sender, + type_c_sender, } } @@ -153,12 +149,7 @@ impl< current_status: new_status, }); self.status = new_status; - self.context - .send_port_event(ServicePortEvent { - port: self.global_port, - event, - }) - .await?; + self.type_c_sender.send(event).await; Ok(event) } @@ -227,9 +218,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> Named for Port<'device, C, Shared, PowerSender, LoopbackSender> +> Named for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { fn name(&self) -> &'static str { self.name diff --git a/type-c-service/src/controller/pd.rs b/type-c-service/src/controller/pd.rs index 0cfe2bc6d..2091c9241 100644 --- a/type-c-service/src/controller/pd.rs +++ b/type-c-service/src/controller/pd.rs @@ -11,7 +11,7 @@ use type_c_interface::control::{ }; use type_c_interface::controller::pd::StateMachine; use type_c_interface::port::event::{VdmData, VdmNotification}; -use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventData as ServicePortEventData}; +use type_c_interface::service::event::PortEventData as ServicePortEventData; use super::*; use crate::controller::state::SharedState; @@ -20,9 +20,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> Port<'device, C, Shared, PowerSender, LoopbackSender> +> Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { /// Process a VDM event by retrieving the relevant VDM data from the `controller` for the appropriate `port`. pub(super) async fn process_vdm_event(&mut self, event: VdmNotification) -> Result { @@ -38,13 +39,7 @@ impl< }; let event = ServicePortEventData::Vdm(vdm_data); - let _ = self - .context - .send_port_event(ServicePortEvent { - port: self.global_port, - event: ServicePortEventData::Vdm(vdm_data), - }) - .await; + self.type_c_sender.send(event).await; Ok(event) } @@ -53,13 +48,7 @@ impl< debug!("({}): Processing DP status update event", self.name); let status = self.controller.lock().await.get_dp_status(self.port).await?; let event = ServicePortEventData::DpStatusUpdate(status); - let _ = self - .context - .send_port_event(ServicePortEvent { - port: self.global_port, - event, - }) - .await; + self.type_c_sender.send(event).await; Ok(event) } @@ -68,13 +57,7 @@ impl< debug!("({}): PD alert: {:#?}", self.name, ado); if let Some(ado) = ado { let event = ServicePortEventData::Alert(ado); - let _ = self - .context - .send_port_event(ServicePortEvent { - port: self.global_port, - event, - }) - .await; + self.type_c_sender.send(event).await; Ok(Some(event)) } else { // For some reason we didn't read an alert, nothing to do @@ -87,9 +70,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::port::pd::Pd for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::port::pd::Pd for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn get_port_status(&mut self) -> Result { self.controller.lock().await.get_port_status(self.port).await @@ -152,9 +136,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::port::pd::StateMachine for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::port::pd::StateMachine for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn set_pd_state_machine_config(&mut self, config: PdStateMachineConfig) -> Result<(), PdError> { self.controller diff --git a/type-c-service/src/controller/power.rs b/type-c-service/src/controller/power.rs index 9e477e410..8d48d3817 100644 --- a/type-c-service/src/controller/power.rs +++ b/type-c-service/src/controller/power.rs @@ -19,9 +19,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> Port<'device, C, Shared, PowerSender, LoopbackSender> +> Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract(&mut self, new_status: &PortStatus) -> Result<(), PdError> { @@ -114,9 +115,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> Psu for Port<'device, C, Shared, PowerSender, LoopbackSender> +> Psu for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn disconnect(&mut self) -> Result<(), PsuError> { self.controller @@ -169,9 +171,11 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::port::power::SystemPowerStateStatus for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::port::power::SystemPowerStateStatus + for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn set_system_power_state_status( &mut self, diff --git a/type-c-service/src/controller/retimer.rs b/type-c-service/src/controller/retimer.rs index cdb9a4a0a..f7710169b 100644 --- a/type-c-service/src/controller/retimer.rs +++ b/type-c-service/src/controller/retimer.rs @@ -11,9 +11,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::port::retimer::Retimer for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::port::retimer::Retimer for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn get_rt_fw_update_status(&mut self) -> Result { self.controller.lock().await.get_rt_fw_update_status(self.port).await diff --git a/type-c-service/src/controller/type_c.rs b/type-c-service/src/controller/type_c.rs index 663212e65..30bd84c89 100644 --- a/type-c-service/src/controller/type_c.rs +++ b/type-c-service/src/controller/type_c.rs @@ -11,9 +11,10 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::port::type_c::StateMachine for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::port::type_c::StateMachine for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { async fn set_type_c_state_machine_config(&mut self, state: TypeCStateMachineState) -> Result<(), PdError> { self.controller diff --git a/type-c-service/src/controller/ucsi.rs b/type-c-service/src/controller/ucsi.rs index b899720be..3dfdbf6dd 100644 --- a/type-c-service/src/controller/ucsi.rs +++ b/type-c-service/src/controller/ucsi.rs @@ -1,6 +1,6 @@ //! UCSI LPM port trait implementation use embedded_services::{event::Sender, sync::Lockable}; -use embedded_usb_pd::PdError; +use embedded_usb_pd::{PdError, ucsi::lpm}; use type_c_interface::ucsi::Lpm as UcsiLpm; use super::*; @@ -10,14 +10,12 @@ impl< 'device, C: Lockable, Shared: Lockable, + TypeCSender: Sender, PowerSender: Sender, LoopbackSender: Sender, -> type_c_interface::ucsi::Lpm for Port<'device, C, Shared, PowerSender, LoopbackSender> +> type_c_interface::ucsi::Lpm for Port<'device, C, Shared, TypeCSender, PowerSender, LoopbackSender> { - async fn execute_lpm_command( - &mut self, - command: embedded_usb_pd::ucsi::lpm::LocalCommand, - ) -> Result, PdError> { + async fn execute_lpm_command(&mut self, command: lpm::LocalCommand) -> Result, PdError> { self.controller.lock().await.execute_lpm_command(command).await } } diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index 9e42b2000..2ee1d6488 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -pub mod bridge; pub mod controller; pub mod driver; pub mod service; diff --git a/type-c-service/src/service/event_receiver.rs b/type-c-service/src/service/event_receiver.rs new file mode 100644 index 000000000..b048fc9a6 --- /dev/null +++ b/type-c-service/src/service/event_receiver.rs @@ -0,0 +1,100 @@ +use core::pin::pin; + +use crate::service::Event; +use embassy_futures::select::{Either, select, select_slice}; +use embedded_services::{event::Receiver, sync::Lockable}; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; +use type_c_interface::{port::pd::Pd, service::event::PortEvent}; + +struct PowerPolicySubscriber> { + receiver: PowerReceiver, +} + +impl> PowerPolicySubscriber { + /// Wait for a power policy event + async fn wait_next(&mut self) -> PowerPolicyEventData { + self.receiver.wait_next().await + } +} + +pub struct ArrayPortReceivers< + 'port, + const N: usize, + Port: Lockable, + PortReceiver: Receiver, +> { + ports: [&'port Port; N], + port_receivers: [PortReceiver; N], +} + +impl< + 'port, + const N: usize, + Port: Lockable, + PortReceiver: Receiver, +> ArrayPortReceivers<'port, N, Port, PortReceiver> +{ + /// Get the next pending PSU event + pub async fn wait_next(&mut self) -> Event<'port, Port> { + let ((event, port), _) = { + let mut futures = heapless::Vec::<_, N>::new(); + for (receiver, psu) in self.port_receivers.iter_mut().zip(self.ports.iter()) { + // Push will never fail since the number of receivers is the same as the capacity of the vector + let _ = futures.push(async move { (receiver.wait_next().await, psu) }); + } + select_slice(pin!(&mut futures)).await + }; + + Event::PortEvent(PortEvent { port: *port, event }) + } +} + +/// Struct used to contain port event receivers and manage mapping from a receiver to its corresponding device. +pub struct ArrayEventReceiver< + 'a, + const N: usize, + Port: Lockable, + PortReceiver: Receiver, + PowerReceiver: Receiver, +> { + /// Power policy event subscriber + power_policy_event_subscriber: PowerPolicySubscriber, + /// Port event receivers and corresponding ports + port_receivers: ArrayPortReceivers<'a, N, Port, PortReceiver>, +} + +impl< + 'port, + const N: usize, + Port: Lockable, + PortReceiver: Receiver, + PowerReceiver: Receiver, +> ArrayEventReceiver<'port, N, Port, PortReceiver, PowerReceiver> +{ + /// Create a new instance + pub fn new( + ports: [&'port Port; N], + port_receivers: [PortReceiver; N], + power_policy_event_receiver: PowerReceiver, + ) -> Self { + Self { + port_receivers: ArrayPortReceivers { ports, port_receivers }, + power_policy_event_subscriber: PowerPolicySubscriber { + receiver: power_policy_event_receiver, + }, + } + } + + /// Wait for the next event, whether it's a port event or a power policy event + pub async fn wait_next(&mut self) -> Event<'port, Port> { + match select( + self.port_receivers.wait_next(), + self.power_policy_event_subscriber.wait_next(), + ) + .await + { + Either::First(event) => event, + Either::Second(event) => Event::PowerPolicy(event), + } + } +} diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index a0ff5ec8b..32def70cf 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -1,156 +1,152 @@ -use core::cell::RefCell; -use core::future::pending; -use core::pin::pin; +use core::marker::PhantomData; +use core::ptr; -use embassy_futures::select::select_slice; -use embassy_futures::select::{Either, select}; -use embedded_services::{debug, error, event::Receiver, info, trace}; +use embedded_services::event::Sender as _; +use embedded_services::named::Named as _; +use embedded_services::sync::Lockable; +use embedded_services::{debug, error, info, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; use power_policy_interface::service::event::EventData as PowerPolicyEventData; use type_c_interface::control::pd::PortStatus; -use type_c_interface::service::event::{PortEvent, PortEventData}; +use type_c_interface::port::pd::Pd; +use type_c_interface::service::event::{DebugAccessoryData, EventData, PortEvent, PortEventData}; -use type_c_interface::port::Device; use type_c_interface::port::event::PortStatusEventBitfield; -use type_c_interface::service::event; +use type_c_interface::service::event::Event as ServiceEvent; + +use crate::service::registration::Registration; pub mod config; +pub mod event_receiver; mod power; +pub mod registration; mod ucsi; -pub mod vdm; - -const MAX_SUPPORTED_PORTS: usize = 4; - -/// Type-C service state -#[derive(Default)] -struct State { - /// Current port status - port_status: [PortStatus; MAX_SUPPORTED_PORTS], - /// UCSI state - ucsi: ucsi::State, -} /// Type-C service /// /// Constructing a Service is the first step in using the Type-C service. /// Arguments should be an initialized context -pub struct Service<'a> { - /// Type-C context - pub(crate) context: &'a type_c_interface::service::context::Context, - /// Current state - state: State, +pub struct Service<'port, Reg: Registration<'port>> { + /// UCSI state + ucsi: ucsi::State, /// Config config: config::Config, -} - -/// Power policy events -// 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), - /// Consumer disconnected - ConsumerDisconnected, - /// Consumer connected - ConsumerConnected, + /// Service registration + registration: Reg, + _phantom: PhantomData<&'port ()>, } /// Type-C service events -#[derive(Copy, Clone)] -pub enum Event { +#[derive(Clone)] +pub enum Event<'port, Port: Lockable> { /// Port event - PortEvent(PortEvent), + PortEvent(PortEvent<'port, Port>), /// Power policy event - PowerPolicy(PowerPolicyEvent), + PowerPolicy(PowerPolicyEventData), } -impl<'a> Service<'a> { +impl<'port, Reg: Registration<'port>> Service<'port, Reg> { /// Create a new service the given configuration - pub fn create(config: config::Config, context: &'a type_c_interface::service::context::Context) -> Self { + pub fn new(config: config::Config, registration: Reg) -> Self { Self { - context, - state: State::default(), + ucsi: ucsi::State::default(), config, + registration, + _phantom: PhantomData, } } - /// Get the cached port status - pub fn get_cached_port_status(&self, port_id: GlobalPortId) -> Result { - Ok(*self - .state - .port_status + fn get_port_index(&self, port: &'port Reg::Port) -> Result { + self.registration + .ports() + .iter() + .position(|p| ptr::eq(*p, port)) + .ok_or(Error::InvalidPort) + } + + /// Look up the port for a given global port ID + fn lookup_port(&self, port_id: GlobalPortId) -> Result<&Reg::Port, Error> { + self.registration + .ports() .get(port_id.0 as usize) - .ok_or(Error::InvalidPort)?) + .ok_or(Error::InvalidPort) + .copied() } - /// Set the cached port status - fn set_cached_port_status(&mut self, port_id: GlobalPortId, status: PortStatus) -> Result<(), Error> { - *self - .state - .port_status - .get_mut(port_id.0 as usize) - .ok_or(Error::InvalidPort)? = status; - Ok(()) + /// Send an event to all registered listeners + async fn broadcast_event(&mut self, event: ServiceEvent<'port, Reg::Port>) { + for sender in self.registration.event_senders() { + sender.send(event.clone()).await; + } } /// Process events for a specific port async fn process_port_status_event( &mut self, - port_id: GlobalPortId, + port: &'port Reg::Port, event: PortStatusEventBitfield, - status: PortStatus, + new_status: PortStatus, + old_status: PortStatus, ) -> Result<(), Error> { - let old_status = self.get_cached_port_status(port_id)?; + let port_name = { port.lock().await.name() }; - debug!("Port{}: Event: {:#?}", port_id.0, event); - debug!("Port{} Previous status: {:#?}", port_id.0, old_status); - debug!("Port{} Status: {:#?}", port_id.0, status); + debug!("({}): Event: {:#?}", port_name, event); + debug!("({}) Previous status: {:#?}", port_name, old_status); + debug!("({}) Status: {:#?}", port_name, new_status); - let connection_changed = status.is_connected() != old_status.is_connected(); - if connection_changed && (status.is_debug_accessory() || old_status.is_debug_accessory()) { + let connection_changed = new_status.is_connected() != old_status.is_connected(); + if connection_changed && (new_status.is_debug_accessory() || old_status.is_debug_accessory()) { // Notify that a debug connection has connected/disconnected - if status.is_connected() { - debug!("Port{}: Debug accessory connected", port_id.0); + if new_status.is_connected() { + debug!("({}): Debug accessory connected", port_name); } else { - debug!("Port{}: Debug accessory disconnected", port_id.0); + debug!("({}): Debug accessory disconnected", port_name); } - self.context - .broadcast_message(event::Event::DebugAccessory(event::DebugAccessory { - port: port_id, - connected: status.is_connected(), - })) - .await; + self.broadcast_event(ServiceEvent { + port, + event: EventData::DebugAccessory(DebugAccessoryData { + connected: new_status.is_connected(), + }), + }) + .await; } - self.set_cached_port_status(port_id, status)?; - self.handle_ucsi_port_event(port_id, event, &status).await; + self.handle_ucsi_port_event(port, GlobalPortId(self.get_port_index(port)? as u8), event, &new_status) + .await; Ok(()) } - async fn process_port_event(&mut self, event: &PortEvent) -> Result<(), Error> { + async fn process_port_event(&mut self, event: &PortEvent<'port, Reg::Port>) -> Result<(), Error> { match &event.event { PortEventData::StatusChanged(status_event) => { - self.process_port_status_event(event.port, status_event.status_event, status_event.current_status) - .await + self.process_port_status_event( + event.port, + status_event.status_event, + status_event.current_status, + status_event.previous_status, + ) + .await } unhandled => { // Currently just log notifications, but may want to do more in the future - debug!("Port{}: Received notification event: {:#?}", event.port.0, unhandled); + debug!( + "({}): Received notification event: {:#?}", + event.port.lock().await.name(), + unhandled + ); Ok(()) } } } /// Process the given event - pub async fn process_event(&mut self, event: Event) -> Result<(), Error> { + pub async fn process_event(&mut self, event: Event<'port, Reg::Port>) -> Result<(), Error> { match event { Event::PortEvent(event) => { - trace!("Port{}: Processing port event", event.port.0); + trace!("({}): Processing port event", event.port.lock().await.name()); self.process_port_event(&event).await } Event::PowerPolicy(event) => { @@ -160,76 +156,3 @@ impl<'a> Service<'a> { } } } - -/// Event receiver for the Type-C service -pub struct EventReceiver<'a, PowerReceiver: Receiver> { - /// Type-C context - pub(crate) context: &'a type_c_interface::service::context::Context, - /// Power policy event subscriber - /// - /// Used to allow partial borrows of Self for the call to select - power_policy_event_subscriber: RefCell, -} - -impl<'a, PowerReceiver: Receiver> EventReceiver<'a, PowerReceiver> { - /// Create a new event receiver - pub fn new( - context: &'a type_c_interface::service::context::Context, - power_policy_event_subscriber: PowerReceiver, - ) -> Self { - Self { - context, - power_policy_event_subscriber: RefCell::new(power_policy_event_subscriber), - } - } - - /// Wait for the next event - pub async fn wait_next(&mut self) -> Event { - match select(self.wait_port_event(), self.wait_power_policy_event()).await { - Either::First(event) => event, - Either::Second(event) => event, - } - } - - /// Wait for a port event - async fn wait_port_event(&self) -> Event { - let (event, _) = { - let mut futures = heapless::Vec::<_, MAX_SUPPORTED_PORTS>::new(); - for device in self.context.controllers.iter_only::() { - for descriptor in device.ports.iter() { - let _ = futures.push(async move { descriptor.receiver.receive().await }); - } - } - select_slice(pin!(&mut futures)).await - }; - - Event::PortEvent(event) - } - - /// Wait for a power policy event - #[allow(clippy::await_holding_refcell_ref)] - async fn wait_power_policy_event(&self) -> Event { - let Ok(mut subscriber) = self.power_policy_event_subscriber.try_borrow_mut() else { - // This should never happen because this function is not public and is only called from wait_next, which takes &mut self - error!("Attempt to call `wait_power_policy_event` simultaneously"); - return pending().await; - }; - - loop { - match subscriber.wait_next().await { - power_policy_interface::service::event::EventData::Unconstrained(state) => { - return Event::PowerPolicy(PowerPolicyEvent::Unconstrained(state)); - } - power_policy_interface::service::event::EventData::ConsumerDisconnected => { - return Event::PowerPolicy(PowerPolicyEvent::ConsumerDisconnected); - } - power_policy_interface::service::event::EventData::ConsumerConnected(_) => { - return Event::PowerPolicy(PowerPolicyEvent::ConsumerConnected); - } - _ => { - // No other events currently implemented - } - } - } - } -} diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index 4ec9e3807..fcb763e6d 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -1,14 +1,17 @@ +use core::ptr; + +use embedded_services::sync::Lockable as _; use power_policy_interface::service as power_policy; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; +use type_c_interface::port::pd::Pd as _; use super::*; -impl Service<'_> { +impl<'a, Reg: Registration<'a>> Service<'a, Reg> { /// Set the unconstrained state for all ports pub(super) async fn set_unconstrained_all(&mut self, unconstrained: bool) -> Result<(), Error> { - for port_index in 0..self.context.get_num_ports() { - self.context - .set_unconstrained_power(GlobalPortId(port_index as u8), unconstrained) - .await?; + for port in self.registration.ports() { + port.lock().await.set_unconstrained_power(unconstrained).await?; } Ok(()) } @@ -28,25 +31,27 @@ impl Service<'_> { 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(); - let unconstrained_port = self - .state - .port_status - .iter() - .take(num_ports) - .position(|status| status.available_sink_contract.is_some() && status.unconstrained_power); + let mut unconstrained_port = None; + for port in self.registration.ports().iter() { + let status = port.lock().await.get_port_status().await?; + if status.available_sink_contract.is_some() && status.unconstrained_power { + unconstrained_port = Some(*port); + break; + } + } - if let Some(unconstrained_index) = unconstrained_port { + if let Some(unconstrained_port) = unconstrained_port { // One of our ports is the unconstrained consumer // If it switches to sourcing then the system will no longer be unconstrained // So set that port to constrained and unconstrain all others info!( - "Setting port{} to constrained, all others unconstrained", - unconstrained_index + "Setting port ({}) to constrained, all others unconstrained", + unconstrained_port.lock().await.name() ); - for port_index in 0..num_ports { - self.context - .set_unconstrained_power(GlobalPortId(port_index as u8), port_index != unconstrained_index) + for port in self.registration.ports().iter() { + port.lock() + .await + .set_unconstrained_power(ptr::eq(*port, unconstrained_port)) .await?; } } else { @@ -66,21 +71,26 @@ impl Service<'_> { } /// Process power policy events - pub(super) async fn process_power_policy_event(&mut self, message: &PowerPolicyEvent) -> Result<(), Error> { + pub(super) async fn process_power_policy_event(&mut self, message: &PowerPolicyEventData) -> Result<(), Error> { match message { - PowerPolicyEvent::Unconstrained(state) => self.process_unconstrained_state_change(state).await, - PowerPolicyEvent::ConsumerDisconnected => { - self.state.ucsi.psu_connected = false; + PowerPolicyEventData::Unconstrained(state) => self.process_unconstrained_state_change(state).await, + PowerPolicyEventData::ConsumerDisconnected => { + self.ucsi.psu_connected = false; // Notify OPM because this can affect battery charging capability status - self.pend_ucsi_connected_ports().await; + if self.ucsi.notifications_enabled.battery_charge_change() { + self.pend_ucsi_connected_ports().await; + } Ok(()) } - PowerPolicyEvent::ConsumerConnected => { - self.state.ucsi.psu_connected = true; + PowerPolicyEventData::ConsumerConnected(_) => { + self.ucsi.psu_connected = true; // Notify OPM because this can affect battery charging capability status - self.pend_ucsi_connected_ports().await; + if self.ucsi.notifications_enabled.battery_charge_change() { + self.pend_ucsi_connected_ports().await; + } Ok(()) } + _ => Ok(()), // Other events don't require any action from the service } } } diff --git a/type-c-service/src/service/registration.rs b/type-c-service/src/service/registration.rs new file mode 100644 index 000000000..815b151cb --- /dev/null +++ b/type-c-service/src/service/registration.rs @@ -0,0 +1,67 @@ +//! Code related to registration with the type-C service + +use embedded_services::{event::Sender, sync::Lockable}; +use embedded_usb_pd::{GlobalPortId, LocalPortId}; +use type_c_interface::port::pd::Pd; +use type_c_interface::service::event::Event as ServiceEvent; +use type_c_interface::ucsi::Lpm as UcsiLpm; + +/// Registration trait that abstracts over various registration details. +pub trait Registration<'port> { + type Port: Lockable + 'port; + type ServiceSender: Sender>; + + /// Returns a slice to access ports + fn ports(&self) -> &[&'port Self::Port]; + /// Returns a slice to access type-c event senders + fn event_senders(&mut self) -> &mut [Self::ServiceSender]; + /// Returns the ucsi local port ID for a given global port + fn ucsi_local_port_id(&self, global_port: GlobalPortId) -> Option; +} + +pub struct PortData { + /// local port ID + pub local_port: Option, +} + +/// A registration implementation based around arrays +pub struct ArrayRegistration< + 'port, + Port: Lockable + 'port, + const PORT_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> { + /// Array of registered ports + pub ports: [&'port Port; PORT_COUNT], + /// Array of local port data + pub port_data: [PortData; PORT_COUNT], + /// Array of service event senders + pub service_senders: [ServiceSender; SERVICE_SENDER_COUNT], +} + +impl< + 'port, + Port: Lockable + 'port, + const PORT_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> Registration<'port> for ArrayRegistration<'port, Port, PORT_COUNT, ServiceSender, SERVICE_SENDER_COUNT> +{ + type Port = Port; + type ServiceSender = ServiceSender; + + fn event_senders(&mut self) -> &mut [Self::ServiceSender] { + &mut self.service_senders + } + + fn ports(&self) -> &[&'port Self::Port] { + &self.ports + } + + fn ucsi_local_port_id(&self, global_port: GlobalPortId) -> Option { + self.port_data + .get(global_port.0 as usize) + .and_then(|data| data.local_port) + } +} diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index 9b75db63e..241e7fb9a 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -1,3 +1,4 @@ +use embedded_services::sync::Lockable; use embedded_services::warn; use embedded_usb_pd::ucsi::cci::{Cci, GlobalCci}; use embedded_usb_pd::ucsi::lpm::get_connector_status::{BatteryChargingCapabilityStatus, ConnectorStatusChange}; @@ -7,10 +8,13 @@ use embedded_usb_pd::ucsi::ppm::state_machine::{ }; use embedded_usb_pd::ucsi::{GlobalCommand, ResponseData, lpm, ppm}; use embedded_usb_pd::{PdError, PowerRole}; -use type_c_interface::service::event::{Event, UsciChangeIndicator}; +use type_c_interface::service::event::{Event, UsciChangeIndicatorData}; +use type_c_interface::ucsi::Lpm as _; use super::*; +const MAX_SUPPORTED_PORTS: usize = 4; + /// UCSI command response #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -27,40 +31,40 @@ pub struct UcsiResponse { #[derive(Default)] pub(super) struct State { /// PPM state machine - ppm_state_machine: StateMachine, + pub ppm_state_machine: StateMachine, /// Currently enabled notifications - notifications_enabled: NotificationEnable, + pub notifications_enabled: NotificationEnable, /// Queued pending port notifications - pending_ports: heapless::Deque, + pub pending_ports: heapless::Deque, /// Ports that have a valid battery charging status capability /// /// We provide a battery charging status only after the port has negotiated power. /// This prevents the port from temporarily reporting slow or no charging before the contract has finalized. - valid_battery_charging_capability: heapless::FnvIndexSet, + pub valid_battery_charging_capability: heapless::FnvIndexSet, /// PSU connected - pub(super) psu_connected: bool, + pub psu_connected: bool, } -impl Service<'_> { +impl<'port, Reg: Registration<'port>> Service<'port, Reg> { /// PPM reset implementation fn process_ppm_reset(&mut self) { debug!("Resetting PPM"); - self.state.ucsi.notifications_enabled = NotificationEnable::default(); - self.state.ucsi.pending_ports.clear(); - self.state.ucsi.valid_battery_charging_capability.clear(); + self.ucsi.notifications_enabled = NotificationEnable::default(); + self.ucsi.pending_ports.clear(); + self.ucsi.valid_battery_charging_capability.clear(); } /// Set notification enable implementation fn process_set_notification_enable(&mut self, enable: NotificationEnable) { debug!("Set Notification Enable: {:?}", enable); - self.state.ucsi.notifications_enabled = enable; + self.ucsi.notifications_enabled = enable; } /// PPM get capabilities implementation 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 = self.context.get_num_ports() as u8; + capabilities.num_connectors = self.registration.ports().len() as u8; ppm::ResponseData::GetCapability(capabilities) } @@ -82,7 +86,7 @@ impl Service<'_> { port_status: &PortStatus, ) -> Option { if port_status.power_role == PowerRole::Sink { - if self.state.ucsi.valid_battery_charging_capability.contains(&port_id) && !self.state.ucsi.psu_connected { + if self.ucsi.valid_battery_charging_capability.contains(&port_id) && !self.ucsi.psu_connected { // Only run this logic when no PSU is attached to prevent excessive notifications // when new type-C PSUs are attached let power_mw = port_status @@ -106,17 +110,24 @@ impl Service<'_> { command: &ucsi::lpm::GlobalCommand, ) -> Result, PdError> { debug!("Processing LPM command: {:?}", command); + let mut port = self.lookup_port(command.port())?.lock().await; + let local_port_id = self + .registration + .ucsi_local_port_id(command.port()) + .ok_or(PdError::InvalidPort)?; + let local_command = ucsi::lpm::LocalCommand::new(local_port_id, command.operation()); + match command.operation() { lpm::CommandData::GetConnectorCapability => { // Override the capabilities if present in the config if let Some(capabilities) = &self.config.ucsi_port_capabilities { Ok(Some(lpm::ResponseData::GetConnectorCapability(*capabilities))) } else { - self.context.execute_ucsi_command(*command).await + port.execute_lpm_command(local_command).await } } lpm::CommandData::GetConnectorStatus => { - let mut response = self.context.execute_ucsi_command(*command).await; + let mut response = port.execute_lpm_command(local_command).await; if let Ok(Some(lpm::ResponseData::GetConnectorStatus(lpm::get_connector_status::ResponseData { status_change: ref mut states_change, status: @@ -127,22 +138,21 @@ impl Service<'_> { .. }))) = response { - let raw_port = command.port().0 as usize; - let port_status = self.state.port_status.get(raw_port).ok_or(PdError::InvalidPort)?; + let port_status = port.get_port_status().await?; *battery_charging_status = - self.determine_battery_charging_capability_status(command.port(), port_status); + self.determine_battery_charging_capability_status(command.port(), &port_status); states_change.set_battery_charging_status_change(battery_charging_status.is_some()); } response } - _ => self.context.execute_ucsi_command(*command).await, + _ => port.execute_lpm_command(local_command).await, } } /// Update the CCI connector change field based on the current pending port fn set_cci_connector_change(&self, cci: &mut GlobalCci) { - if let Some(current_port) = self.state.ucsi.pending_ports.front() { + if let Some(current_port) = self.ucsi.pending_ports.front() { // UCSI connector numbers are 1-based cci.set_connector_change(GlobalPortId(current_port.0 + 1)); } else { @@ -152,18 +162,20 @@ impl Service<'_> { } /// Acknowledge the current connector change and move to the next if present - async fn ack_connector_change(&mut self, cci: &mut GlobalCci) { + async fn ack_connector_change(&mut self, port: &'port Reg::Port, cci: &mut GlobalCci) { // Pop the just acknowledged port and move to the next if present - if let Some(_current_port) = self.state.ucsi.pending_ports.pop_front() { - if let Some(next_port) = self.state.ucsi.pending_ports.front() { + if let Some(_current_port) = self.ucsi.pending_ports.pop_front() { + if let Some(next_port) = self.ucsi.pending_ports.front() { debug!("ACK_CCI processed, next pending port: {:?}", next_port); - self.context - .broadcast_message(Event::UcsiCci(UsciChangeIndicator { + self.broadcast_event(Event { + port, + event: EventData::UsciChangeIndicator(UsciChangeIndicatorData { port: *next_port, // False here because the OPM gets notified by the CCI, don't need a separate notification notify_opm: false, - })) - .await; + }), + }) + .await; } else { debug!("ACK_CCI processed, no more pending ports"); } @@ -175,7 +187,7 @@ impl Service<'_> { } /// Process a UCSI command - pub async fn process_ucsi_command(&mut self, command: &GlobalCommand) -> UcsiResponse { + pub async fn process_ucsi_command(&mut self, port: &'port Reg::Port, command: &GlobalCommand) -> UcsiResponse { let mut next_input = Some(PpmInput::Command(command)); let mut response = UcsiResponse { notify_opm: false, @@ -188,7 +200,7 @@ impl Service<'_> { // Using a loop allows all logic to be centralized loop { let output = if let Some(next_input) = next_input.take() { - self.state.ucsi.ppm_state_machine.consume(next_input) + self.ucsi.ppm_state_machine.consume(next_input) } else { error!("Unexpected end of state machine processing"); return UcsiResponse { @@ -232,20 +244,20 @@ impl Service<'_> { // Don't return yet, need to inform state machine that command is complete } PpmOutput::OpmNotifyCommandComplete => { - response.notify_opm = self.state.ucsi.notifications_enabled.cmd_complete(); + response.notify_opm = self.ucsi.notifications_enabled.cmd_complete(); response.cci.set_cmd_complete(true); response.cci.set_error(response.data.is_err()); self.set_cci_connector_change(&mut response.cci); return response; } PpmOutput::AckComplete(ack) => { - response.notify_opm = self.state.ucsi.notifications_enabled.cmd_complete(); + response.notify_opm = self.ucsi.notifications_enabled.cmd_complete(); if ack.command_complete() { response.cci.set_ack_command(true); } if ack.connector_change() { - self.ack_connector_change(&mut response.cci).await; + self.ack_connector_change(port, &mut response.cci).await; } return response; @@ -262,7 +274,7 @@ impl Service<'_> { } PpmOutput::OpmNotifyBusy => { // Notify if notifications are enabled in general - response.notify_opm = !self.state.ucsi.notifications_enabled.is_empty(); + response.notify_opm = !self.ucsi.notifications_enabled.is_empty(); response.cci.set_busy(true); self.set_cci_connector_change(&mut response.cci); return response; @@ -283,6 +295,7 @@ impl Service<'_> { /// Handle PD port events, update UCSI state, and generate corresponding UCSI notifications pub(super) async fn handle_ucsi_port_event( &mut self, + port: &'port Reg::Port, port_id: GlobalPortId, port_event: PortStatusEventBitfield, port_status: &PortStatus, @@ -308,48 +321,51 @@ impl Service<'_> { ucsi_event.set_battery_charging_status_change(true); // Power negotiation completed, battery charging capability status is now valid - if self - .state - .ucsi - .valid_battery_charging_capability - .insert(port_id) - .is_err() - { - error!("Valid battery charging capability overflow for port {:?}", port_id); + if self.ucsi.valid_battery_charging_capability.insert(port_id).is_err() { + error!( + "({}): Valid battery charging capability overflow", + port.lock().await.name() + ); } } if !port_status.is_connected() { // Reset battery charging capability status when disconnected - let _ = self.state.ucsi.valid_battery_charging_capability.remove(&port_id); + let _ = self.ucsi.valid_battery_charging_capability.remove(&port_id); } - if ucsi_event - .filter_enabled(self.state.ucsi.notifications_enabled) - .is_empty() - { + if ucsi_event.filter_enabled(self.ucsi.notifications_enabled).is_empty() { trace!("{:?}: event received, but no UCSI notifications enabled", port_id); return; } - self.pend_ucsi_port(port_id).await; + self.pend_ucsi_port(port, port_id).await; } /// Pend UCSI events for all connected ports pub(super) async fn pend_ucsi_connected_ports(&mut self) { // Panic Safety: i is limited by the length of port_status #[allow(clippy::indexing_slicing)] - for i in 0..self.state.port_status.len() { + for i in 0..self.registration.ports().len() { let port_id = GlobalPortId(i as u8); - if self.state.port_status[i].is_connected() { - self.pend_ucsi_port(port_id).await; + let Some(port) = self.registration.ports().get(i) else { + error!("Invalid port ID: {}", i); + continue; + }; + + if let Ok(port_status) = port.lock().await.get_port_status().await { + if port_status.is_connected() { + self.pend_ucsi_port(port, port_id).await; + } + } else { + error!("({}): Failed to get status for port", port.lock().await.name()); } } } /// Pend a UCSI event for the given port - async fn pend_ucsi_port(&mut self, port_id: GlobalPortId) { - if self.state.ucsi.pending_ports.iter().any(|pending| *pending == port_id) { + async fn pend_ucsi_port(&mut self, port: &'port Reg::Port, port_id: GlobalPortId) { + if self.ucsi.pending_ports.iter().any(|pending| *pending == port_id) { // Already have a pending event for this port, don't need to process it twice return; } @@ -357,14 +373,16 @@ impl Service<'_> { // Only notifiy the OPM if we don't have any pending events // Once the OPM starts processing events, the next pending port will be sent as part // of the CCI response to the ACK_CC_CI command. See [`Self::set_cci_connector_change`] - let notify_opm = self.state.ucsi.pending_ports.is_empty(); - if self.state.ucsi.pending_ports.push_back(port_id).is_ok() { - self.context - .broadcast_message(Event::UcsiCci(UsciChangeIndicator { + let notify_opm = self.ucsi.pending_ports.is_empty(); + if self.ucsi.pending_ports.push_back(port_id).is_ok() { + self.broadcast_event(Event { + port, + event: EventData::UsciChangeIndicator(UsciChangeIndicatorData { port: port_id, notify_opm, - })) - .await; + }), + }) + .await; } else { // This shouldn't happen because we have a single slot per port // Would likely indicate that an invalid port ID got in somehow diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs deleted file mode 100644 index d238c61cf..000000000 --- a/type-c-service/src/service/vdm.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! VDM (Vendor Defined Messages) related functionality. - -use embedded_usb_pd::{GlobalPortId, PdError}; -use type_c_interface::control::vdm::{AttnVdm, OtherVdm}; - -use super::Service; - -impl Service<'_> { - /// Get the other vdm for the given port - 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, 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 98dbe4f18..8ad364bd9 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,12 +1,18 @@ use embedded_services::{error, event::Receiver, info, sync::Lockable}; use power_policy_interface::service::event::EventData as PowerPolicyEventData; +use type_c_interface::port::pd::Pd; -use crate::service::{EventReceiver, Service}; +use crate::service::{Service, event_receiver::ArrayEventReceiver, registration::Registration}; /// Task to run the Type-C service, running the default event loop -pub async fn task>( - service: &'static impl Lockable>, - mut event_receiver: EventReceiver<'static, PowerReceiver>, +pub async fn task< + const N: usize, + Port: Lockable, + PortReceiver: Receiver, + PowerReceiver: Receiver, +>( + service: &'static impl Lockable>>, + mut event_receiver: ArrayEventReceiver<'static, N, Port, PortReceiver, PowerReceiver>, ) { info!("Starting type-c task"); diff --git a/type-c-service/tests/common/mod.rs b/type-c-service/tests/common/mod.rs new file mode 100644 index 000000000..24bc504b0 --- /dev/null +++ b/type-c-service/tests/common/mod.rs @@ -0,0 +1,381 @@ +use std::mem::ManuallyDrop; + +use embassy_futures::{ + join::{join, join3}, + select::{Either, select}, +}; +use embassy_sync::{ + channel::{Channel, DynamicReceiver, DynamicSender}, + mutex::Mutex, + once_lock::OnceLock, + watch, +}; +use embassy_time::{Duration, with_timeout}; +use embedded_services::{GlobalRawMutex, event::Sender}; +use embedded_usb_pd::LocalPortId; +use paste::paste; +use power_policy_interface::charger::mock::NoopCharger; +use type_c_service::service::registration::PortData; + +pub const DEFAULT_TEST_DURATION: Duration = Duration::from_secs(5); + +pub const DEFAULT_PER_CALL_TIMEOUT: Duration = Duration::from_secs(1); + +/// Total number of type-C ports +pub const TYPE_C_PORT_COUNT: usize = 3; +/// Number of senders for type-c service events +pub const TYPE_C_SERVICE_SENDER_COUNT: usize = 1; +/// Number of senders for power policy events +pub const POWER_POLICY_SENDER_COUNT: usize = 1; + +/// Mutex wrapped controller mock +pub type ControllerMockMutexType = Mutex; + +/// [`type_c_service::controller::Port`] sender to type-C service +pub type PortTypeCSender<'a> = DynamicSender<'a, type_c_interface::service::event::PortEventData>; +/// Corresponding receiver for [`PortTypeCSender`] +pub type PortTypeCReceiver<'a> = DynamicReceiver<'a, type_c_interface::service::event::PortEventData>; +/// [`type_c_service::controller::Port`] sender to power policy service +pub type PortPowerSender<'a> = DynamicSender<'a, power_policy_interface::psu::event::EventData>; +/// Corresponding receiver for [`PortPowerSender`] +pub type PortPowerReceiver<'a> = DynamicReceiver<'a, power_policy_interface::psu::event::EventData>; +/// [`type_c_service::controller::Port`] sender for loopback events +pub type PortLoopbackSender<'a> = DynamicSender<'a, type_c_service::controller::event::Loopback>; +/// Shared port state type +pub type PortSharedState = Mutex; +/// Port type +pub type PortMutexType<'port, 'ch> = Mutex< + GlobalRawMutex, + type_c_service::controller::Port< + 'port, + ControllerMockMutexType, + PortSharedState, + PortTypeCSender<'ch>, + PortPowerSender<'ch>, + PortLoopbackSender<'ch>, + >, +>; + +/// Sender for events broadcast by the power policy service +pub type PowerPolicyServiceSender<'port, 'ch> = PowerPolicyServiceEventRouter<'port, 'ch>; +/// Receiver for events broadcast by the power policy service +pub type PowerPolicyServiceReceiver<'port, 'ch> = + DynamicReceiver<'ch, power_policy_interface::service::event::Event<'port, PortMutexType<'port, 'ch>>>; +/// Power policy registration type +pub type PowerPolicyRegistrationType<'port, 'ch> = power_policy_service::service::registration::ArrayRegistration< + 'port, + // PSU type + PortMutexType<'port, 'ch>, + // PSU count + TYPE_C_PORT_COUNT, + // Senders for events broadcast by the service + PowerPolicyServiceSender<'port, 'ch>, + // Number of service event senders + POWER_POLICY_SENDER_COUNT, + // Charger type + Mutex, + // Charger count + 0, +>; +/// Power policy service type +pub type PowerPolicyServiceMutexType<'port, 'ch> = + Mutex>>; + +/// Sender for events broadcast by the type-C service +pub type TypeCServiceSender<'port, 'ch> = + DynamicSender<'ch, type_c_interface::service::event::Event<'port, PortMutexType<'port, 'ch>>>; +/// Receiver for events broadcast by the type-C service +pub type TypeCServiceReceiver<'port, 'ch> = + DynamicReceiver<'ch, type_c_interface::service::event::Event<'port, PortMutexType<'port, 'ch>>>; +/// Type-C service registration type +pub type TypeCRegistrationType<'port, 'ch> = type_c_service::service::registration::ArrayRegistration< + 'port, + PortMutexType<'port, 'ch>, + TYPE_C_PORT_COUNT, + TypeCServiceSender<'port, 'ch>, + TYPE_C_SERVICE_SENDER_COUNT, +>; +/// Type-C service type +pub type TypeCServiceMutexType<'port, 'ch> = + Mutex>>; + +pub const CHANNEL_SIZE: usize = 4; + +/// Struct to pass port components to a test implementation. +pub struct TestPort<'port, 'ch> { + pub port: &'port PortMutexType<'port, 'ch>, + pub mock: &'port ControllerMockMutexType, +} + +pub trait Test { + fn run<'port, 'ch>( + &mut self, + type_c_receiver: TypeCServiceReceiver<'port, 'ch>, + power_policy_receiver: PowerPolicyServiceReceiver<'port, 'ch>, + port0: TestPort<'port, 'ch>, + port1: TestPort<'port, 'ch>, + port2: TestPort<'port, 'ch>, + ) -> impl Future; +} + +/// Used by the [`define_port`] macro to work around macro hygiene issues. +struct PortComponents<'port, 'ch> { + port: PortMutexType<'port, 'ch>, + mock: &'port ControllerMockMutexType, + type_c_receiver: PortTypeCReceiver<'ch>, + power_policy_receiver: PortPowerReceiver<'ch>, +} + +macro_rules! define_port { + ($name:ident, $mock_name:expr, $port_name:expr, $config:expr, $local_id:expr) => { + let port0_type_c_channel: Channel< + GlobalRawMutex, + type_c_interface::service::event::PortEventData, + CHANNEL_SIZE, + > = Channel::new(); + paste! { let [<$name _type_c_sender>] = port0_type_c_channel.dyn_sender(); } + paste! { let [<$name _type_c_receiver>] = port0_type_c_channel.dyn_receiver(); } + + paste! { let [<$name _power_policy_channel>]: Channel< + GlobalRawMutex, + power_policy_interface::psu::event::EventData, + CHANNEL_SIZE, + > = Channel::new(); } + paste! { let [<$name _power_policy_sender>] = paste! { [<$name _power_policy_channel>]}.dyn_sender(); } + paste! { let [<$name _power_policy_receiver>] = paste! { [<$name _power_policy_channel>]}.dyn_receiver(); } + + paste! { let [<$name _loopback_channel>]: Channel< + GlobalRawMutex, + type_c_service::controller::event::Loopback, + CHANNEL_SIZE, + > = Channel::new(); } + paste! { let [<$name _loopback_sender>] = paste! { [<$name _loopback_channel>]}.dyn_sender(); } + + paste! { let [<$name _mock>] = Mutex::new(type_c_interface_mocks::controller::Mock::new($mock_name)); } + paste! { let [<$name _shared_state>] = + PortSharedState::new(type_c_service::controller::state::SharedState::new()); } + paste! { let $name = PortComponents { + port: Mutex::new(type_c_service::controller::Port::new( + $port_name, + $config, + $local_id, + &paste! { [<$name _mock>] }, + &paste! { [<$name _shared_state>] }, + paste! { [<$name _type_c_sender>] }, + paste! { [<$name _power_policy_sender>] }, + paste! { [<$name _loopback_sender>] }, + )), + mock: &paste! { [<$name _mock>] }, + type_c_receiver: paste! { [<$name _type_c_receiver>] }, + power_policy_receiver: paste! { [<$name _power_policy_receiver>] }, + }; + } + }; +} + +/// Router for events from the power policy service. Forwards events to the test receiver and the type-C service. +// TODO: remove this once enum_dispatch is implemented +pub struct PowerPolicyServiceEventRouter<'port, 'ch> { + /// Sender to the test receiver + test_sender: DynamicSender<'ch, power_policy_interface::service::event::Event<'port, PortMutexType<'port, 'ch>>>, + /// Sender to the type-C service + type_c_sender: DynamicSender<'ch, power_policy_interface::service::event::EventData>, +} + +impl<'port, 'ch> Sender>> + for PowerPolicyServiceEventRouter<'port, 'ch> +{ + fn try_send( + &mut self, + event: power_policy_interface::service::event::Event<'port, PortMutexType<'port, 'ch>>, + ) -> Option<()> { + self.test_sender.try_send(event).ok()?; + self.type_c_sender.try_send(event.into()).ok() + } + + async fn send(&mut self, event: power_policy_interface::service::event::Event<'port, PortMutexType<'port, 'ch>>) { + join(self.test_sender.send(event), self.type_c_sender.send(event.into())).await; + } +} + +async fn power_policy_task<'psu, 'ch, 'service, 'completion>( + mut completion_signal: watch::DynReceiver<'completion, ()>, + power_policy: &'service PowerPolicyServiceMutexType<'psu, 'ch>, + mut event_receivers: power_policy_service::psu::PsuEventReceivers< + 'psu, + TYPE_C_PORT_COUNT, + PortMutexType<'psu, 'ch>, + DynamicReceiver<'ch, power_policy_interface::psu::event::EventData>, + >, +) { + while let Either::First(event) = select(event_receivers.wait_event(), completion_signal.get()).await { + power_policy.lock().await.process_psu_event(event).await.unwrap(); + } +} + +async fn type_c_service_task<'port, 'ch, 'service, 'completion>( + mut completion_signal: watch::DynReceiver<'completion, ()>, + service: &'service TypeCServiceMutexType<'port, 'ch>, + mut event_receiver: type_c_service::service::event_receiver::ArrayEventReceiver< + 'port, + TYPE_C_PORT_COUNT, + PortMutexType<'port, 'ch>, + DynamicReceiver<'ch, type_c_interface::service::event::PortEventData>, + DynamicReceiver<'ch, power_policy_interface::service::event::EventData>, + >, +) { + while let Either::First(event) = select(event_receiver.wait_next(), completion_signal.get()).await { + service.lock().await.process_event(event).await.unwrap(); + } +} + +pub async fn run_test( + duration: Duration, + type_c_service_config: type_c_service::service::config::Config, + port_config: [type_c_service::controller::config::Config; TYPE_C_PORT_COUNT], + mut test: impl Test, +) { + // Tokio runs tests in parallel, but logging is global so we need to run tests sequentially to avoid interleaved logs. + static TEST_MUTEX: OnceLock> = OnceLock::new(); + let test_mutex = TEST_MUTEX.get_or_init(|| Mutex::new(())); + let _lock = test_mutex.lock().await; + + // Initialize logging, ignore the error if the logger was already initialized by another test. + let _ = env_logger::builder().filter_level(log::LevelFilter::Info).try_init(); + embedded_services::init().await; + + define_port!(port0, "mock0", "port0", port_config[0], LocalPortId(0)); + let PortComponents { + port: port0, + type_c_receiver: port0_type_c_receiver, + power_policy_receiver: port0_power_policy_receiver, + mock: port0_mock, + } = port0; + + define_port!(port1, "mock1", "port1", port_config[1], LocalPortId(0)); + let PortComponents { + port: port1, + type_c_receiver: port1_type_c_receiver, + power_policy_receiver: port1_power_policy_receiver, + mock: port1_mock, + } = port1; + + define_port!(port2, "mock2", "port2", port_config[2], LocalPortId(0)); + let PortComponents { + port: port2, + type_c_receiver: port2_type_c_receiver, + power_policy_receiver: port2_power_policy_receiver, + mock: port2_mock, + } = port2; + + // Channel to broadcast events from the type-C service + let type_c_service_channel: ManuallyDrop< + Channel>, CHANNEL_SIZE>, + > = ManuallyDrop::new(Channel::new()); + let type_c_service_sender = type_c_service_channel.dyn_sender(); + let type_c_service_receiver = type_c_service_channel.dyn_receiver(); + + let type_c_service = Mutex::new(type_c_service::service::Service::new( + type_c_service_config, + TypeCRegistrationType { + ports: [&port0, &port1, &port2], + port_data: [ + PortData { + local_port: Some(LocalPortId(0)), + }, + PortData { + local_port: Some(LocalPortId(0)), + }, + PortData { + local_port: Some(LocalPortId(0)), + }, + ], + service_senders: [type_c_service_sender], + }, + )); + + // Channel for events from the power policy service to the type-C service + let type_c_power_policy_events: ManuallyDrop< + Channel, + > = ManuallyDrop::new(Channel::new()); + let type_c_power_policy_sender = type_c_power_policy_events.dyn_sender(); + let type_c_power_policy_receiver = type_c_power_policy_events.dyn_receiver(); + + let type_c_service_event_receivers = type_c_service::service::event_receiver::ArrayEventReceiver::new( + [&port0, &port1, &port2], + [port0_type_c_receiver, port1_type_c_receiver, port2_type_c_receiver], + type_c_power_policy_receiver, + ); + + // Channel for events from the power policy service to the test + let power_policy_service_channel: ManuallyDrop< + Channel>, CHANNEL_SIZE>, + > = ManuallyDrop::new(Channel::new()); + let power_policy_service_sender = power_policy_service_channel.dyn_sender(); + let power_policy_service_receiver = power_policy_service_channel.dyn_receiver(); + + // Router for power policy service events + let power_policy_service_event_router = PowerPolicyServiceEventRouter { + test_sender: power_policy_service_sender, + type_c_sender: type_c_power_policy_sender, + }; + + let power_policy_service = Mutex::new(power_policy_service::service::Service::new( + power_policy_service::service::registration::ArrayRegistration { + psus: [&port0, &port1, &port2], + chargers: [], + service_senders: [power_policy_service_event_router], + }, + Default::default(), + )); + + let power_policy_event_receiver = power_policy_service::psu::PsuEventReceivers { + psu_devices: [&port0, &port1, &port2], + receivers: [ + port0_power_policy_receiver, + port1_power_policy_receiver, + port2_power_policy_receiver, + ], + }; + + let completion_signal: watch::Watch = watch::Watch::new(); + let completion_sender = completion_signal.dyn_sender(); + + with_timeout( + duration, + join3( + power_policy_task( + completion_signal.dyn_receiver().unwrap(), + &power_policy_service, + power_policy_event_receiver, + ), + type_c_service_task( + completion_signal.dyn_receiver().unwrap(), + &type_c_service, + type_c_service_event_receivers, + ), + async { + test.run( + type_c_service_receiver, + power_policy_service_receiver, + TestPort { + port: &port0, + mock: port0_mock, + }, + TestPort { + port: &port1, + mock: port1_mock, + }, + TestPort { + port: &port2, + mock: port2_mock, + }, + ) + .await; + completion_sender.send(()); + }, + ), + ) + .await + .unwrap(); +} diff --git a/type-c-service/tests/power.rs b/type-c-service/tests/power.rs new file mode 100644 index 000000000..27e22a8fa --- /dev/null +++ b/type-c-service/tests/power.rs @@ -0,0 +1,130 @@ +#![allow(clippy::unwrap_used)] +#![allow(clippy::panic)] +use std::ptr; + +use embassy_futures::join::join; +use embassy_time::{TimeoutError, with_timeout}; +use embedded_usb_pd::{PowerRole, type_c::ConnectionState}; +use power_policy_interface::{ + capability::{ConsumerFlags, ConsumerPowerCapability, PsuType}, + service::event::Event as PowerPolicyEvent, +}; +use type_c_interface::{ + control::pd::PortStatus, + port::event::{PortEvent, PortStatusEventBitfield}, +}; +use type_c_service::{controller::event::Event, util::POWER_CAPABILITY_5V_1A5}; + +use crate::common::{ + DEFAULT_PER_CALL_TIMEOUT, DEFAULT_TEST_DURATION, PowerPolicyServiceReceiver, Test, TestPort, TypeCServiceReceiver, +}; + +mod common; + +/// Test basic consumer attach flow +struct TestBasicConsumerFlow; + +impl Test for TestBasicConsumerFlow { + async fn run<'port, 'ch>( + &mut self, + type_c_receiver: TypeCServiceReceiver<'port, 'ch>, + power_policy_receiver: PowerPolicyServiceReceiver<'port, 'ch>, + port0: TestPort<'port, 'ch>, + _port1: TestPort<'port, 'ch>, + _port2: TestPort<'port, 'ch>, + ) { + { + // Set up the mock to report a sink connection and allow enabling the sink path + let mut mock0 = port0.mock.lock().await; + mock0.next_result_get_port_status = Some(Ok(PortStatus { + available_sink_contract: Some(POWER_CAPABILITY_5V_1A5), + connection_state: Some(ConnectionState::Attached), + power_role: PowerRole::Sink, + ..Default::default() + })); + } + + // Simulate a plug event and a new consumer contract + let mut port_event = PortStatusEventBitfield::none(); + port_event.set_plug_inserted_or_removed(true); + port_event.set_new_power_contract_as_consumer(true); + port_event.set_sink_ready(true); + + port0 + .port + .lock() + .await + .process_event(Event::PortEvent(PortEvent::StatusChanged(port_event))) + .await + .unwrap(); + + let (type_c_result, power_policy_result) = join( + with_timeout(DEFAULT_PER_CALL_TIMEOUT, type_c_receiver.receive()), + with_timeout(DEFAULT_PER_CALL_TIMEOUT, power_policy_receiver.receive()), + ) + .await; + + // Shouldn't get any Type-C service events in this flow + assert_eq!(type_c_result.err(), Some(TimeoutError)); + + // Power policy service should broadcast a consumer connect event + match power_policy_result { + Ok(PowerPolicyEvent::ConsumerConnected(psu, capability)) => { + assert_eq!( + capability, + ConsumerPowerCapability { + capability: POWER_CAPABILITY_5V_1A5, + flags: ConsumerFlags::none().with_psu_type(PsuType::TypeC), + } + ); + assert!(ptr::eq(psu, port0.port)); + } + _ => panic!("Did not receive consumer connected event"), + } + + { + // Set up the mock to report an unplug + let mut mock0 = port0.mock.lock().await; + mock0.next_result_get_port_status = None; + } + + // Simulate an unplug event + let mut port_event = PortStatusEventBitfield::none(); + port_event.set_plug_inserted_or_removed(true); + + port0 + .port + .lock() + .await + .process_event(Event::PortEvent(PortEvent::StatusChanged(port_event))) + .await + .unwrap(); + + let (type_c_result, power_policy_result) = join( + with_timeout(DEFAULT_PER_CALL_TIMEOUT, type_c_receiver.receive()), + with_timeout(DEFAULT_PER_CALL_TIMEOUT, power_policy_receiver.receive()), + ) + .await; + + // Type-C service currently shouldn't broadcast any events in this flow + assert_eq!(type_c_result.err(), Some(TimeoutError)); + // Power policy service should broadcast a consumer disconnect event + match power_policy_result { + Ok(PowerPolicyEvent::ConsumerDisconnected(psu)) => { + assert!(ptr::eq(psu, port0.port)); + } + _ => panic!("Did not receive consumer disconnected event"), + } + } +} + +#[tokio::test] +async fn test_basic_consumer_flow() { + common::run_test( + DEFAULT_TEST_DURATION, + Default::default(), + Default::default(), + TestBasicConsumerFlow, + ) + .await; +}