From 296dcc605d3399fe35b1967cade59f5aac28aa87 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Thu, 11 Dec 2025 08:26:01 -0800 Subject: [PATCH 01/79] v0.2.0 mergeback: Fix charge fail issue of hotplug dock on same port (#595) (#630) Mergeback commit 7a7f754422621aa8a47c211e9f014e7a72fe6018 from main into v0.2.0 Co-authored-by: zhuyi2024 <79184862@qq.com> Co-authored-by: zhuyi --- power-policy-service/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index e7437f129..c43719f81 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -58,10 +58,8 @@ impl PowerPolicy { async fn process_notify_detach(&self, device: &device::Device) -> Result<(), Error> { self.context.send_response(Ok(policy::ResponseData::Complete)).await; - if !self.remove_connected_provider(device.id()).await { - // Only update consumers if a consumer was detached - self.update_current_consumer().await?; - } + self.remove_connected_provider(device.id()).await; + self.update_current_consumer().await?; Ok(()) } From 2e46e7733305d9f044df1ee77236b5c704459302 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Fri, 9 Jan 2026 11:55:17 -0800 Subject: [PATCH 02/79] v0.2.0 mergeback: 01-09-2026 bincode CI regression (#672) Mergeback commit c7f8e86c33f81da6bbdfd1f80effdb9bfa3dcad0 from #669 into v0.2.0 , which is failing CI. --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index c59ab90d7..d856b23a2 100644 --- a/deny.toml +++ b/deny.toml @@ -77,6 +77,7 @@ ignore = [ { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, { id = "RUSTSEC-2023-0089", reason = "this is a deprecation warning for a dependency of a dependency. https://github.com/jamesmunns/postcard/issues/223 tracks fixing the dependency; until that's resolved, we can accept the deprecated code as it has no known vulnerabilities." }, + { id = "RUSTSEC-2025-0141", reason = "bincode is unmaintained, planning on migrating to an alternative." }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. From 83b11d55ae5f6d46240dc5d27112210112622931 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:47:47 -0800 Subject: [PATCH 03/79] Rework MCTP/comms to have separate types/messaging crates for each service (#670) This change breaks requests and responses sent over the comms bus into distinct types for each kind of service that leverages the bus to communicate to the host rather than a single enum with all possible request and response types. Previously, these were all defined in the embedded-service crate, which meant that new services couldn't be implemented without forking this repo and extending that enum. With this change, each service has an associated 'messages' crate that defines the types of requests and responses associated with that service and how to serialize/deserialize them. This incidentally removes a lot of boilerplate across all services associated with handling message types that are not intended for the service that receives them, since services no longer need to reason about other services' message types. In the course of this work, a few bugs in message serialization were found and fixed (out of order fields and the like). --- Cargo.lock | 53 +- Cargo.toml | 7 + battery-service-messages/Cargo.toml | 18 + battery-service-messages/src/lib.rs | 719 +++++++++ battery-service/Cargo.toml | 2 + battery-service/src/acpi.rs | 685 +++------ battery-service/src/context.rs | 80 +- battery-service/src/device.rs | 7 +- battery-service/src/lib.rs | 12 +- debug-service-messages/Cargo.toml | 17 + debug-service-messages/src/lib.rs | 115 ++ debug-service/Cargo.toml | 4 + debug-service/src/debug_service.rs | 5 +- debug-service/src/task.rs | 34 +- embedded-service/src/ec_type/message.rs | 182 --- embedded-service/src/ec_type/mod.rs | 1 - .../src/ec_type/protocols/acpi.rs | 35 - .../src/ec_type/protocols/debug.rs | 8 - .../src/ec_type/protocols/mctp.rs | 1367 ----------------- embedded-service/src/ec_type/protocols/mod.rs | 15 - .../src/ec_type/protocols/mptf.rs | 17 - embedded-service/src/lib.rs | 1 + embedded-service/src/relay/mod.rs | 97 ++ espi-service/Cargo.toml | 11 + espi-service/src/espi_service.rs | 401 ++--- espi-service/src/lib.rs | 1 + espi-service/src/mctp.rs | 354 +++++ espi-service/src/task.rs | 4 +- examples/rt633/Cargo.lock | 51 +- examples/rt685s-evk/Cargo.lock | 17 +- examples/rt685s-evk/Cargo.toml | 1 + examples/std/Cargo.lock | 31 + examples/std/Cargo.toml | 2 + examples/std/src/bin/debug.rs | 67 +- examples/std/src/bin/thermal.rs | 60 +- keyboard-service/src/lib.rs | 1 + thermal-service-messages/Cargo.toml | 18 + thermal-service-messages/src/lib.rs | 277 ++++ thermal-service/Cargo.toml | 2 + thermal-service/src/context.rs | 39 +- thermal-service/src/lib.rs | 33 +- thermal-service/src/mptf.rs | 643 ++------ thermal-service/src/task.rs | 7 +- thermal-service/src/utils.rs | 7 +- 44 files changed, 2411 insertions(+), 3097 deletions(-) create mode 100644 battery-service-messages/Cargo.toml create mode 100644 battery-service-messages/src/lib.rs create mode 100644 debug-service-messages/Cargo.toml create mode 100644 debug-service-messages/src/lib.rs delete mode 100644 embedded-service/src/ec_type/protocols/acpi.rs delete mode 100644 embedded-service/src/ec_type/protocols/debug.rs delete mode 100644 embedded-service/src/ec_type/protocols/mctp.rs delete mode 100644 embedded-service/src/ec_type/protocols/mod.rs delete mode 100644 embedded-service/src/ec_type/protocols/mptf.rs create mode 100644 embedded-service/src/relay/mod.rs create mode 100644 espi-service/src/mctp.rs create mode 100644 thermal-service-messages/Cargo.toml create mode 100644 thermal-service-messages/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b2be60584..a48728f17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ + "battery-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -172,6 +173,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + [[package]] name = "bbq2" version = "0.4.2" @@ -202,14 +213,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -450,6 +453,7 @@ version = "0.1.0" dependencies = [ "bbq2", "critical-section", + "debug-service-messages", "defmt 0.3.100", "embassy-sync", "embassy-time", @@ -458,6 +462,15 @@ dependencies = [ "rtt-target", ] +[[package]] +name = "debug-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -910,9 +923,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags 2.9.4", "num-traits", "num_enum", @@ -924,8 +937,11 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ + "battery-service-messages", + "bitfield 0.17.0", "cortex-m", "cortex-m-rt", + "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-imxrt", @@ -934,6 +950,8 @@ dependencies = [ "embedded-services", "log", "mctp-rs", + "num_enum", + "thermal-service-messages", ] [[package]] @@ -1253,9 +1271,9 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1986,6 +2004,17 @@ dependencies = [ "heapless", "log", "mctp-rs", + "thermal-service-messages", + "uuid", +] + +[[package]] +name = "thermal-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index e5a02b4bb..3f7b0142a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,9 @@ resolver = "2" members = [ "battery-service", + "battery-service-messages", "thermal-service", + "thermal-service-messages", "cfu-service", "embedded-service", "espi-service", @@ -15,6 +17,7 @@ members = [ "power-policy-service", "type-c-service", "debug-service", + "debug-service-messages", "keyboard-service", ] exclude = ["examples/*"] @@ -45,6 +48,7 @@ unwrap_used = "deny" [workspace.dependencies] aligned = "0.4" anyhow = "1.0" +battery-service-messages = { path = "./battery-service-messages" } bitfield = "0.17.0" bitflags = "2.8.0" bitvec = { version = "1.0.1", default-features = false } @@ -56,6 +60,7 @@ cortex-m-rt = "0.7.5" critical-section = "1.1" defmt = "0.3" document-features = "0.2.7" +debug-service-messages = { path = "./debug-service-messages" } embassy-futures = "0.1.2" embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" } embassy-sync = "0.7.2" @@ -68,6 +73,7 @@ embedded-hal-async = "1.0" embedded-hal-nb = "1.0" embedded-io = "0.6.1" embedded-io-async = "0.6.1" +embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } embedded-services = { path = "./embedded-service" } embedded-storage = "0.3" embedded-storage-async = "0.4.1" @@ -86,6 +92,7 @@ rstest = { version = "0.26.1", default-features = false } serde = { version = "1.0.*", default-features = false } static_cell = "2.1.0" toml = { version = "0.8", default-features = false } +thermal-service-messages = { path = "./thermal-service-messages" } syn = "2.0" tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" } tokio = { version = "1.42.0" } diff --git a/battery-service-messages/Cargo.toml b/battery-service-messages/Cargo.toml new file mode 100644 index 000000000..86d56e089 --- /dev/null +++ b/battery-service-messages/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "battery-service-messages" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embedded-batteries-async.workspace = true +embedded-services.workspace = true +num_enum.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt", "embedded-batteries-async/defmt"] diff --git a/battery-service-messages/src/lib.rs b/battery-service-messages/src/lib.rs new file mode 100644 index 000000000..3c856b9a8 --- /dev/null +++ b/battery-service-messages/src/lib.rs @@ -0,0 +1,719 @@ +#![no_std] + +use embedded_batteries_async::acpi::{ + BCT_RETURN_SIZE_BYTES, BMD_RETURN_SIZE_BYTES, BPC_RETURN_SIZE_BYTES, BPS_RETURN_SIZE_BYTES, BST_RETURN_SIZE_BYTES, + BTM_RETURN_SIZE_BYTES, PSR_RETURN_SIZE_BYTES, STA_RETURN_SIZE_BYTES, +}; +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// Standard Battery Service Model Number String Size +pub const STD_BIX_MODEL_SIZE: usize = 8; +/// Standard Battery Service Serial Number String Size +pub const STD_BIX_SERIAL_SIZE: usize = 8; +/// Standard Battery Service Battery Type String Size +pub const STD_BIX_BATTERY_SIZE: usize = 8; +/// Standard Battery Service OEM Info String Size +pub const STD_BIX_OEM_SIZE: usize = 8; +/// Standard Power Policy Service Model Number String Size +pub const STD_PIF_MODEL_SIZE: usize = 8; +/// Standard Power Policy Serial Number String Size +pub const STD_PIF_SERIAL_SIZE: usize = 8; +/// Standard Power Policy Service OEM Info String Size +pub const STD_PIF_OEM_SIZE: usize = 8; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// ACPI Battery Methods +enum BatteryCmd { + /// Battery Information eXtended + GetBix = 1, + /// Battery Status + GetBst = 2, + /// Power Source + GetPsr = 3, + /// Power source InFormation + GetPif = 4, + /// Battery Power State + GetBps = 5, + /// Battery Trip Point + SetBtp = 6, + /// Battery Power Threshold + SetBpt = 7, + /// Battery Power Characteristics + GetBpc = 8, + /// Battery Maintenance Control + SetBmc = 9, + /// Battery Maintenance Data + GetBmd = 10, + /// Battery Charge Time + GetBct = 11, + /// Battery Time + GetBtm = 12, + /// Battery Measurement Sampling Time + SetBms = 13, + /// Battery Measurement Averaging Interval + SetBma = 14, + /// Device Status + GetSta = 15, +} + +impl From<&AcpiBatteryRequest> for BatteryCmd { + fn from(request: &AcpiBatteryRequest) -> Self { + match request { + AcpiBatteryRequest::BatteryGetBixRequest { .. } => BatteryCmd::GetBix, + AcpiBatteryRequest::BatteryGetBstRequest { .. } => BatteryCmd::GetBst, + AcpiBatteryRequest::BatteryGetPsrRequest { .. } => BatteryCmd::GetPsr, + AcpiBatteryRequest::BatteryGetPifRequest { .. } => BatteryCmd::GetPif, + AcpiBatteryRequest::BatteryGetBpsRequest { .. } => BatteryCmd::GetBps, + AcpiBatteryRequest::BatterySetBtpRequest { .. } => BatteryCmd::SetBtp, + AcpiBatteryRequest::BatterySetBptRequest { .. } => BatteryCmd::SetBpt, + AcpiBatteryRequest::BatteryGetBpcRequest { .. } => BatteryCmd::GetBpc, + AcpiBatteryRequest::BatterySetBmcRequest { .. } => BatteryCmd::SetBmc, + AcpiBatteryRequest::BatteryGetBmdRequest { .. } => BatteryCmd::GetBmd, + AcpiBatteryRequest::BatteryGetBctRequest { .. } => BatteryCmd::GetBct, + AcpiBatteryRequest::BatteryGetBtmRequest { .. } => BatteryCmd::GetBtm, + AcpiBatteryRequest::BatterySetBmsRequest { .. } => BatteryCmd::SetBms, + AcpiBatteryRequest::BatterySetBmaRequest { .. } => BatteryCmd::SetBma, + AcpiBatteryRequest::BatteryGetStaRequest { .. } => BatteryCmd::GetSta, + } + } +} + +impl From<&AcpiBatteryResponse> for BatteryCmd { + fn from(response: &AcpiBatteryResponse) -> Self { + match response { + AcpiBatteryResponse::BatteryGetBixResponse { .. } => BatteryCmd::GetBix, + AcpiBatteryResponse::BatteryGetBstResponse { .. } => BatteryCmd::GetBst, + AcpiBatteryResponse::BatteryGetPsrResponse { .. } => BatteryCmd::GetPsr, + AcpiBatteryResponse::BatteryGetPifResponse { .. } => BatteryCmd::GetPif, + AcpiBatteryResponse::BatteryGetBpsResponse { .. } => BatteryCmd::GetBps, + AcpiBatteryResponse::BatterySetBtpResponse { .. } => BatteryCmd::SetBtp, + AcpiBatteryResponse::BatterySetBptResponse { .. } => BatteryCmd::SetBpt, + AcpiBatteryResponse::BatteryGetBpcResponse { .. } => BatteryCmd::GetBpc, + AcpiBatteryResponse::BatterySetBmcResponse { .. } => BatteryCmd::SetBmc, + AcpiBatteryResponse::BatteryGetBmdResponse { .. } => BatteryCmd::GetBmd, + AcpiBatteryResponse::BatteryGetBctResponse { .. } => BatteryCmd::GetBct, + AcpiBatteryResponse::BatteryGetBtmResponse { .. } => BatteryCmd::GetBtm, + AcpiBatteryResponse::BatterySetBmsResponse { .. } => BatteryCmd::SetBms, + AcpiBatteryResponse::BatterySetBmaResponse { .. } => BatteryCmd::SetBma, + AcpiBatteryResponse::BatteryGetStaResponse { .. } => BatteryCmd::GetSta, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BixFixedStrings { + /// Revision of the BIX structure. Current revision is 1. + pub revision: u32, + /// Unit used for capacity and rate values. + pub power_unit: embedded_batteries_async::acpi::PowerUnit, + /// Design capacity of the battery (in mWh or mAh). + pub design_capacity: u32, + /// Last full charge capacity (in mWh or mAh). + pub last_full_charge_capacity: u32, + /// Battery technology type. + pub battery_technology: embedded_batteries_async::acpi::BatteryTechnology, + /// Design voltage (in mV). + pub design_voltage: u32, + /// Warning capacity threshold (in mWh or mAh). + pub design_cap_of_warning: u32, + /// Low capacity threshold (in mWh or mAh). + pub design_cap_of_low: u32, + /// Number of charge/discharge cycles. + pub cycle_count: u32, + /// Measurement accuracy in thousandths of a percent (e.g., 80000 = 80.000%). + pub measurement_accuracy: u32, + /// Maximum supported sampling time (in ms). + pub max_sampling_time: u32, + /// Minimum supported sampling time (in ms). + pub min_sampling_time: u32, + /// Maximum supported averaging interval (in ms). + pub max_averaging_interval: u32, + /// Minimum supported averaging interval (in ms). + pub min_averaging_interval: u32, + /// Capacity granularity between low and warning (in mWh or mAh). + pub battery_capacity_granularity_1: u32, + /// Capacity granularity between warning and full (in mWh or mAh). + pub battery_capacity_granularity_2: u32, + /// OEM-specific model number (ASCIIZ). + pub model_number: [u8; STD_BIX_MODEL_SIZE], + /// OEM-specific serial number (ASCIIZ). + pub serial_number: [u8; STD_BIX_SERIAL_SIZE], + /// OEM-specific battery type (ASCIIZ). + pub battery_type: [u8; STD_BIX_BATTERY_SIZE], + /// OEM-specific information (ASCIIZ). + pub oem_info: [u8; STD_BIX_OEM_SIZE], + /// Battery swapping capability. + pub battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability, +} + +// TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? +impl BixFixedStrings { + pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), MessageSerializationError> { + const MODEL_NUM_START_IDX: usize = 64; + let model_num_end_idx: usize = MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; + let serial_num_start_idx = model_num_end_idx; + let serial_num_end_idx = serial_num_start_idx + STD_BIX_SERIAL_SIZE; + let battery_type_start_idx = serial_num_end_idx; + let battery_type_end_idx = battery_type_start_idx + STD_BIX_BATTERY_SIZE; + let oem_info_start_idx = battery_type_end_idx; + let oem_info_end_idx = oem_info_start_idx + STD_BIX_OEM_SIZE; + + if dst_slice.len() < oem_info_end_idx { + return Err(MessageSerializationError::BufferTooSmall); + } + + dst_slice + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.revision)); + dst_slice + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.power_unit.into())); + dst_slice + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_capacity)); + dst_slice + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.last_full_charge_capacity)); + dst_slice + .get_mut(16..20) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_technology.into())); + dst_slice + .get_mut(20..24) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_voltage)); + dst_slice + .get_mut(24..28) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_cap_of_warning)); + dst_slice + .get_mut(28..32) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.design_cap_of_low)); + dst_slice + .get_mut(32..36) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.cycle_count)); + dst_slice + .get_mut(36..40) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.measurement_accuracy)); + dst_slice + .get_mut(40..44) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_sampling_time)); + dst_slice + .get_mut(44..48) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.min_sampling_time)); + dst_slice + .get_mut(48..52) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_averaging_interval)); + dst_slice + .get_mut(52..56) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.min_averaging_interval)); + dst_slice + .get_mut(56..60) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_1)); + dst_slice + .get_mut(60..64) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_2)); + dst_slice + .get_mut(MODEL_NUM_START_IDX..model_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.model_number); + dst_slice + .get_mut(serial_num_start_idx..serial_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.serial_number); + dst_slice + .get_mut(battery_type_start_idx..battery_type_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.battery_type); + dst_slice + .get_mut(oem_info_start_idx..oem_info_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.oem_info); + dst_slice + .get_mut(oem_info_end_idx..oem_info_end_idx + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.battery_swapping_capability.into())); + Ok(()) + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PifFixedStrings { + /// Bitfield describing the state and characteristics of the power source. + pub power_source_state: embedded_batteries_async::acpi::PowerSourceState, + /// Maximum rated output power in milliwatts (mW). + /// + /// 0xFFFFFFFF indicates the value is unavailable. + pub max_output_power: u32, + /// Maximum rated input power in milliwatts (mW). + /// + /// 0xFFFFFFFF indicates the value is unavailable. + pub max_input_power: u32, + /// OEM-specific model number (ASCIIZ). Empty string if not supported. + pub model_number: [u8; STD_BIX_MODEL_SIZE], + /// OEM-specific serial number (ASCIIZ). Empty string if not supported. + pub serial_number: [u8; STD_BIX_SERIAL_SIZE], + /// OEM-specific information (ASCIIZ). Empty string if not supported. + pub oem_info: [u8; STD_BIX_OEM_SIZE], +} + +impl PifFixedStrings { + pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), MessageSerializationError> { + const MODEL_NUM_START_IDX: usize = 12; + let model_num_end_idx: usize = MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; + let serial_num_start_idx = model_num_end_idx; + let serial_num_end_idx = serial_num_start_idx + STD_BIX_SERIAL_SIZE; + let oem_info_start_idx = serial_num_end_idx; + let oem_info_end_idx = oem_info_start_idx + STD_BIX_OEM_SIZE; + + if dst_slice.len() < oem_info_end_idx { + return Err(MessageSerializationError::BufferTooSmall); + } + + dst_slice + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.power_source_state.bits())); + dst_slice + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_output_power)); + dst_slice + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(self.max_input_power)); + dst_slice + .get_mut(MODEL_NUM_START_IDX..model_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.model_number); + dst_slice + .get_mut(serial_num_start_idx..serial_num_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.serial_number); + dst_slice + .get_mut(oem_info_start_idx..oem_info_end_idx) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&self.oem_info); + Ok(()) + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiBatteryRequest { + BatteryGetBixRequest { + battery_id: u8, + }, + BatteryGetBstRequest { + battery_id: u8, + }, + BatteryGetPsrRequest { + battery_id: u8, + }, + BatteryGetPifRequest { + battery_id: u8, + }, + BatteryGetBpsRequest { + battery_id: u8, + }, + BatterySetBtpRequest { + battery_id: u8, + btp: embedded_batteries_async::acpi::Btp, + }, + BatterySetBptRequest { + battery_id: u8, + bpt: embedded_batteries_async::acpi::Bpt, + }, + BatteryGetBpcRequest { + battery_id: u8, + }, + BatterySetBmcRequest { + battery_id: u8, + bmc: embedded_batteries_async::acpi::Bmc, + }, + BatteryGetBmdRequest { + battery_id: u8, + }, + BatteryGetBctRequest { + battery_id: u8, + bct: embedded_batteries_async::acpi::Bct, + }, + BatteryGetBtmRequest { + battery_id: u8, + btm: embedded_batteries_async::acpi::Btm, + }, + BatterySetBmsRequest { + battery_id: u8, + bms: embedded_batteries_async::acpi::Bms, + }, + BatterySetBmaRequest { + battery_id: u8, + bma: embedded_batteries_async::acpi::Bma, + }, + BatteryGetStaRequest { + battery_id: u8, + }, +} + +impl SerializableMessage for AcpiBatteryRequest { + fn serialize(self, _buffer: &mut [u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match BatteryCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + BatteryCmd::GetBix => Self::BatteryGetBixRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBst => Self::BatteryGetBstRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetPsr => Self::BatteryGetPsrRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetPif => Self::BatteryGetPifRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBps => Self::BatteryGetBpsRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::SetBtp => Self::BatterySetBtpRequest { + battery_id: safe_get_u8(buffer, 0)?, + btp: embedded_batteries_async::acpi::Btp { + trip_point: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBpt => Self::BatterySetBptRequest { + battery_id: safe_get_u8(buffer, 0)?, + bpt: embedded_batteries_async::acpi::Bpt { + revision: safe_get_dword(buffer, 1)?, + threshold_id: match safe_get_dword(buffer, 5)? { + 0 => embedded_batteries_async::acpi::ThresholdId::ClearAll, + 1 => embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower, + 2 => embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower, + _ => { + return Err(MessageSerializationError::InvalidPayload("Unsupported threshold id")); + } + }, + threshold_value: safe_get_dword(buffer, 9)?, + }, + }, + BatteryCmd::GetBpc => Self::BatteryGetBpcRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::SetBmc => Self::BatterySetBmcRequest { + battery_id: safe_get_u8(buffer, 0)?, + bmc: embedded_batteries_async::acpi::Bmc { + maintenance_control_flags: embedded_batteries_async::acpi::BmcControlFlags::from_bits_retain( + safe_get_dword(buffer, 1)?, + ), + }, + }, + BatteryCmd::GetBmd => Self::BatteryGetBmdRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBct => Self::BatteryGetBctRequest { + battery_id: safe_get_u8(buffer, 0)?, + bct: embedded_batteries_async::acpi::Bct { + charge_level_percent: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::GetBtm => Self::BatteryGetBtmRequest { + battery_id: safe_get_u8(buffer, 0)?, + btm: embedded_batteries_async::acpi::Btm { + discharge_rate: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBms => Self::BatterySetBmsRequest { + battery_id: safe_get_u8(buffer, 0)?, + bms: embedded_batteries_async::acpi::Bms { + sampling_time_ms: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBma => Self::BatterySetBmaRequest { + battery_id: safe_get_u8(buffer, 0)?, + bma: embedded_batteries_async::acpi::Bma { + averaging_interval_ms: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::GetSta => Self::BatteryGetStaRequest { + battery_id: safe_get_u8(buffer, 0)?, + }, + }, + ) + } + + fn discriminant(&self) -> u16 { + BatteryCmd::from(self).into() + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiBatteryResponse { + BatteryGetBixResponse { + bix: BixFixedStrings, + }, + BatteryGetBstResponse { + bst: embedded_batteries_async::acpi::BstReturn, + }, + BatteryGetPsrResponse { + psr: embedded_batteries_async::acpi::PsrReturn, + }, + BatteryGetPifResponse { + pif: PifFixedStrings, + }, + BatteryGetBpsResponse { + bps: embedded_batteries_async::acpi::Bps, + }, + BatterySetBtpResponse {}, + BatterySetBptResponse {}, + BatteryGetBpcResponse { + bpc: embedded_batteries_async::acpi::Bpc, + }, + BatterySetBmcResponse {}, + BatteryGetBmdResponse { + bmd: embedded_batteries_async::acpi::Bmd, + }, + BatteryGetBctResponse { + bct_response: embedded_batteries_async::acpi::BctReturnResult, + }, + BatteryGetBtmResponse { + btm_response: embedded_batteries_async::acpi::BtmReturnResult, + }, + BatterySetBmsResponse { + status: u32, + }, + BatterySetBmaResponse { + status: u32, + }, + BatteryGetStaResponse { + sta: embedded_batteries_async::acpi::StaReturn, + }, +} + +impl SerializableMessage for AcpiBatteryResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::BatteryGetBixResponse { bix } => bix.to_bytes(buffer).map(|_| 100), + Self::BatteryGetBstResponse { bst } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_state.bits())); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_present_rate)); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_remaining_capacity)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bst.battery_present_voltage)); + + Ok(BST_RETURN_SIZE_BYTES) + } + Self::BatteryGetPsrResponse { psr } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(psr.power_source.into())); + + Ok(PSR_RETURN_SIZE_BYTES) + } + + Self::BatteryGetPifResponse { pif } => pif.to_bytes(buffer).map(|_| 36), + Self::BatteryGetBpsResponse { bps } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.revision)); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_level)); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_period)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_level)); + buffer + .get_mut(16..20) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_period)); + + Ok(BPS_RETURN_SIZE_BYTES) + } + Self::BatterySetBtpResponse {} => Ok(0), + Self::BatterySetBptResponse {} => Ok(0), + Self::BatteryGetBpcResponse { bpc } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.revision)); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.power_threshold_support.bits())); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.max_instantaneous_peak_power_threshold)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bpc.max_sustainable_peak_power_threshold)); + + Ok(BPC_RETURN_SIZE_BYTES) + } + Self::BatterySetBmcResponse {} => Ok(0), + Self::BatteryGetBmdResponse { bmd } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.status_flags.bits())); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.capability_flags.bits())); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.recalibrate_count)); + buffer + .get_mut(12..16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.quick_recalibrate_time)); + buffer + .get_mut(16..20) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bmd.slow_recalibrate_time)); + + Ok(BMD_RETURN_SIZE_BYTES) + } + Self::BatteryGetBctResponse { bct_response } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(bct_response.into())); + + Ok(BCT_RETURN_SIZE_BYTES) + } + Self::BatteryGetBtmResponse { btm_response } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(btm_response.into())); + + Ok(BTM_RETURN_SIZE_BYTES) + } + Self::BatterySetBmsResponse { status } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(status)); + + Ok(4) + } + Self::BatterySetBmaResponse { status } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(status)); + + Ok(4) + } + Self::BatteryGetStaResponse { sta } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(sta.bits())); + + Ok(STA_RETURN_SIZE_BYTES) + } + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + BatteryCmd::from(self).into() + } +} + +/// Fuel gauge ID +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DeviceId(pub u8); + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum AcpiBatteryError { + UnknownDeviceId = 1, + UnspecifiedFailure = 2, +} + +impl SerializableMessage for AcpiBatteryError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + AcpiBatteryError::UnknownDeviceId | AcpiBatteryError::UnspecifiedFailure => Ok(0), + } + } + + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + AcpiBatteryError::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant)) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +fn safe_get_u8(buffer: &[u8], index: usize) -> Result { + buffer + .get(index) + .copied() + .ok_or(MessageSerializationError::BufferTooSmall) +} + +fn safe_get_dword(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u32::from_le_bytes(bytes)) +} diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 44d718bf9..2e12930a7 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] defmt = { workspace = true, optional = true } +battery-service-messages.workspace = true embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true @@ -27,6 +28,7 @@ mctp-rs = { workspace = true, features = ["espi"] } default = [] defmt = [ "dep:defmt", + "battery-service-messages/defmt", "embedded-services/defmt", "embassy-time/defmt", "embassy-sync/defmt", diff --git a/battery-service/src/acpi.rs b/battery-service/src/acpi.rs index c2d0dab02..910318523 100644 --- a/battery-service/src/acpi.rs +++ b/battery-service/src/acpi.rs @@ -2,89 +2,19 @@ use core::ops::Deref; use embedded_batteries_async::acpi::{PowerSourceState, PowerUnit}; -use embedded_services::{ - debug, - ec_type::message::{ - STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, - STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, StdHostMsg, StdHostRequest, - }, - ec_type::protocols::mctp, - error, info, - power::policy::PowerCapability, - trace, +use embedded_services::{info, power::policy::PowerCapability, trace}; + +use battery_service_messages::{ + AcpiBatteryResponse, BixFixedStrings, DeviceId, PifFixedStrings, STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, + STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, }; use crate::{ + AcpiBatteryError, context::PsuState, - device::{DeviceId, DynamicBatteryMsgs, StaticBatteryMsgs}, + device::{DynamicBatteryMsgs, StaticBatteryMsgs}, }; -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct Payload<'a> { - pub command: AcpiCmd, - pub status: u8, - pub data: &'a [u8], -} - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum PayloadError { - MalformedPayload, - BufTooSmall(usize), -} - -const ACPI_HEADER_SIZE: usize = 4; - -#[derive(Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum AcpiCmd { - GetBix = 1, - GetBst = 2, - GetPsr = 3, - GetPif = 4, - GetBps = 5, - SetBtp = 6, - SetBpt = 7, - GetBpc = 8, - SetBmc = 9, - GetBmd = 10, - GetBct = 11, - GetBtm = 12, - SetBms = 13, - SetBma = 14, - GetSta = 15, -} - -impl TryFrom for AcpiCmd { - type Error = PayloadError; - fn try_from(value: u8) -> Result { - match value { - 1 => Ok(AcpiCmd::GetBix), - 2 => Ok(AcpiCmd::GetBst), - 3 => Ok(AcpiCmd::GetPsr), - 4 => Ok(AcpiCmd::GetPif), - 5 => Ok(AcpiCmd::GetBps), - 6 => Ok(AcpiCmd::SetBtp), - 7 => Ok(AcpiCmd::SetBpt), - 8 => Ok(AcpiCmd::GetBpc), - 9 => Ok(AcpiCmd::SetBmc), - 10 => Ok(AcpiCmd::GetBmd), - 11 => Ok(AcpiCmd::GetBct), - 12 => Ok(AcpiCmd::GetBtm), - 13 => Ok(AcpiCmd::SetBms), - 14 => Ok(AcpiCmd::SetBma), - 15 => Ok(AcpiCmd::GetSta), - _ => Err(PayloadError::MalformedPayload), - } - } -} - -impl From for u8 { - fn from(value: AcpiCmd) -> Self { - value as u8 - } -} - pub(crate) fn compute_bst(cache: &DynamicBatteryMsgs) -> embedded_batteries_async::acpi::BstReturn { let charging = if cache.battery_status & (1 << 6) == 0 { embedded_batteries_async::acpi::BatteryState::CHARGING @@ -104,37 +34,34 @@ pub(crate) fn compute_bst(cache: &DynamicBatteryMsgs) -> embedded_batteries_asyn pub(crate) fn compute_bix<'a>( static_cache: &'a StaticBatteryMsgs, dynamic_cache: &'a DynamicBatteryMsgs, -) -> Result, ()> -{ - let mut bix_return = - mctp::BixFixedStrings:: { - revision: 1, - power_unit: if static_cache.battery_mode.capacity_mode() { - PowerUnit::MilliWatts - } else { - PowerUnit::MilliAmps - }, - design_capacity: static_cache.design_capacity_mwh, - last_full_charge_capacity: dynamic_cache.full_charge_capacity_mwh, - battery_technology: embedded_batteries_async::acpi::BatteryTechnology::Secondary, - design_voltage: static_cache.design_voltage_mv.into(), - design_cap_of_warning: static_cache.design_cap_warning, - design_cap_of_low: static_cache.design_cap_low, - cycle_count: dynamic_cache.cycle_count.into(), - measurement_accuracy: u32::from(100 - dynamic_cache.max_error_pct) * 1000u32, - max_sampling_time: static_cache.max_sample_time, - min_sampling_time: static_cache.min_sample_time, - max_averaging_interval: static_cache.max_averaging_interval, - min_averaging_interval: static_cache.min_averaging_interval, - battery_capacity_granularity_1: static_cache.cap_granularity_1, - battery_capacity_granularity_2: static_cache.cap_granularity_2, - model_number: [0u8; STD_BIX_MODEL_SIZE], - serial_number: [0u8; STD_BIX_SERIAL_SIZE], - battery_type: [0u8; STD_BIX_BATTERY_SIZE], - oem_info: [0u8; STD_BIX_OEM_SIZE], - battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability::NonSwappable, - }; - +) -> Result { + let mut bix_return = BixFixedStrings { + revision: 1, + power_unit: if static_cache.battery_mode.capacity_mode() { + PowerUnit::MilliWatts + } else { + PowerUnit::MilliAmps + }, + design_capacity: static_cache.design_capacity_mwh, + last_full_charge_capacity: dynamic_cache.full_charge_capacity_mwh, + battery_technology: embedded_batteries_async::acpi::BatteryTechnology::Secondary, + design_voltage: static_cache.design_voltage_mv.into(), + design_cap_of_warning: static_cache.design_cap_warning, + design_cap_of_low: static_cache.design_cap_low, + cycle_count: dynamic_cache.cycle_count.into(), + measurement_accuracy: u32::from(100 - dynamic_cache.max_error_pct) * 1000u32, + max_sampling_time: static_cache.max_sample_time, + min_sampling_time: static_cache.min_sample_time, + max_averaging_interval: static_cache.max_averaging_interval, + min_averaging_interval: static_cache.min_averaging_interval, + battery_capacity_granularity_1: static_cache.cap_granularity_1, + battery_capacity_granularity_2: static_cache.cap_granularity_2, + model_number: [0u8; STD_BIX_MODEL_SIZE], + serial_number: [0u8; STD_BIX_SERIAL_SIZE], + battery_type: [0u8; STD_BIX_BATTERY_SIZE], + oem_info: [0u8; STD_BIX_OEM_SIZE], + battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability::NonSwappable, + }; let model_number_len = core::cmp::min(STD_BIX_MODEL_SIZE - 1, static_cache.device_name.len() - 1); bix_return .model_number @@ -234,16 +161,14 @@ pub(crate) fn compute_psr(psu_state: &PsuState) -> embedded_batteries_async::acp } } -pub(crate) fn compute_pif( - psu_state: &PsuState, -) -> mctp::PifFixedStrings { +pub(crate) fn compute_pif(psu_state: &PsuState) -> PifFixedStrings { // TODO: Grab real values from power policy let capability = psu_state.power_capability.unwrap_or(PowerCapability { voltage_mv: 0, current_ma: 0, }); - mctp::PifFixedStrings { + PifFixedStrings { power_source_state: PowerSourceState::empty(), max_output_power: capability.max_power_mw(), max_input_power: capability.max_power_mw(), @@ -255,437 +180,221 @@ pub(crate) fn compute_pif( impl crate::context::Context { // TODO Move these to a trait - pub(super) async fn bix_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bix_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BIX command!"); - // Enough space for all string fields to have 7 bytes + 1 null terminator byte - match request.payload { - mctp::Odp::BatteryGetBixRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - let static_cache_guard = fg.get_static_battery_cache_guarded().await; - let dynamic_cache_guard = fg.get_dynamic_battery_cache_guarded().await; - request.payload = mctp::Odp::BatteryGetBixResponse { - bix: match compute_bix(static_cache_guard.deref(), dynamic_cache_guard.deref()) { - Ok(bix) => bix, - Err(()) => { - error!("Battery service: Failed to compute BIX"); - // Drop locks before next await point to eliminate possibility of deadlock - drop(static_cache_guard); - drop(dynamic_cache_guard); - - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - request, - ) - .await - .unwrap(); - debug!("response sent to espi_service"); - return; - } - }, - }; - // Drop locks before next await point to eliminate possibility of deadlock - drop(static_cache_guard); - drop(dynamic_cache_guard); - - request.status = 0; - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - - debug!("response sent to espi_service"); - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - } - } - _ => error!("Battery service: command and body mismatch!"), - } + + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + let static_cache_guard = fg.get_static_battery_cache_guarded().await; + let dynamic_cache_guard = fg.get_dynamic_battery_cache_guarded().await; + + Ok(AcpiBatteryResponse::BatteryGetBixResponse { + bix: compute_bix(static_cache_guard.deref(), dynamic_cache_guard.deref()) + .map_err(|_| AcpiBatteryError::UnspecifiedFailure)?, + }) } - pub(super) async fn bst_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bst_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BST command!"); - match request.payload { - mctp::Odp::BatteryGetBstRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetBstResponse { - bst: compute_bst(&fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - - trace!("response sent to espi_service"); + + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetBstResponse { + bst: compute_bst(&fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn psr_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn psr_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got PSR command!"); - match request.payload { - mctp::Odp::BatteryGetPsrRequest { battery_id } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetPsrResponse { - psr: compute_psr(&self.get_power_info().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - trace!("response sent to espi_service"); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetPsrResponse { + psr: compute_psr(&self.get_power_info().await), + }) } - pub(super) async fn pif_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn pif_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got PIF command!"); - match request.payload { - mctp::Odp::BatteryGetPifRequest { battery_id } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetPifResponse { - pif: compute_pif(&self.get_power_info().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); - trace!("response sent to espi_service"); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetPifResponse { + pif: compute_pif(&self.get_power_info().await), + }) } - pub(super) async fn bps_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bps_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BPS command!"); - match request.payload { - mctp::Odp::BatteryGetBpsRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetBpsResponse { - bps: compute_bps(&fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetBpsResponse { + bps: compute_bps(&fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn btp_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn btp_handler( + &self, + device_id: DeviceId, + btp: embedded_batteries_async::acpi::Btp, + ) -> Result { trace!("Battery service: got BTP command!"); - match request.payload { - mctp::Odp::BatterySetBtpRequest { battery_id, btp } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - // TODO: Save trip point - info!("Battery service: New BTP {}", btp.trip_point); - request.payload = mctp::Odp::BatterySetBtpResponse {}; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + // TODO: Save trip point + info!("Battery service: New BTP {}", btp.trip_point); + + Ok(AcpiBatteryResponse::BatterySetBtpResponse {}) } - pub(super) async fn bpt_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bpt_handler( + &self, + device_id: DeviceId, + bpt: embedded_batteries_async::acpi::Bpt, + ) -> Result { trace!("Battery service: got BPT command!"); - match request.payload { - mctp::Odp::BatterySetBptRequest { battery_id, bpt } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!( - "Battery service: Threshold ID: {:?}, Threshold value: {:?}", - bpt.threshold_id as u32, bpt.threshold_value - ); - request.payload = mctp::Odp::BatterySetBptResponse {}; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!( + "Battery service: Threshold ID: {:?}, Threshold value: {:?}", + bpt.threshold_id as u32, bpt.threshold_value + ); + + Ok(AcpiBatteryResponse::BatterySetBptResponse {}) } - pub(super) async fn bpc_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bpc_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BPC command!"); - match request.payload { - mctp::Odp::BatteryGetBpcRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - // TODO: Save trip point - request.payload = mctp::Odp::BatteryGetBpcResponse { - bpc: compute_bpc(&fg.get_static_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + // TODO: Save trip point + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetBpcResponse { + bpc: compute_bpc(&fg.get_static_battery_cache().await), + }) } - pub(super) async fn bmc_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bmc_handler( + &self, + device_id: DeviceId, + bmc: embedded_batteries_async::acpi::Bmc, + ) -> Result { trace!("Battery service: got BMC command!"); - match request.payload { - mctp::Odp::BatterySetBmcRequest { battery_id, bmc } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Battery service: Bmc {}", bmc.maintenance_control_flags.bits()); - request.payload = mctp::Odp::BatterySetBmcResponse {}; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Battery service: Bmc {}", bmc.maintenance_control_flags.bits()); + + Ok(AcpiBatteryResponse::BatterySetBmcResponse {}) } - pub(super) async fn bmd_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bmd_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BMD command!"); - match request.payload { - mctp::Odp::BatteryGetBmdRequest { battery_id } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - let static_cache = fg.get_static_battery_cache().await; - let dynamic_cache = fg.get_dynamic_battery_cache().await; - request.payload = mctp::Odp::BatteryGetBmdResponse { - bmd: compute_bmd(&static_cache, &dynamic_cache), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + let static_cache = fg.get_static_battery_cache().await; + let dynamic_cache = fg.get_dynamic_battery_cache().await; + + Ok(AcpiBatteryResponse::BatteryGetBmdResponse { + bmd: compute_bmd(&static_cache, &dynamic_cache), + }) } - pub(super) async fn bct_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bct_handler( + &self, + device_id: DeviceId, + bct: embedded_batteries_async::acpi::Bct, + ) -> Result { trace!("Battery service: got BCT command!"); - match request.payload { - mctp::Odp::BatteryGetBctRequest { battery_id, bct } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BCT charge_level_percent: {}", bct.charge_level_percent); - request.payload = mctp::Odp::BatteryGetBctResponse { - bct_response: compute_bct(&bct, &fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BCT charge_level_percent: {}", bct.charge_level_percent); + Ok(AcpiBatteryResponse::BatteryGetBctResponse { + bct_response: compute_bct(&bct, &fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn btm_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn btm_handler( + &self, + device_id: DeviceId, + btm: embedded_batteries_async::acpi::Btm, + ) -> Result { trace!("Battery service: got BTM command!"); - match request.payload { - mctp::Odp::BatteryGetBtmRequest { battery_id, btm } => { - if let Some(fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BTM discharge_rate: {}", btm.discharge_rate); - request.payload = mctp::Odp::BatteryGetBtmResponse { - btm_response: compute_btm(&btm, &fg.get_dynamic_battery_cache().await), - }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BTM discharge_rate: {}", btm.discharge_rate); + Ok(AcpiBatteryResponse::BatteryGetBtmResponse { + btm_response: compute_btm(&btm, &fg.get_dynamic_battery_cache().await), + }) } - pub(super) async fn bms_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bms_handler( + &self, + device_id: DeviceId, + bms: embedded_batteries_async::acpi::Bms, + ) -> Result { trace!("Battery service: got BMS command!"); - match request.payload { - mctp::Odp::BatterySetBmsRequest { battery_id, bms } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BMS sampling_time: {}", bms.sampling_time_ms); - request.payload = mctp::Odp::BatterySetBmsResponse { status: 0 }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::BatterySetBmsResponse { status: 1 }; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BMS sampling_time: {}", bms.sampling_time_ms); + Ok(AcpiBatteryResponse::BatterySetBmsResponse { status: 0 }) } - pub(super) async fn bma_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn bma_handler( + &self, + device_id: DeviceId, + bma: embedded_batteries_async::acpi::Bma, + ) -> Result { trace!("Battery service: got BMA command!"); - match request.payload { - mctp::Odp::BatterySetBmaRequest { battery_id, bma } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - info!("Recvd BMA averaging_interval_ms: {}", bma.averaging_interval_ms); - request.payload = mctp::Odp::BatterySetBmaResponse { status: 0 }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::BatterySetBmaResponse { status: 1 }; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + info!("Recvd BMA averaging_interval_ms: {}", bma.averaging_interval_ms); + Ok(AcpiBatteryResponse::BatterySetBmaResponse { status: 0 }) } - pub(super) async fn sta_handler(&self, request: &mut StdHostRequest) { + pub(super) async fn sta_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got STA command!"); - match request.payload { - mctp::Odp::BatteryGetStaRequest { battery_id } => { - if let Some(_fg) = self.get_fuel_gauge(DeviceId(battery_id)) { - request.payload = mctp::Odp::BatteryGetStaResponse { sta: compute_sta() }; - request.status = 0; - } else { - error!("Battery service: FG not found when trying to process ACPI cmd!"); - request.status = 1; - request.payload = mctp::Odp::ErrorResponse {}; - } - } - _ => error!("Battery service: command and body mismatch!"), - } - - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &StdHostMsg::Response(*request), - ) - .await - .unwrap(); + let _fg = self + .get_fuel_gauge(device_id) + .ok_or(AcpiBatteryError::UnknownDeviceId)?; + + Ok(AcpiBatteryResponse::BatteryGetStaResponse { sta: compute_sta() }) } } diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index 48764f60a..73edb63d0 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -1,5 +1,7 @@ -use crate::device::{self, DeviceId}; +use crate::AcpiBatteryError; +use crate::device::{self}; use crate::device::{Device, FuelGaugeError}; +use battery_service_messages::{AcpiBatteryRequest, AcpiBatteryResponse, DeviceId}; use embassy_sync::channel::Channel; use embassy_sync::channel::TrySendError; use embassy_sync::mutex::Mutex; @@ -7,8 +9,6 @@ use embassy_sync::signal::Signal; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::comms::MailboxDelegateError; -use embedded_services::ec_type::message::StdHostRequest; -use embedded_services::ec_type::protocols::acpi::BatteryCmd; use embedded_services::power::policy::PowerCapability; use embedded_services::{IntrusiveList, debug, error, info, intrusive_list, trace, warn}; @@ -138,7 +138,7 @@ pub struct Context { battery_response: Channel, no_op_retry_count: AtomicUsize, config: Config, - acpi_request: Signal, + acpi_request: Signal, power_info: Mutex, } @@ -162,8 +162,6 @@ impl Default for Config { } } -embedded_services::define_static_buffer!(acpi_buf, u8, [0u8; 133]); - impl Context { /// Create a new context instance. pub fn new() -> Self { @@ -404,27 +402,51 @@ impl Context { } } - pub(super) async fn process_acpi_cmd(&self, acpi_msg: &mut StdHostRequest) { - match acpi_msg.command { - embedded_services::ec_type::message::OdpCommand::Battery(cmd) => match cmd { - BatteryCmd::GetBix => self.bix_handler(acpi_msg).await, - BatteryCmd::GetBst => self.bst_handler(acpi_msg).await, - BatteryCmd::GetPsr => self.psr_handler(acpi_msg).await, - BatteryCmd::GetPif => self.pif_handler(acpi_msg).await, - BatteryCmd::GetBps => self.bps_handler(acpi_msg).await, - BatteryCmd::SetBtp => self.btp_handler(acpi_msg).await, - BatteryCmd::SetBpt => self.bpt_handler(acpi_msg).await, - BatteryCmd::GetBpc => self.bpc_handler(acpi_msg).await, - BatteryCmd::SetBmc => self.bmc_handler(acpi_msg).await, - BatteryCmd::GetBmd => self.bmd_handler(acpi_msg).await, - BatteryCmd::GetBct => self.bct_handler(acpi_msg).await, - BatteryCmd::GetBtm => self.btm_handler(acpi_msg).await, - BatteryCmd::SetBms => self.bms_handler(acpi_msg).await, - BatteryCmd::SetBma => self.bma_handler(acpi_msg).await, - BatteryCmd::GetSta => self.sta_handler(acpi_msg).await, - }, - _ => error!("Battery service: host command not found!"), + pub(super) async fn process_acpi_cmd(&self, acpi_msg: &AcpiBatteryRequest) { + let response: Result = match *acpi_msg { + AcpiBatteryRequest::BatteryGetBixRequest { battery_id } => self.bix_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetBstRequest { battery_id } => self.bst_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetPsrRequest { battery_id } => self.psr_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetPifRequest { battery_id } => self.pif_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetBpsRequest { battery_id } => self.bps_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatterySetBtpRequest { battery_id, btp } => { + self.btp_handler(DeviceId(battery_id), btp).await + } + AcpiBatteryRequest::BatterySetBptRequest { battery_id, bpt } => { + self.bpt_handler(DeviceId(battery_id), bpt).await + } + AcpiBatteryRequest::BatteryGetBpcRequest { battery_id } => self.bpc_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatterySetBmcRequest { battery_id, bmc } => { + self.bmc_handler(DeviceId(battery_id), bmc).await + } + AcpiBatteryRequest::BatteryGetBmdRequest { battery_id } => self.bmd_handler(DeviceId(battery_id)).await, + AcpiBatteryRequest::BatteryGetBctRequest { battery_id, bct } => { + self.bct_handler(DeviceId(battery_id), bct).await + } + AcpiBatteryRequest::BatteryGetBtmRequest { battery_id, btm } => { + self.btm_handler(DeviceId(battery_id), btm).await + } + + AcpiBatteryRequest::BatterySetBmsRequest { battery_id, bms } => { + self.bms_handler(DeviceId(battery_id), bms).await + } + AcpiBatteryRequest::BatterySetBmaRequest { battery_id, bma } => { + self.bma_handler(DeviceId(battery_id), bma).await + } + AcpiBatteryRequest::BatteryGetStaRequest { battery_id } => self.sta_handler(DeviceId(battery_id)).await, + }; + + if let Err(e) = response { + error!("Battery service command failed: {:?}", e); } + + // TODO We should probably be responding to the requestor rather than just assuming the request came from the host + super::comms_send( + crate::EndpointID::External(embedded_services::comms::External::Host), + &response, + ) + .await + .expect("comms_send is infallible"); } pub(crate) fn get_fuel_gauge(&self, id: DeviceId) -> Option<&'static Device> { @@ -472,11 +494,11 @@ impl Context { self.battery_event.receive().await } - pub(super) fn send_acpi_cmd(&self, raw: StdHostRequest) { - self.acpi_request.signal(raw); + pub(super) fn send_acpi_cmd(&self, request: AcpiBatteryRequest) { + self.acpi_request.signal(request); } - pub(super) async fn wait_acpi_cmd(&self) -> StdHostRequest { + pub(super) async fn wait_acpi_cmd(&self) -> AcpiBatteryRequest { self.acpi_request.wait().await } diff --git a/battery-service/src/device.rs b/battery-service/src/device.rs index 6cf314f6b..3c0f73785 100644 --- a/battery-service/src/device.rs +++ b/battery-service/src/device.rs @@ -9,6 +9,8 @@ use embedded_batteries_async::{ }; use embedded_services::{GlobalRawMutex, Node, NodeContainer, SyncCell}; +pub use battery_service_messages::DeviceId; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Device errors. @@ -155,11 +157,6 @@ pub struct DynamicBatteryMsgs { pub bmd_status: BmdStatusFlags, } -/// Fuel gauge ID -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - /// Hardware agnostic device object to be registered with context. pub struct Device { node: embedded_services::Node, diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index c6554076c..811d48c29 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -2,11 +2,11 @@ use core::{any::Any, convert::Infallible}; +use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest}; use context::BatteryEvent; use embassy_futures::select::select; use embedded_services::{ comms::{self, EndpointID}, - ec_type::message::StdHostRequest, trace, }; @@ -62,9 +62,9 @@ impl Service { trace!("Battery service: state machine event recvd {:?}", event); self.context.process(event).await } - Event::AcpiRequest(mut acpi_msg) => { + Event::AcpiRequest(acpi_msg) => { trace!("Battery service: ACPI cmd recvd"); - self.context.process_acpi_cmd(&mut acpi_msg).await + self.context.process_acpi_cmd(&acpi_msg).await } } } @@ -74,7 +74,7 @@ impl Service { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Event { StateMachine(BatteryEvent), - AcpiRequest(StdHostRequest), + AcpiRequest(AcpiBatteryRequest), } impl Default for Service { @@ -89,8 +89,8 @@ impl comms::MailboxDelegate for Service { self.context.send_event_no_wait(*event).map_err(|e| match e { embassy_sync::channel::TrySendError::Full(_) => comms::MailboxDelegateError::BufferFull, })? - } else if let Some(acpi_cmd) = message.data.get::() { - self.context.send_acpi_cmd(*acpi_cmd); + } else if let Some(battery_request) = message.data.get::() { + self.context.send_acpi_cmd(*battery_request); } else if let Some(power_policy_msg) = message.data.get::() { self.context.set_power_info(&power_policy_msg.data)?; } diff --git a/debug-service-messages/Cargo.toml b/debug-service-messages/Cargo.toml new file mode 100644 index 000000000..5127a7579 --- /dev/null +++ b/debug-service-messages/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "debug-service-messages" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embedded-services.workspace = true +num_enum.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt"] diff --git a/debug-service-messages/src/lib.rs b/debug-service-messages/src/lib.rs new file mode 100644 index 000000000..c8b1262ee --- /dev/null +++ b/debug-service-messages/src/lib.rs @@ -0,0 +1,115 @@ +#![no_std] +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// Standard Debug Service Log Buffer Size +pub const STD_DEBUG_BUF_SIZE: usize = 128; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// ODP Specific Debug Commands +enum DebugCmd { + /// Get buffer of debug messages, if available. + /// Can be used to poll debug messages. + GetMsgs = 1, +} + +impl From<&DebugRequest> for DebugCmd { + fn from(request: &DebugRequest) -> Self { + match request { + DebugRequest::DebugGetMsgsRequest => DebugCmd::GetMsgs, + } + } +} + +impl From<&DebugResponse> for DebugCmd { + fn from(response: &DebugResponse) -> Self { + match response { + DebugResponse::DebugGetMsgsResponse { .. } => DebugCmd::GetMsgs, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DebugRequest { + DebugGetMsgsRequest, +} + +impl SerializableMessage for DebugRequest { + fn serialize(self, _buffer: &mut [u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + Ok( + match DebugCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + DebugCmd::GetMsgs => Self::DebugGetMsgsRequest, + }, + ) + } + + fn discriminant(&self) -> u16 { + let cmd: DebugCmd = self.into(); + cmd.into() + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DebugResponse { + DebugGetMsgsResponse { debug_buf: [u8; STD_DEBUG_BUF_SIZE] }, +} + +impl SerializableMessage for DebugResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::DebugGetMsgsResponse { debug_buf } => { + buffer + .get_mut(..debug_buf.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&debug_buf); + Ok(debug_buf.len()) + } + } + } + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + DebugCmd::from(self).into() + } +} + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum DebugError { + UnspecifiedFailure = 1, +} + +impl SerializableMessage for DebugError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + Self::UnspecifiedFailure => Ok(0), + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +pub type DebugResult = Result; diff --git a/debug-service/Cargo.toml b/debug-service/Cargo.toml index 799499ca1..1e27eda1d 100644 --- a/debug-service/Cargo.toml +++ b/debug-service/Cargo.toml @@ -9,11 +9,15 @@ repository.workspace = true bbq2 = "0.4.2" critical-section.workspace = true defmt.workspace = true +debug-service-messages.workspace = true embassy-sync.workspace = true embassy-time.workspace = true embedded-services.workspace = true log.workspace = true rtt-target = "0.6.1" +[features] +defmt = ["debug-service-messages/defmt"] + [lints] workspace = true diff --git a/debug-service/src/debug_service.rs b/debug-service/src/debug_service.rs index 134fed490..1626ca6bd 100644 --- a/debug-service/src/debug_service.rs +++ b/debug-service/src/debug_service.rs @@ -2,7 +2,6 @@ use embassy_sync::{once_lock::OnceLock, signal::Signal}; use embedded_services::GlobalRawMutex; use embedded_services::buffer::{OwnedRef, SharedRef}; use embedded_services::comms::{self, EndpointID, Internal}; -use embedded_services::ec_type::message::StdHostRequest; use embedded_services::{debug, error}; // Maximum number of bytes to request per defmt frame write grant. @@ -15,7 +14,7 @@ use embedded_services::{debug, error}; // - No partial publication: a frame is either not yet committed or fully committed. // If a defmt log event were to exceed this size, it will be split across multiple // BBQueue frames (each ≤ 1024). The consumer always observes complete frames. -pub(crate) const DEFMT_MAX_BYTES: u16 = embedded_services::ec_type::message::STD_DEBUG_BUF_SIZE as u16; +pub(crate) const DEFMT_MAX_BYTES: u16 = debug_service_messages::STD_DEBUG_BUF_SIZE as u16; // Static buffer for ACPI-style messages carrying defmt frames embedded_services::define_static_buffer!(defmt_acpi_buf, u8, [0u8; DEFMT_MAX_BYTES as usize]); @@ -69,7 +68,7 @@ impl Service { impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(_request) = message.data.get::() { + if let Some(_request) = message.data.get::() { // Host sent an ACPI/MCTP request (e.g. GetDebugBuffer). Treat this as the // trigger to send the staged debug buffer back to the host. embedded_services::trace!("Received host ACPI request for debug buffer from {:?}", message.from); diff --git a/debug-service/src/task.rs b/debug-service/src/task.rs index 757c31bbe..5d973a4ac 100644 --- a/debug-service/src/task.rs +++ b/debug-service/src/task.rs @@ -1,9 +1,7 @@ use core::borrow::{Borrow, BorrowMut}; -use embedded_services::{ - comms, - ec_type::message::{StdHostPayload, StdHostRequest}, -}; +use debug_service_messages::{DebugError, DebugResponse}; +use embedded_services::comms; use crate::{debug_service_entry, defmt_ring_logger::DEFMT_BUFFER, frame_available, shared_buffer}; @@ -20,7 +18,6 @@ pub async fn defmt_to_host_task() -> Result { embedded_services::info!("defmt to host task start"); use crate::debug_service::{host_endpoint_id, response_notify_signal}; use embedded_services::comms::{self, EndpointID, Internal}; - use embedded_services::ec_type::message::HostMsg; let framed_consumer = DEFMT_BUFFER.framed_consumer(); @@ -67,19 +64,13 @@ pub async fn defmt_to_host_task() -> Result { // Send the staged defmt bytes frame as an ACPI-style message. // Scope the message so the shared borrow is dropped before we clear the buffer. { - let msg = HostMsg::Response(StdHostRequest { - command: embedded_services::ec_type::message::OdpCommand::Debug( - embedded_services::ec_type::protocols::debug::DebugCmd::GetMsgs, - ), - status: 0, - payload: StdHostPayload::DebugGetMsgsResponse { - debug_buf: { - let access = shared_buffer().borrow().map_err(Error::Buffer)?; - let slice: &[u8] = access.borrow(); - slice.try_into().unwrap() - }, + let msg = DebugResponse::DebugGetMsgsResponse { + debug_buf: { + let access = shared_buffer().borrow().map_err(Error::Buffer)?; + let slice: &[u8] = access.borrow(); + slice.try_into().unwrap() }, - }); + }; let _ = comms::send(EndpointID::Internal(Internal::Debug), host_ep, &msg).await; embedded_services::trace!("sent {} defmt bytes to host", copy_len); } @@ -99,7 +90,6 @@ pub async fn no_avail_to_host_task() -> Result embedded_services::info!("no avail to host task start"); use crate::debug_service::{host_endpoint_id, no_avail_notify_signal}; use embedded_services::comms::{self, EndpointID, Internal}; - use embedded_services::ec_type::message::HostMsg; let host_ep = host_endpoint_id().await; @@ -111,13 +101,7 @@ pub async fn no_avail_to_host_task() -> Result buf[4..12].copy_from_slice(&0xDEADBEEFu64.to_be_bytes()); } - let msg = HostMsg::Response(StdHostRequest { - command: embedded_services::ec_type::message::OdpCommand::Debug( - embedded_services::ec_type::protocols::debug::DebugCmd::GetMsgs, - ), - status: 1, - payload: StdHostPayload::ErrorResponse {}, - }); + let msg: Result = Err(DebugError::UnspecifiedFailure); // Send DEADBEEF if host requests frame but non available loop { diff --git a/embedded-service/src/ec_type/message.rs b/embedded-service/src/ec_type/message.rs index 3aa6be584..f31f178a9 100644 --- a/embedded-service/src/ec_type/message.rs +++ b/embedded-service/src/ec_type/message.rs @@ -1,7 +1,5 @@ //! EC Internal Messages -use crate::ec_type::protocols::{acpi, debug, mctp::OdpCommandCode, mptf}; - #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] pub enum CapabilitiesMessage { @@ -68,26 +66,6 @@ pub enum BatteryMessage { SampleTime(u32), } -/// ACPI Message, compatible with comms system -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct HostRequest { - /// Command - pub command: Command, - /// Status code - pub status: u8, - /// Data payload - pub payload: Payload, -} - -/// Notification type to be sent to Host -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct NotificationMsg { - /// Interrupt offset - pub offset: u8, -} - #[allow(missing_docs)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum ThermalMessage { @@ -108,163 +86,3 @@ pub enum ThermalMessage { Tmp1Low(u32), Tmp1High(u32), } - -/// Message type that services can send to communicate with the Host. -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum HostMsg { - /// Notification without data. After receivng a notification, - /// typically the host will request some data from the EC - Notification(NotificationMsg), - /// Response to Host request. - Response(HostRequest), -} - -/// ODP specific command code that can come in from the host. -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum OdpCommand { - /// Battery commands - Battery(acpi::BatteryCmd), - /// Thermal commands - Thermal(mptf::ThermalCmd), - /// Debug commands - Debug(debug::DebugCmd), -} - -/// Standard Battery Service Model Number String Size -pub const STD_BIX_MODEL_SIZE: usize = 8; -/// Standard Battery Service Serial Number String Size -pub const STD_BIX_SERIAL_SIZE: usize = 8; -/// Standard Battery Service Battery Type String Size -pub const STD_BIX_BATTERY_SIZE: usize = 8; -/// Standard Battery Service OEM Info String Size -pub const STD_BIX_OEM_SIZE: usize = 8; -/// Standard Power Policy Service Model Number String Size -pub const STD_PIF_MODEL_SIZE: usize = 8; -/// Standard Power Policy Serial Number String Size -pub const STD_PIF_SERIAL_SIZE: usize = 8; -/// Standard Power Policy Service OEM Info String Size -pub const STD_PIF_OEM_SIZE: usize = 8; -/// Standard Debug Service Log Buffer Size -pub const STD_DEBUG_BUF_SIZE: usize = 128; - -/// Standard ODP Host Payload -pub type StdHostPayload = crate::ec_type::protocols::mctp::Odp< - STD_BIX_MODEL_SIZE, - STD_BIX_SERIAL_SIZE, - STD_BIX_BATTERY_SIZE, - STD_BIX_OEM_SIZE, - STD_PIF_MODEL_SIZE, - STD_PIF_SERIAL_SIZE, - STD_PIF_OEM_SIZE, - STD_DEBUG_BUF_SIZE, ->; - -/// Standard Host Request -pub type StdHostRequest = HostRequest; -/// Standard Host Message -pub type StdHostMsg = HostMsg; - -impl From for OdpCommand { - fn from(value: OdpCommandCode) -> Self { - match value { - OdpCommandCode::BatteryGetBixRequest | OdpCommandCode::BatteryGetBixResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBix) - } - OdpCommandCode::BatteryGetBstRequest | OdpCommandCode::BatteryGetBstResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBst) - } - OdpCommandCode::BatteryGetPsrRequest | OdpCommandCode::BatteryGetPsrResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetPsr) - } - OdpCommandCode::BatteryGetPifRequest | OdpCommandCode::BatteryGetPifResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetPif) - } - OdpCommandCode::BatteryGetBpsRequest | OdpCommandCode::BatteryGetBpsResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBps) - } - OdpCommandCode::BatterySetBtpRequest | OdpCommandCode::BatterySetBtpResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBtp) - } - OdpCommandCode::BatterySetBptRequest | OdpCommandCode::BatterySetBptResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBpt) - } - OdpCommandCode::BatteryGetBpcRequest | OdpCommandCode::BatteryGetBpcResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBpc) - } - OdpCommandCode::BatterySetBmcRequest | OdpCommandCode::BatterySetBmcResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBmc) - } - OdpCommandCode::BatteryGetBmdRequest | OdpCommandCode::BatteryGetBmdResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBmd) - } - OdpCommandCode::BatteryGetBctRequest | OdpCommandCode::BatteryGetBctResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBct) - } - OdpCommandCode::BatteryGetBtmRequest | OdpCommandCode::BatteryGetBtmResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetBtm) - } - OdpCommandCode::BatterySetBmsRequest | OdpCommandCode::BatterySetBmsResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBms) - } - OdpCommandCode::BatterySetBmaRequest | OdpCommandCode::BatterySetBmaResponse => { - OdpCommand::Battery(acpi::BatteryCmd::SetBma) - } - OdpCommandCode::BatteryGetStaRequest | OdpCommandCode::BatteryGetStaResponse => { - OdpCommand::Battery(acpi::BatteryCmd::GetSta) - } - OdpCommandCode::ThermalGetTmpRequest | OdpCommandCode::ThermalGetTmpResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::GetTmp) - } - OdpCommandCode::ThermalSetThrsRequest | OdpCommandCode::ThermalSetThrsResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::SetThrs) - } - OdpCommandCode::ThermalGetThrsRequest | OdpCommandCode::ThermalGetThrsResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::GetThrs) - } - OdpCommandCode::ThermalSetScpRequest | OdpCommandCode::ThermalSetScpResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::SetScp) - } - OdpCommandCode::ThermalGetVarRequest | OdpCommandCode::ThermalGetVarResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::GetVar) - } - OdpCommandCode::ThermalSetVarRequest | OdpCommandCode::ThermalSetVarResponse => { - OdpCommand::Thermal(mptf::ThermalCmd::SetVar) - } - OdpCommandCode::DebugGetMsgsRequest | OdpCommandCode::DebugGetMsgsResponse => { - OdpCommand::Debug(debug::DebugCmd::GetMsgs) - } - } - } -} - -// TODO: Maybe map to Response instead? -impl From for OdpCommandCode { - fn from(value: OdpCommand) -> Self { - match value { - OdpCommand::Battery(acpi::BatteryCmd::GetBix) => OdpCommandCode::BatteryGetBixRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBst) => OdpCommandCode::BatteryGetBstRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetPsr) => OdpCommandCode::BatteryGetPsrRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetPif) => OdpCommandCode::BatteryGetPifRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBps) => OdpCommandCode::BatteryGetBpsRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBtp) => OdpCommandCode::BatterySetBtpRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBpt) => OdpCommandCode::BatterySetBptRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBpc) => OdpCommandCode::BatteryGetBpcRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBmc) => OdpCommandCode::BatterySetBmcRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBmd) => OdpCommandCode::BatteryGetBmdRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBct) => OdpCommandCode::BatteryGetBctRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetBtm) => OdpCommandCode::BatteryGetBtmRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBms) => OdpCommandCode::BatterySetBmsRequest, - OdpCommand::Battery(acpi::BatteryCmd::SetBma) => OdpCommandCode::BatterySetBmaRequest, - OdpCommand::Battery(acpi::BatteryCmd::GetSta) => OdpCommandCode::BatteryGetStaRequest, - OdpCommand::Thermal(mptf::ThermalCmd::GetTmp) => OdpCommandCode::ThermalGetTmpRequest, - OdpCommand::Thermal(mptf::ThermalCmd::SetThrs) => OdpCommandCode::ThermalSetThrsRequest, - OdpCommand::Thermal(mptf::ThermalCmd::GetThrs) => OdpCommandCode::ThermalGetThrsRequest, - OdpCommand::Thermal(mptf::ThermalCmd::SetScp) => OdpCommandCode::ThermalSetScpRequest, - OdpCommand::Thermal(mptf::ThermalCmd::GetVar) => OdpCommandCode::ThermalGetVarRequest, - OdpCommand::Thermal(mptf::ThermalCmd::SetVar) => OdpCommandCode::ThermalSetVarRequest, - OdpCommand::Debug(debug::DebugCmd::GetMsgs) => OdpCommandCode::DebugGetMsgsRequest, - } - } -} diff --git a/embedded-service/src/ec_type/mod.rs b/embedded-service/src/ec_type/mod.rs index 970afd5a2..c74b13a2a 100644 --- a/embedded-service/src/ec_type/mod.rs +++ b/embedded-service/src/ec_type/mod.rs @@ -2,7 +2,6 @@ use core::mem::offset_of; pub mod message; -pub mod protocols; pub mod structure; /// Error type diff --git a/embedded-service/src/ec_type/protocols/acpi.rs b/embedded-service/src/ec_type/protocols/acpi.rs deleted file mode 100644 index fc234e0f9..000000000 --- a/embedded-service/src/ec_type/protocols/acpi.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// ACPI Battery Methods -pub enum BatteryCmd { - /// Battery Information eXtended - GetBix = 1, - /// Battery Status - GetBst = 2, - /// Power Source - GetPsr = 3, - /// Power source InFormation - GetPif = 4, - /// Battery Power State - GetBps = 5, - /// Battery Trip Point - SetBtp = 6, - /// Battery Power Threshold - SetBpt = 7, - /// Battery Power Characteristics - GetBpc = 8, - /// Battery Maintenance Control - SetBmc = 9, - /// Battery Maintenance Data - GetBmd = 10, - /// Battery Charge Time - GetBct = 11, - /// Battery Time - GetBtm = 12, - /// Battery Measurement Sampling Time - SetBms = 13, - /// Battery Measurement Averaging Interval - SetBma = 14, - /// Device Status - GetSta = 15, -} diff --git a/embedded-service/src/ec_type/protocols/debug.rs b/embedded-service/src/ec_type/protocols/debug.rs deleted file mode 100644 index cf299f6af..000000000 --- a/embedded-service/src/ec_type/protocols/debug.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// ODP Specific Debug Commands -pub enum DebugCmd { - /// Get buffer of debug messages, if available. - /// Can be used to poll debug messages. - GetMsgs = 1, -} diff --git a/embedded-service/src/ec_type/protocols/mctp.rs b/embedded-service/src/ec_type/protocols/mctp.rs deleted file mode 100644 index 3f71f4cc8..000000000 --- a/embedded-service/src/ec_type/protocols/mctp.rs +++ /dev/null @@ -1,1367 +0,0 @@ -#![allow(missing_docs)] - -use core::ops::{Div, Mul}; - -use embedded_batteries_async::acpi::{ - BCT_RETURN_SIZE_BYTES, BMD_RETURN_SIZE_BYTES, BPC_RETURN_SIZE_BYTES, BPS_RETURN_SIZE_BYTES, BST_RETURN_SIZE_BYTES, - BTM_RETURN_SIZE_BYTES, BatteryState, BmdCapabilityFlags, BmdStatusFlags, PSR_RETURN_SIZE_BYTES, PowerSourceState, - PowerThresholdSupport, PsrReturn, STA_RETURN_SIZE_BYTES, -}; - -use mctp_rs::{ - MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, error::MctpPacketResult, - mctp_completion_code::MctpCompletionCode, -}; - -/// Append an MCTP header to the front of a message. -/// Returns the message and its new total with the appended header. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum MctpError { - /// Header is not at least 9 bytes long. - InvalidHeaderSize, - /// Wrong destination address. - WrongDestinationAddr, - /// Invalid command code. - InvalidCommandCode, - /// Invalid byte count, encoded byte count does not match MCTP message length. - InvalidByteCount, - /// Invalid header version. Should be 1. - InvalidHeaderVersion, - /// Invalid destination endpoint - InvalidDestinationEndpoint, - /// Invalid source endpoint. - InvalidSourceEndpoint, - /// Multi message not supported. - InvalidFlags, -} - -/// Data type for MCTP message underlying data size. -pub type PayloadLen = usize; - -/// Max payload len, due to SMBUS block transaction limits. -pub const MAX_MCTP_BYTE_COUNT: usize = 69; -/// Payload len + bytes for destination target address, command code, and byte count. -pub const MAX_MCTP_PACKET_LEN: usize = MAX_MCTP_BYTE_COUNT + 3; - -fn round_up_to_nearest_mod_4(unrounded: usize) -> usize { - unrounded + (unrounded % 4) -} - -/// Decode a header from and MCTP message. -/// Returns the underlying data and its service endpoint ID and the underlying data size. -pub fn handle_mctp_header( - mctp_msg: &[u8], - data: &mut [u8], -) -> Result<(crate::comms::EndpointID, PayloadLen), MctpError> { - // assert we have at least 9 bytes, minimum - if mctp_msg.len() < 9 { - return Err(MctpError::InvalidHeaderSize); - } - - // EC is at address 2, if we have anything other than 2 reject it. - if mctp_msg[0] != 2 { - return Err(MctpError::WrongDestinationAddr); - } - - // MCTP command code is 0x0F. - if mctp_msg[1] != 0x0F { - return Err(MctpError::InvalidCommandCode); - } - - // Check the byte count is correctly formed and is not larger than the max in the spec. - if usize::from(mctp_msg[2]) > MAX_MCTP_BYTE_COUNT { - return Err(MctpError::InvalidByteCount); - } - // Some eSPI controllers behave oddly if packet sizes aren't multiples of 4, so the MCTP message is padded - // to multiples of 4. - // Byte size + header size (3) + padding to align size to multiple of 4 should equal length of message. - // Unfortunately since padding is variable, there is no way to validate byte count is truly correct. - // There is a chance that the number of valid bytes exceeds the byte count if mctp_msg.len() - // is not a multiple of 4 (and thus has padding bytes) - if ((usize::from(mctp_msg[2]) + 3) + 3).div(4).mul(4) != mctp_msg.len() { - return Err(MctpError::InvalidByteCount); - } - - // Only support header version 1. - if mctp_msg[4] != 1 { - return Err(MctpError::InvalidHeaderVersion); - } - - // Subsystems supported currently are battery (0x02), thermal (0x03), and debug (0x04). - let endpoint_id = match mctp_msg[5] { - 2 => crate::comms::EndpointID::Internal(crate::comms::Internal::Battery), - 3 => crate::comms::EndpointID::Internal(crate::comms::Internal::Thermal), - 4 => crate::comms::EndpointID::Internal(crate::comms::Internal::Debug), - _ => return Err(MctpError::InvalidDestinationEndpoint), - }; - - // Only source endpoint supported currently is host (1). - if mctp_msg[6] != 1 { - return Err(MctpError::InvalidSourceEndpoint); - } - - let som = mctp_msg[7] & (1 << 7) != 0; - let eom = mctp_msg[7] & (1 << 6) != 0; - let seq_num = (mctp_msg[7] & 0b0011_0000) >> 4; - let msg_tag = mctp_msg[7] & 0b0000_0111; - - // Verify flags - if !som || !eom || seq_num != 1 || msg_tag != 3 { - return Err(MctpError::InvalidFlags); - } - - let len = usize::from(mctp_msg[2]) - 5; - // Copy message contents without the padding to a multiple of 4 at the end. - data[..len].copy_from_slice(&mctp_msg[8..8 + len]); - - Ok((endpoint_id, len)) -} - -/// Append an MCTP header to the front of a message. -/// Returns the message and its new total with the appended header. -pub fn build_mctp_header( - data: &[u8], - data_len: usize, - src_endpoint: crate::comms::EndpointID, - start_of_msg: bool, - end_of_msg: bool, -) -> Result<([u8; MAX_MCTP_PACKET_LEN], usize), MctpError> { - let mut ret = [0u8; MAX_MCTP_PACKET_LEN]; - let padding = [0u8; 3]; - - // Host is at address 0. - ret[0] = 0; - - // MCTP command code is 0x0F. - ret[1] = 0x0F; - - // Size of the payload length + header size, without padding - ret[2] = (data_len + 5) as u8; - - // Source is EC (upper 7 bits = 0x01 | hardcoded LSB of 0x01) - ret[3] = 3; - - // Header version is 1 - ret[4] = 1; - - // Destination endpoint ID is Host (0x01) - ret[5] = 1; - - // Subsystems supported currently are battery (0x02), thermal (0x03), and debug (0x04). - match src_endpoint { - crate::comms::EndpointID::Internal(crate::comms::Internal::Battery) => ret[6] = 2, - crate::comms::EndpointID::Internal(crate::comms::Internal::Thermal) => ret[6] = 3, - crate::comms::EndpointID::Internal(crate::comms::Internal::Debug) => ret[6] = 4, - _ => return Err(MctpError::InvalidDestinationEndpoint), - } - - // Seq num 1 + Msg tag 3 - ret[7] = 0x13; - if start_of_msg { - ret[7] |= 1 << 7; - } - if end_of_msg { - ret[7] |= 1 << 6; - } - - // True packet size must be a multple of 4. Header is 8 bytes which is already a multiple of 4, - // so we don't need to include it here. - let data_len_padded = round_up_to_nearest_mod_4(data_len); - - ret[8..data_len + 8].copy_from_slice(&data[..data_len]); - - // Add padding to align to 4 bytes - ret[data_len + 8..data_len_padded + 8].copy_from_slice(&padding[..data_len_padded - data_len]); - - Ok((ret, data_len_padded + 8)) -} - -// 5 bits total -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum OdpService { - Battery = 0x01, - Thermal = 0x02, - Debug = 0x03, -} - -// 10 bits total -// TODO: Fully define offsets for subsystem, temporarily it is every 32 entries -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u16)] -pub enum OdpCommandCode { - BatteryGetBixRequest = 0x01, - BatteryGetBstRequest = 0x02, - BatteryGetPsrRequest = 0x03, - BatteryGetPifRequest = 0x04, - BatteryGetBpsRequest = 0x05, - BatterySetBtpRequest = 0x06, - BatterySetBptRequest = 0x07, - BatteryGetBpcRequest = 0x08, - BatterySetBmcRequest = 0x09, - BatteryGetBmdRequest = 0x0A, - BatteryGetBctRequest = 0x0B, - BatteryGetBtmRequest = 0x0C, - BatterySetBmsRequest = 0x0D, - BatterySetBmaRequest = 0x0E, - BatteryGetStaRequest = 0x0F, - BatteryGetBixResponse = 0x11, - BatteryGetBstResponse = 0x12, - BatteryGetPsrResponse = 0x13, - BatteryGetPifResponse = 0x14, - BatteryGetBpsResponse = 0x15, - BatterySetBtpResponse = 0x16, - BatterySetBptResponse = 0x17, - BatteryGetBpcResponse = 0x18, - BatterySetBmcResponse = 0x19, - BatteryGetBmdResponse = 0x1A, - BatteryGetBctResponse = 0x1B, - BatteryGetBtmResponse = 0x1C, - BatterySetBmsResponse = 0x1D, - BatterySetBmaResponse = 0x1E, - BatteryGetStaResponse = 0x1F, - ThermalGetTmpRequest = 0x20, - ThermalSetThrsRequest = 0x21, - ThermalGetThrsRequest = 0x22, - ThermalSetScpRequest = 0x23, - ThermalGetVarRequest = 0x24, - ThermalSetVarRequest = 0x25, - ThermalGetTmpResponse = 0x30, - ThermalSetThrsResponse = 0x31, - ThermalGetThrsResponse = 0x32, - ThermalSetScpResponse = 0x33, - ThermalGetVarResponse = 0x34, - ThermalSetVarResponse = 0x35, - DebugGetMsgsRequest = 0x40, - DebugGetMsgsResponse = 0x50, -} - -// 3 byte header -#[derive(Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OdpHeader { - pub request_bit: bool, // [23:23] (1 bit) - pub datagram_bit: bool, // [22:22] (1 bit) - pub service: OdpService, // [18:21] (4 bits) - pub command_code: OdpCommandCode, // [8:17] (10 bits) - pub completion_code: MctpCompletionCode, // [0:7] (8 bits) -} - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BixFixedStrings< - const MODEL_SIZE: usize, - const SERIAL_SIZE: usize, - const BATTERY_SIZE: usize, - const OEM_SIZE: usize, -> { - /// Revision of the BIX structure. Current revision is 1. - pub revision: u32, - /// Unit used for capacity and rate values. - pub power_unit: embedded_batteries_async::acpi::PowerUnit, - /// Design capacity of the battery (in mWh or mAh). - pub design_capacity: u32, - /// Last full charge capacity (in mWh or mAh). - pub last_full_charge_capacity: u32, - /// Battery technology type. - pub battery_technology: embedded_batteries_async::acpi::BatteryTechnology, - /// Design voltage (in mV). - pub design_voltage: u32, - /// Warning capacity threshold (in mWh or mAh). - pub design_cap_of_warning: u32, - /// Low capacity threshold (in mWh or mAh). - pub design_cap_of_low: u32, - /// Number of charge/discharge cycles. - pub cycle_count: u32, - /// Measurement accuracy in thousandths of a percent (e.g., 80000 = 80.000%). - pub measurement_accuracy: u32, - /// Maximum supported sampling time (in ms). - pub max_sampling_time: u32, - /// Minimum supported sampling time (in ms). - pub min_sampling_time: u32, - /// Maximum supported averaging interval (in ms). - pub max_averaging_interval: u32, - /// Minimum supported averaging interval (in ms). - pub min_averaging_interval: u32, - /// Capacity granularity between low and warning (in mWh or mAh). - pub battery_capacity_granularity_1: u32, - /// Capacity granularity between warning and full (in mWh or mAh). - pub battery_capacity_granularity_2: u32, - /// OEM-specific model number (ASCIIZ). - pub model_number: [u8; MODEL_SIZE], - /// OEM-specific serial number (ASCIIZ). - pub serial_number: [u8; SERIAL_SIZE], - /// OEM-specific battery type (ASCIIZ). - pub battery_type: [u8; BATTERY_SIZE], - /// OEM-specific information (ASCIIZ). - pub oem_info: [u8; OEM_SIZE], - /// Battery swapping capability. - pub battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Error type when serializing ODP return types with fixed size strings. -pub enum OdpSerializeErr { - /// Input slice is too small to encapsulate all the fields. - InputSliceTooSmall, -} - -impl - BixFixedStrings -{ - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), OdpSerializeErr> { - const MODEL_NUM_START_IDX: usize = 64; - let model_num_end_idx: usize = MODEL_NUM_START_IDX + MODEL_SIZE; - let serial_num_start_idx = model_num_end_idx; - let serial_num_end_idx = serial_num_start_idx + SERIAL_SIZE; - let battery_type_start_idx = serial_num_end_idx; - let battery_type_end_idx = battery_type_start_idx + BATTERY_SIZE; - let oem_info_start_idx = battery_type_end_idx; - let oem_info_end_idx = oem_info_start_idx + OEM_SIZE; - - if dst_slice.len() < oem_info_end_idx { - return Err(OdpSerializeErr::InputSliceTooSmall); - } - - dst_slice[..4].copy_from_slice(&u32::to_le_bytes(self.revision)); - dst_slice[4..8].copy_from_slice(&u32::to_le_bytes(self.power_unit.into())); - dst_slice[8..12].copy_from_slice(&u32::to_le_bytes(self.design_capacity)); - dst_slice[12..16].copy_from_slice(&u32::to_le_bytes(self.last_full_charge_capacity)); - dst_slice[16..20].copy_from_slice(&u32::to_le_bytes(self.battery_technology.into())); - dst_slice[20..24].copy_from_slice(&u32::to_le_bytes(self.design_voltage)); - dst_slice[24..28].copy_from_slice(&u32::to_le_bytes(self.design_cap_of_warning)); - dst_slice[28..32].copy_from_slice(&u32::to_le_bytes(self.design_cap_of_low)); - dst_slice[32..36].copy_from_slice(&u32::to_le_bytes(self.cycle_count)); - dst_slice[36..40].copy_from_slice(&u32::to_le_bytes(self.measurement_accuracy)); - dst_slice[40..44].copy_from_slice(&u32::to_le_bytes(self.max_sampling_time)); - dst_slice[44..48].copy_from_slice(&u32::to_le_bytes(self.min_sampling_time)); - dst_slice[48..52].copy_from_slice(&u32::to_le_bytes(self.max_averaging_interval)); - dst_slice[52..56].copy_from_slice(&u32::to_le_bytes(self.min_averaging_interval)); - dst_slice[56..60].copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_1)); - dst_slice[60..64].copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_2)); - dst_slice[MODEL_NUM_START_IDX..model_num_end_idx].copy_from_slice(&self.model_number); - dst_slice[serial_num_start_idx..serial_num_end_idx].copy_from_slice(&self.serial_number); - dst_slice[battery_type_start_idx..battery_type_end_idx].copy_from_slice(&self.battery_type); - dst_slice[oem_info_start_idx..oem_info_end_idx].copy_from_slice(&self.oem_info); - dst_slice[oem_info_end_idx..oem_info_end_idx + 4] - .copy_from_slice(&u32::to_le_bytes(self.battery_swapping_capability.into())); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PifFixedStrings { - /// Bitfield describing the state and characteristics of the power source. - pub power_source_state: embedded_batteries_async::acpi::PowerSourceState, - /// Maximum rated output power in milliwatts (mW). - /// - /// 0xFFFFFFFF indicates the value is unavailable. - pub max_output_power: u32, - /// Maximum rated input power in milliwatts (mW). - /// - /// 0xFFFFFFFF indicates the value is unavailable. - pub max_input_power: u32, - /// OEM-specific model number (ASCIIZ). Empty string if not supported. - pub model_number: [u8; MODEL_SIZE], - /// OEM-specific serial number (ASCIIZ). Empty string if not supported. - pub serial_number: [u8; SERIAL_SIZE], - /// OEM-specific information (ASCIIZ). Empty string if not supported. - pub oem_info: [u8; OEM_SIZE], -} - -impl - PifFixedStrings -{ - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), OdpSerializeErr> { - const MODEL_NUM_START_IDX: usize = 12; - let model_num_end_idx: usize = MODEL_NUM_START_IDX + MODEL_SIZE; - let serial_num_start_idx = model_num_end_idx; - let serial_num_end_idx = serial_num_start_idx + SERIAL_SIZE; - let oem_info_start_idx = serial_num_end_idx; - let oem_info_end_idx = oem_info_start_idx + OEM_SIZE; - - if dst_slice.len() < oem_info_end_idx { - return Err(OdpSerializeErr::InputSliceTooSmall); - } - - dst_slice[..4].copy_from_slice(&u32::to_le_bytes(self.power_source_state.bits())); - dst_slice[4..8].copy_from_slice(&u32::to_le_bytes(self.max_output_power)); - dst_slice[8..12].copy_from_slice(&u32::to_le_bytes(self.max_input_power)); - dst_slice[MODEL_NUM_START_IDX..model_num_end_idx].copy_from_slice(&self.model_number); - dst_slice[serial_num_start_idx..serial_num_end_idx].copy_from_slice(&self.serial_number); - dst_slice[oem_info_start_idx..oem_info_end_idx].copy_from_slice(&self.oem_info); - Ok(()) - } -} - -/// Standard 32-bit DWORD -pub type Dword = u32; - -/// 16-bit variable length -pub type VarLen = u16; - -/// Instance ID -pub type InstanceId = u8; - -/// Time in milliseconds -pub type Milliseconds = Dword; - -/// MPTF expects temperatures in tenth Kelvins -pub type DeciKelvin = Dword; - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Odp< - const BIX_MODEL_SIZE: usize, - const BIX_SERIAL_SIZE: usize, - const BIX_BATTERY_SIZE: usize, - const BIX_OEM_SIZE: usize, - const PIF_MODEL_SIZE: usize, - const PIF_SERIAL_SIZE: usize, - const PIF_OEM_SIZE: usize, - const DEBUG_BUF_SIZE: usize, -> { - BatteryGetBixRequest { - battery_id: u8, - }, - BatteryGetBstRequest { - battery_id: u8, - }, - BatteryGetPsrRequest { - battery_id: u8, - }, - BatteryGetPifRequest { - battery_id: u8, - }, - BatteryGetBpsRequest { - battery_id: u8, - }, - BatterySetBtpRequest { - battery_id: u8, - btp: embedded_batteries_async::acpi::Btp, - }, - BatterySetBptRequest { - battery_id: u8, - bpt: embedded_batteries_async::acpi::Bpt, - }, - BatteryGetBpcRequest { - battery_id: u8, - }, - BatterySetBmcRequest { - battery_id: u8, - bmc: embedded_batteries_async::acpi::Bmc, - }, - BatteryGetBmdRequest { - battery_id: u8, - }, - BatteryGetBctRequest { - battery_id: u8, - bct: embedded_batteries_async::acpi::Bct, - }, - BatteryGetBtmRequest { - battery_id: u8, - btm: embedded_batteries_async::acpi::Btm, - }, - BatterySetBmsRequest { - battery_id: u8, - bms: embedded_batteries_async::acpi::Bms, - }, - BatterySetBmaRequest { - battery_id: u8, - bma: embedded_batteries_async::acpi::Bma, - }, - BatteryGetStaRequest { - battery_id: u8, - }, - BatteryGetBixResponse { - bix: BixFixedStrings, - }, - BatteryGetBstResponse { - bst: embedded_batteries_async::acpi::BstReturn, - }, - BatteryGetPsrResponse { - psr: embedded_batteries_async::acpi::PsrReturn, - }, - BatteryGetPifResponse { - pif: PifFixedStrings, - }, - BatteryGetBpsResponse { - bps: embedded_batteries_async::acpi::Bps, - }, - BatterySetBtpResponse {}, - BatterySetBptResponse {}, - BatteryGetBpcResponse { - bpc: embedded_batteries_async::acpi::Bpc, - }, - BatterySetBmcResponse {}, - BatteryGetBmdResponse { - bmd: embedded_batteries_async::acpi::Bmd, - }, - BatteryGetBctResponse { - bct_response: embedded_batteries_async::acpi::BctReturnResult, - }, - BatteryGetBtmResponse { - btm_response: embedded_batteries_async::acpi::BtmReturnResult, - }, - BatterySetBmsResponse { - status: Dword, - }, - BatterySetBmaResponse { - status: Dword, - }, - BatteryGetStaResponse { - sta: embedded_batteries_async::acpi::StaReturn, - }, - - ThermalGetTmpRequest { - instance_id: u8, - }, - ThermalSetThrsRequest { - instance_id: u8, - timeout: Milliseconds, - low: DeciKelvin, - high: DeciKelvin, - }, - ThermalGetThrsRequest { - instance_id: u8, - }, - ThermalSetScpRequest { - instance_id: u8, - policy_id: Dword, - acoustic_lim: Dword, - power_lim: Dword, - }, - ThermalGetVarRequest { - instance_id: u8, - len: VarLen, - var_uuid: uuid::Bytes, - }, - ThermalSetVarRequest { - instance_id: u8, - len: VarLen, - var_uuid: uuid::Bytes, - set_var: Dword, - }, - DebugGetMsgsRequest, - - ThermalGetTmpResponse { - temperature: DeciKelvin, - }, - ThermalSetThrsResponse { - status: Dword, - }, - ThermalGetThrsResponse { - status: Dword, - timeout: Milliseconds, - low: DeciKelvin, - high: DeciKelvin, - }, - ThermalSetScpResponse { - status: Dword, - }, - ThermalGetVarResponse { - status: Dword, - val: Dword, - }, - ThermalSetVarResponse { - status: Dword, - }, - DebugGetMsgsResponse { - debug_buf: [u8; DEBUG_BUF_SIZE], - }, - ErrorResponse {}, -} - -impl MctpMessageHeaderTrait for OdpHeader { - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - check_header_length(buffer)?; - let command_code: u16 = self.command_code as u16; - buffer[0] = (self.request_bit as u8) << 7 - | (self.datagram_bit as u8) << 6 - | ((self.service as u8) & 0b0000_1111) << 2 - | ((command_code >> 8) as u8 & 0b0000_0011); - buffer[1] = (command_code & 0x00FF) as u8; - buffer[2] = self.completion_code.into(); - Ok(3) - } - - fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { - check_header_length(buffer)?; - let request_bit = buffer[0] & 0b1000_0000 != 0; - let datagram_bit = buffer[0] & 0b0100_0000 != 0; - let service = (buffer[0] & 0b0011_1100) >> 2; - let command_code = ((buffer[0] & 0b0000_0011) as u16) << 8 | (buffer[1] as u16); - - let completion_code = buffer[2] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("invalid completion code"))?; - let service = service - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service"))?; - let command_code = command_code - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp command code"))?; - - Ok(( - OdpHeader { - request_bit, - datagram_bit, - service, - command_code, - completion_code, - }, - &buffer[3..], - )) - } -} - -impl< - const BIX_MODEL_SIZE: usize, - const BIX_SERIAL_SIZE: usize, - const BIX_BATTERY_SIZE: usize, - const BIX_OEM_SIZE: usize, - const PIF_MODEL_SIZE: usize, - const PIF_SERIAL_SIZE: usize, - const PIF_OEM_SIZE: usize, - const DEBUG_BUF_SIZE: usize, -> MctpMessageTrait<'_> - for Odp< - BIX_MODEL_SIZE, - BIX_SERIAL_SIZE, - BIX_BATTERY_SIZE, - BIX_OEM_SIZE, - PIF_MODEL_SIZE, - PIF_SERIAL_SIZE, - PIF_OEM_SIZE, - DEBUG_BUF_SIZE, - > -{ - const MESSAGE_TYPE: u8 = 0x7D; - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - Self::BatteryGetBixRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetBstRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetPsrRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetPifRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetBpsRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatterySetBtpRequest { battery_id, btp } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(btp.trip_point)); - - Ok(5) - } - Self::BatterySetBptRequest { battery_id, bpt } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bpt.revision)); - buffer[5..9].copy_from_slice(&u32::to_le_bytes(match bpt.threshold_id { - embedded_batteries_async::acpi::ThresholdId::ClearAll => 0, - embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower => 1, - embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower => 2, - })); - buffer[9..13].copy_from_slice(&u32::to_le_bytes(bpt.threshold_value)); - - Ok(13) - } - Self::BatteryGetBpcRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatterySetBmcRequest { battery_id, bmc } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bmc.maintenance_control_flags.bits())); - - Ok(5) - } - Self::BatteryGetBmdRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::BatteryGetBctRequest { battery_id, bct } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bct.charge_level_percent)); - - Ok(5) - } - Self::BatteryGetBtmRequest { battery_id, btm } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(btm.discharge_rate)); - - Ok(5) - } - Self::BatterySetBmsRequest { battery_id, bms } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bms.sampling_time_ms)); - - Ok(5) - } - Self::BatterySetBmaRequest { battery_id, bma } => { - buffer[0] = battery_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(bma.averaging_interval_ms)); - - Ok(5) - } - Self::BatteryGetStaRequest { battery_id } => write_to_buffer(buffer, [battery_id]), - Self::ThermalGetTmpRequest { instance_id } => write_to_buffer(buffer, [instance_id]), - Self::ThermalSetThrsRequest { - instance_id, - timeout, - low, - high, - } => { - buffer[0] = instance_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(timeout)); - buffer[5..9].copy_from_slice(&u32::to_le_bytes(low)); - buffer[9..13].copy_from_slice(&u32::to_le_bytes(high)); - - Ok(13) - } - Self::ThermalGetThrsRequest { instance_id } => write_to_buffer(buffer, [instance_id]), - Self::ThermalSetScpRequest { - instance_id, - policy_id, - acoustic_lim, - power_lim, - } => { - buffer[0] = instance_id; - buffer[1..5].copy_from_slice(&u32::to_le_bytes(policy_id)); - buffer[5..9].copy_from_slice(&u32::to_le_bytes(acoustic_lim)); - buffer[9..13].copy_from_slice(&u32::to_le_bytes(power_lim)); - - Ok(13) - } - Self::ThermalGetVarRequest { - instance_id, - len, - var_uuid, - } => { - buffer[0] = instance_id; - buffer[1..3].copy_from_slice(&u16::to_le_bytes(len)); - buffer[3..19].copy_from_slice(&var_uuid); - - Ok(19) - } - Self::ThermalSetVarRequest { - instance_id, - len, - var_uuid, - set_var, - } => { - buffer[0] = instance_id; - buffer[1..3].copy_from_slice(&u16::to_le_bytes(len)); - buffer[3..19].copy_from_slice(&var_uuid); - buffer[19..23].copy_from_slice(&u32::to_le_bytes(set_var)); - - Ok(23) - } - Self::DebugGetMsgsRequest => Ok(0), - Self::BatteryGetBixResponse { bix } => bix - .to_bytes(buffer) - .map(|_| 100) - .map_err(|_| mctp_rs::MctpPacketError::HeaderParseError("Bix parse failed")), - Self::BatteryGetBstResponse { bst } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bst.battery_state.bits())); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bst.battery_remaining_capacity)); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bst.battery_present_rate)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bst.battery_present_voltage)); - - Ok(BST_RETURN_SIZE_BYTES) - } - Self::BatteryGetPsrResponse { psr } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(psr.power_source.into())); - - Ok(PSR_RETURN_SIZE_BYTES) - } - Self::BatteryGetPifResponse { pif } => pif - .to_bytes(buffer) - .map(|_| 36) - .map_err(|_| mctp_rs::MctpPacketError::HeaderParseError("Pif parse failed")), - Self::BatteryGetBpsResponse { bps } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bps.revision)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_level)); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_period)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_level)); - buffer[16..20].copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_period)); - - Ok(BPS_RETURN_SIZE_BYTES) - } - Self::BatterySetBtpResponse {} => Ok(0), - Self::BatterySetBptResponse {} => Ok(0), - Self::BatteryGetBpcResponse { bpc } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bpc.revision)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bpc.power_threshold_support.bits())); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bpc.max_instantaneous_peak_power_threshold)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bpc.max_sustainable_peak_power_threshold)); - - Ok(BPC_RETURN_SIZE_BYTES) - } - Self::BatterySetBmcResponse {} => Ok(0), - Self::BatteryGetBmdResponse { bmd } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bmd.status_flags.bits())); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(bmd.capability_flags.bits())); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(bmd.recalibrate_count)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(bmd.quick_recalibrate_time)); - buffer[16..20].copy_from_slice(&u32::to_le_bytes(bmd.slow_recalibrate_time)); - - Ok(BMD_RETURN_SIZE_BYTES) - } - Self::BatteryGetBctResponse { bct_response } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(bct_response.into())); - - Ok(BCT_RETURN_SIZE_BYTES) - } - Self::BatteryGetBtmResponse { btm_response } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(btm_response.into())); - - Ok(BTM_RETURN_SIZE_BYTES) - } - Self::BatterySetBmsResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::BatterySetBmaResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::BatteryGetStaResponse { sta } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(sta.bits())); - - Ok(STA_RETURN_SIZE_BYTES) - } - Self::ThermalGetTmpResponse { temperature } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(temperature)); - - Ok(4) - } - Self::ThermalSetThrsResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::ThermalGetThrsResponse { - status, - timeout, - low, - high, - } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(timeout)); - buffer[8..12].copy_from_slice(&u32::to_le_bytes(low)); - buffer[12..16].copy_from_slice(&u32::to_le_bytes(high)); - - Ok(16) - } - Self::ThermalSetScpResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::ThermalGetVarResponse { status, val } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - buffer[4..8].copy_from_slice(&u32::to_le_bytes(val)); - - Ok(8) - } - Self::ThermalSetVarResponse { status } => { - buffer[..4].copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::DebugGetMsgsResponse { debug_buf } => { - buffer[..debug_buf.len()].copy_from_slice(&debug_buf); - Ok(debug_buf.len()) - } - Self::ErrorResponse {} => Ok(0), - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.command_code { - OdpCommandCode::BatteryGetBixRequest => Self::BatteryGetBixRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetBstRequest => Self::BatteryGetBstRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetPsrRequest => Self::BatteryGetPsrRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetPifRequest => Self::BatteryGetPifRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetBpsRequest => Self::BatteryGetBpsRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatterySetBtpRequest => Self::BatterySetBtpRequest { - battery_id: safe_get_u8(buffer, 0)?, - btp: embedded_batteries_async::acpi::Btp { - trip_point: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatterySetBptRequest => Self::BatterySetBptRequest { - battery_id: safe_get_u8(buffer, 0)?, - bpt: embedded_batteries_async::acpi::Bpt { - revision: safe_get_dword(buffer, 1)?, - threshold_id: match safe_get_dword(buffer, 5)? { - 0 => embedded_batteries_async::acpi::ThresholdId::ClearAll, - 1 => embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower, - 2 => embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower, - _ => { - return Err(MctpPacketError::HeaderParseError("Unsupported threshold id")); - } - }, - threshold_value: safe_get_dword(buffer, 9)?, - }, - }, - OdpCommandCode::BatteryGetBpcRequest => Self::BatteryGetBpcRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatterySetBmcRequest => Self::BatterySetBmcRequest { - battery_id: safe_get_u8(buffer, 0)?, - bmc: embedded_batteries_async::acpi::Bmc { - maintenance_control_flags: embedded_batteries_async::acpi::BmcControlFlags::from_bits_retain( - safe_get_dword(buffer, 1)?, - ), - }, - }, - OdpCommandCode::BatteryGetBmdRequest => Self::BatteryGetBmdRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::BatteryGetBctRequest => Self::BatteryGetBctRequest { - battery_id: safe_get_u8(buffer, 0)?, - bct: embedded_batteries_async::acpi::Bct { - charge_level_percent: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatteryGetBtmRequest => Self::BatteryGetBtmRequest { - battery_id: safe_get_u8(buffer, 0)?, - btm: embedded_batteries_async::acpi::Btm { - discharge_rate: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatterySetBmsRequest => Self::BatterySetBmsRequest { - battery_id: safe_get_u8(buffer, 0)?, - bms: embedded_batteries_async::acpi::Bms { - sampling_time_ms: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatterySetBmaRequest => Self::BatterySetBmaRequest { - battery_id: safe_get_u8(buffer, 0)?, - bma: embedded_batteries_async::acpi::Bma { - averaging_interval_ms: safe_get_dword(buffer, 1)?, - }, - }, - OdpCommandCode::BatteryGetStaRequest => Self::BatteryGetStaRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::ThermalGetTmpRequest => Self::ThermalGetTmpRequest { - instance_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::ThermalSetThrsRequest => Self::ThermalSetThrsRequest { - instance_id: safe_get_u8(buffer, 0)?, - timeout: safe_get_dword(buffer, 1)?, - low: safe_get_dword(buffer, 5)?, - high: safe_get_dword(buffer, 9)?, - }, - OdpCommandCode::ThermalGetThrsRequest => Self::ThermalGetThrsRequest { - instance_id: safe_get_u8(buffer, 0)?, - }, - OdpCommandCode::ThermalSetScpRequest => Self::ThermalSetScpRequest { - instance_id: safe_get_u8(buffer, 0)?, - policy_id: safe_get_dword(buffer, 1)?, - acoustic_lim: safe_get_dword(buffer, 5)?, - power_lim: safe_get_dword(buffer, 9)?, - }, - OdpCommandCode::ThermalGetVarRequest => Self::ThermalGetVarRequest { - instance_id: safe_get_u8(buffer, 0)?, - len: safe_get_u16(buffer, 1)?, - var_uuid: safe_get_uuid(buffer, 3)?, - }, - OdpCommandCode::ThermalSetVarRequest => Self::ThermalSetVarRequest { - instance_id: safe_get_u8(buffer, 0)?, - len: safe_get_u16(buffer, 1)?, - var_uuid: safe_get_uuid(buffer, 3)?, - set_var: safe_get_dword(buffer, 19)?, - }, - OdpCommandCode::DebugGetMsgsRequest => Self::DebugGetMsgsRequest, - OdpCommandCode::BatteryGetBixResponse => Self::BatteryGetBixResponse { - bix: BixFixedStrings { - revision: safe_get_dword(buffer, 0)?, - power_unit: match safe_get_dword(buffer, 4)? { - 0 => embedded_batteries_async::acpi::PowerUnit::MilliWatts, - 1 => embedded_batteries_async::acpi::PowerUnit::MilliAmps, - _ => { - return Err(MctpPacketError::HeaderParseError("BIX deserialize failed")); - } - }, - design_capacity: safe_get_dword(buffer, 8)?, - last_full_charge_capacity: safe_get_dword(buffer, 12)?, - battery_technology: match safe_get_dword(buffer, 16)? { - 0 => embedded_batteries_async::acpi::BatteryTechnology::Primary, - 1 => embedded_batteries_async::acpi::BatteryTechnology::Secondary, - _ => { - return Err(MctpPacketError::HeaderParseError("BIX deserialize failed")); - } - }, - design_voltage: safe_get_dword(buffer, 20)?, - design_cap_of_warning: safe_get_dword(buffer, 24)?, - design_cap_of_low: safe_get_dword(buffer, 28)?, - cycle_count: safe_get_dword(buffer, 32)?, - measurement_accuracy: safe_get_dword(buffer, 36)?, - max_sampling_time: safe_get_dword(buffer, 40)?, - min_sampling_time: safe_get_dword(buffer, 44)?, - max_averaging_interval: safe_get_dword(buffer, 48)?, - min_averaging_interval: safe_get_dword(buffer, 52)?, - battery_capacity_granularity_1: safe_get_dword(buffer, 56)?, - battery_capacity_granularity_2: safe_get_dword(buffer, 60)?, - model_number: buffer[64..72] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - serial_number: buffer[72..80] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - battery_type: buffer[80..88] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - oem_info: buffer[88..96] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("BIX deserialize failed"))?, - battery_swapping_capability: match safe_get_dword(buffer, 100)? { - 0 => embedded_batteries_async::acpi::BatterySwapCapability::NonSwappable, - 1 => embedded_batteries_async::acpi::BatterySwapCapability::ColdSwappable, - 2 => embedded_batteries_async::acpi::BatterySwapCapability::HotSwappable, - _ => { - return Err(MctpPacketError::HeaderParseError("BIX deserialize failed")); - } - }, - }, - }, - OdpCommandCode::BatteryGetBstResponse => Self::BatteryGetBstResponse { - bst: embedded_batteries_async::acpi::BstReturn { - battery_state: BatteryState::from_bits_retain(safe_get_dword(buffer, 0)?), - battery_remaining_capacity: safe_get_dword(buffer, 4)?, - battery_present_rate: safe_get_dword(buffer, 8)?, - battery_present_voltage: safe_get_dword(buffer, 12)?, - }, - }, - OdpCommandCode::BatteryGetPsrResponse => Self::BatteryGetPsrResponse { - psr: PsrReturn { - power_source: match safe_get_dword(buffer, 0)? { - 0 => embedded_batteries_async::acpi::PowerSource::Offline, - 1 => embedded_batteries_async::acpi::PowerSource::Online, - _ => { - return Err(MctpPacketError::HeaderParseError("PSR deserialize failed")); - } - }, - }, - }, - OdpCommandCode::BatteryGetPifResponse => Self::BatteryGetPifResponse { - pif: PifFixedStrings { - power_source_state: PowerSourceState::from_bits_retain(safe_get_dword(buffer, 0)?), - max_output_power: safe_get_dword(buffer, 4)?, - max_input_power: safe_get_dword(buffer, 8)?, - model_number: buffer[12..20] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Pif deserialize failed"))?, - serial_number: buffer[20..28] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Pif deserialize failed"))?, - oem_info: buffer[28..36] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Pif deserialize failed"))?, - }, - }, - OdpCommandCode::BatteryGetBpsResponse => Self::BatteryGetBpsResponse { - bps: embedded_batteries_async::acpi::Bps { - revision: safe_get_dword(buffer, 0)?, - instantaneous_peak_power_level: safe_get_dword(buffer, 4)?, - instantaneous_peak_power_period: safe_get_dword(buffer, 8)?, - sustainable_peak_power_level: safe_get_dword(buffer, 12)?, - sustainable_peak_power_period: safe_get_dword(buffer, 16)?, - }, - }, - OdpCommandCode::BatterySetBtpResponse => Self::BatterySetBtpResponse {}, - OdpCommandCode::BatterySetBptResponse => Self::BatterySetBptResponse {}, - OdpCommandCode::BatteryGetBpcResponse => Self::BatteryGetBpcResponse { - bpc: embedded_batteries_async::acpi::Bpc { - revision: safe_get_dword(buffer, 0)?, - power_threshold_support: PowerThresholdSupport::from_bits_retain(safe_get_dword(buffer, 4)?), - max_instantaneous_peak_power_threshold: safe_get_dword(buffer, 8)?, - max_sustainable_peak_power_threshold: safe_get_dword(buffer, 12)?, - }, - }, - OdpCommandCode::BatterySetBmcResponse => Self::BatterySetBmcResponse {}, - OdpCommandCode::BatteryGetBmdResponse => Self::BatteryGetBmdResponse { - bmd: embedded_batteries_async::acpi::Bmd { - status_flags: BmdStatusFlags::from_bits_retain(safe_get_dword(buffer, 0)?), - capability_flags: BmdCapabilityFlags::from_bits_retain(safe_get_dword(buffer, 4)?), - recalibrate_count: safe_get_dword(buffer, 8)?, - quick_recalibrate_time: safe_get_dword(buffer, 12)?, - slow_recalibrate_time: safe_get_dword(buffer, 16)?, - }, - }, - OdpCommandCode::BatteryGetBctResponse => Self::BatteryGetBctResponse { - bct_response: embedded_batteries_async::acpi::BctReturnResult::from(safe_get_dword(buffer, 0)?), - }, - OdpCommandCode::BatteryGetBtmResponse => Self::BatteryGetBtmResponse { - btm_response: embedded_batteries_async::acpi::BtmReturnResult::from(safe_get_dword(buffer, 0)?), - }, - OdpCommandCode::BatterySetBmsResponse => Self::BatterySetBmsResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::BatterySetBmaResponse => Self::BatterySetBmaResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::BatteryGetStaResponse => Self::BatteryGetStaResponse { - sta: embedded_batteries_async::acpi::StaReturn::from_bits_retain(safe_get_dword(buffer, 0)?), - }, - OdpCommandCode::ThermalGetTmpResponse => Self::ThermalGetTmpResponse { - temperature: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::ThermalSetThrsResponse => Self::ThermalSetThrsResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::ThermalGetThrsResponse => Self::ThermalGetThrsResponse { - status: safe_get_dword(buffer, 0)?, - timeout: safe_get_dword(buffer, 4)?, - low: safe_get_dword(buffer, 8)?, - high: safe_get_dword(buffer, 12)?, - }, - OdpCommandCode::ThermalSetScpResponse => Self::ThermalSetScpResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::ThermalGetVarResponse => Self::ThermalGetVarResponse { - status: safe_get_dword(buffer, 0)?, - val: safe_get_dword(buffer, 4)?, - }, - OdpCommandCode::ThermalSetVarResponse => Self::ThermalSetVarResponse { - status: safe_get_dword(buffer, 0)?, - }, - OdpCommandCode::DebugGetMsgsResponse => Self::DebugGetMsgsResponse { - debug_buf: buffer[..DEBUG_BUF_SIZE] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("MCTP buf not large enough"))?, - }, - }) - } -} - -fn safe_get_u8(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 1 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - Ok(buffer[index]) -} - -fn safe_get_u16(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 2 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - // Safe from panics as length is verified above. - Ok(u16::from_le_bytes(buffer[index..index + 2].try_into().unwrap())) -} - -fn safe_get_dword(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 4 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - // Safe from panics as length is verified above. - Ok(u32::from_le_bytes(buffer[index..index + 4].try_into().unwrap())) -} - -fn safe_get_uuid(buffer: &[u8], index: usize) -> MctpPacketResult { - if buffer.len() < index + 16 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp message")); - } - // Safe from panics as length is verified above. - Ok(buffer[index..index + 16].try_into().unwrap()) -} - -fn write_to_buffer(buffer: &mut [u8], data: [u8; N]) -> MctpPacketResult { - if buffer.len() < N { - return Err(MctpPacketError::SerializeError("buffer too small for odp message")); - } - buffer[..N].copy_from_slice(&data); - Ok(N) -} - -fn check_header_length(buffer: &[u8]) -> MctpPacketResult<(), M> { - if buffer.len() < 3 { - return Err(MctpPacketError::HeaderParseError("buffer too small for odp header")); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[allow(dead_code)] - mod test_util { - use mctp_rs::{MctpMedium, MctpMediumFrame, MctpPacketError, error::MctpPacketResult}; - - #[derive(Debug, PartialEq, Eq, Copy, Clone)] - pub struct TestMedium { - header: &'static [u8], - trailer: &'static [u8], - mtu: usize, - } - impl TestMedium { - pub fn new() -> Self { - Self { - header: &[], - trailer: &[], - mtu: 32, - } - } - pub fn with_headers(mut self, header: &'static [u8], trailer: &'static [u8]) -> Self { - self.header = header; - self.trailer = trailer; - self - } - pub fn with_mtu(mut self, mtu: usize) -> Self { - self.mtu = mtu; - self - } - } - - #[derive(Debug, PartialEq, Eq, Copy, Clone)] - pub struct TestMediumFrame(usize); - - impl MctpMedium for TestMedium { - type Frame = TestMediumFrame; - type Error = &'static str; - type ReplyContext = (); - - fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { - let packet_len = packet.len(); - - // check that header / trailer is present and correct - if packet.len() < self.header.len() + self.trailer.len() { - return Err(MctpPacketError::MediumError("packet too short")); - } - if packet[0..self.header.len()] != *self.header { - return Err(MctpPacketError::MediumError("header mismatch")); - } - if packet[packet_len - self.trailer.len()..packet_len] != *self.trailer { - return Err(MctpPacketError::MediumError("trailer mismatch")); - } - - let packet = &packet[self.header.len()..packet_len - self.trailer.len()]; - Ok((TestMediumFrame(packet_len), packet)) - } - fn max_message_body_size(&self) -> usize { - self.mtu - } - fn serialize<'buf, F>( - &self, - _: Self::ReplyContext, - buffer: &'buf mut [u8], - message_writer: F, - ) -> MctpPacketResult<&'buf [u8], Self> - where - F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, - { - let header_len = self.header.len(); - let trailer_len = self.trailer.len(); - - // Ensure buffer can fit at least headers and trailers - if buffer.len() < header_len + trailer_len { - return Err(MctpPacketError::MediumError("Buffer too small for headers")); - } - - // Calculate available space for message (respecting MTU) - let max_packet_size = self.mtu.min(buffer.len()); - if max_packet_size < header_len + trailer_len { - return Err(MctpPacketError::MediumError("MTU too small for headers")); - } - let max_message_size = max_packet_size - header_len - trailer_len; - - buffer[0..header_len].copy_from_slice(self.header); - let size = message_writer(&mut buffer[header_len..header_len + max_message_size])?; - let len = header_len + size; - buffer[len..len + trailer_len].copy_from_slice(self.trailer); - Ok(&buffer[..len + trailer_len]) - } - } - - impl MctpMediumFrame for TestMediumFrame { - fn packet_size(&self) -> usize { - self.0 - } - fn reply_context(&self) -> ::ReplyContext {} - } - } - - use test_util::TestMedium; - - #[rstest::rstest] - #[case(OdpHeader { - request_bit: true, - datagram_bit: false, - service: OdpService::Battery, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::Success - })] - #[case( - OdpHeader { - request_bit: false, - datagram_bit: true, - service: OdpService::Debug, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::ErrorUnsupportedCmd - })] - #[case( - OdpHeader { - request_bit: true, - datagram_bit: true, - service: OdpService::Battery, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::CommandSpecific(0x80) - })] - #[case( - OdpHeader { - request_bit: false, - datagram_bit: false, - service: OdpService::Debug, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::Success - })] - fn odp_header_roundtrip_happy_path(#[case] header: OdpHeader) { - let mut buf = [0u8; 3]; - let size = header.serialize::(&mut buf).unwrap(); - assert_eq!(size, 3); - - let (parsed, rest) = OdpHeader::deserialize::(&buf).unwrap(); - assert_eq!(parsed, header); - assert_eq!(rest.len(), 0); - } - - #[test] - #[allow(clippy::panic)] - fn odp_header_error_on_short_buffer() { - let header = OdpHeader { - request_bit: false, - datagram_bit: false, - service: OdpService::Battery, - command_code: OdpCommandCode::BatteryGetBixRequest, - completion_code: MctpCompletionCode::Success, - }; - - // Serialize works with correct buffer - let mut buf_ok = [0u8; 3]; - header.serialize::(&mut buf_ok).unwrap(); - - // Deserialize should fail on too-small buffer - let err = OdpHeader::deserialize::(&buf_ok[..2]).unwrap_err(); - match err { - MctpPacketError::HeaderParseError(msg) => { - assert_eq!(msg, "buffer too small for odp header") - } - other => panic!("unexpected error: {:?}", other), - } - } -} diff --git a/embedded-service/src/ec_type/protocols/mod.rs b/embedded-service/src/ec_type/protocols/mod.rs deleted file mode 100644 index aafb1e632..000000000 --- a/embedded-service/src/ec_type/protocols/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Primitive serde and helper fns for protocols used by the EC. - -/// ACPI (Advanced Configuration and Power Interface). -pub mod acpi; - -/// ODP Specific Debug Protocol. -pub mod debug; - -/// MCTP (Management Component Transport Protocol). -#[allow(clippy::indexing_slicing)] //panic safety: no external client, not being deployed -#[allow(clippy::unwrap_used)] //panic safety: no external client, not being deployed -pub mod mctp; - -/// MTPF (Modern Thermal and Power Framework). -pub mod mptf; diff --git a/embedded-service/src/ec_type/protocols/mptf.rs b/embedded-service/src/ec_type/protocols/mptf.rs deleted file mode 100644 index 533311850..000000000 --- a/embedded-service/src/ec_type/protocols/mptf.rs +++ /dev/null @@ -1,17 +0,0 @@ -/// Standard MPTF requests expected by the thermal subsystem -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ThermalCmd { - /// EC_THM_GET_TMP = 0x1 - GetTmp = 1, - /// EC_THM_SET_THRS = 0x2 - SetThrs = 2, - /// EC_THM_GET_THRS = 0x3 - GetThrs = 3, - /// EC_THM_SET_SCP = 0x4 - SetScp = 4, - /// EC_THM_GET_VAR = 0x5 - GetVar = 5, - /// EC_THM_SET_VAR = 0x6 - SetVar = 6, -} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 4ce50d61b..e43b63481 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -23,6 +23,7 @@ pub mod init; pub mod ipc; pub mod keyboard; pub mod power; +pub mod relay; pub mod sync; pub mod type_c; diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs new file mode 100644 index 000000000..6531fcf67 --- /dev/null +++ b/embedded-service/src/relay/mod.rs @@ -0,0 +1,97 @@ +//! Helper code for serialization/deserialization of arbitrary messages to/from the embedded controller via a relay service, e.g. the eSPI service. + +/// Error type for serializing/deserializing messages +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MessageSerializationError { + /// The message payload does not represent a valid message + InvalidPayload(&'static str), + + /// The message discriminant does not represent a known message type + UnknownMessageDiscriminant(u16), + + /// The provided buffer is too small to serialize the message + BufferTooSmall, + + /// Unspecified error + Other(&'static str), +} + +/// Trait for serializing and deserializing messages +pub trait SerializableMessage: Sized { + /// Serializes the message into the provided buffer. + /// On success, returns the number of bytes written + fn serialize(self, buffer: &mut [u8]) -> Result; + + /// Returns the discriminant needed to deserialize this type of message. + fn discriminant(&self) -> u16; + + /// Deserializes the message from the provided buffer. + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; +} + +// Prevent other types from implementing SerializableResponse - they should instead use SerializableMessage on a Response type and an Error type +#[doc(hidden)] +mod private { + pub trait Sealed {} + + impl Sealed for Result {} +} + +/// Responses are of type Result where T and E both implement SerializableMessage +pub trait SerializableResponse: private::Sealed + Sized { + /// The type of the response when the operation being responsed to succeeded + type SuccessType: SerializableMessage; + + /// The type of the response when the operation being responsed to failed + type ErrorType: SerializableMessage; + + /// Returns true if the response represents a successful operation, false otherwise + fn is_ok(&self) -> bool; + + /// Returns a unique discriminant that can be used to deserialize the specific type of response. + /// Discriminants can be reused for success and error messages. + fn discriminant(&self) -> u16; + + /// Writes the response into the provided buffer. + /// On success, returns the number of bytes written + fn serialize(self, buffer: &mut [u8]) -> Result; + + /// Attempts to deserialize the response from the provided buffer. + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; +} + +impl SerializableResponse for Result +where + T: SerializableMessage, + E: SerializableMessage, +{ + type SuccessType = T; + type ErrorType = E; + + fn is_ok(&self) -> bool { + Result::::is_ok(self) + } + + fn discriminant(&self) -> u16 { + match self { + Ok(success_value) => success_value.discriminant(), + Err(error_value) => error_value.discriminant(), + } + } + + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Ok(success_value) => success_value.serialize(buffer), + Err(error_value) => error_value.serialize(buffer), + } + } + + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result { + if is_error { + Ok(Err(E::deserialize(discriminant, buffer)?)) + } else { + Ok(Ok(T::deserialize(discriminant, buffer)?)) + } + } +} diff --git a/espi-service/Cargo.toml b/espi-service/Cargo.toml index 0734a57e9..7f0975d2c 100644 --- a/espi-service/Cargo.toml +++ b/espi-service/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" workspace = true [dependencies] +bitfield.workspace = true embedded-services.workspace = true defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } @@ -19,6 +20,13 @@ embassy-sync.workspace = true embassy-imxrt = { workspace = true, features = ["mimxrt633s"] } embassy-futures.workspace = true mctp-rs = { workspace = true, features = ["espi"] } +num_enum.workspace = true + +# TODO Service message type crates are a temporary dependency until we can parameterize +# the supported messages types at eSPI service creation time. +battery-service-messages.workspace = true +debug-service-messages.workspace = true +thermal-service-messages.workspace = true [target.'cfg(target_os = "none")'.dependencies] cortex-m-rt.workspace = true @@ -36,12 +44,15 @@ embassy-imxrt = { workspace = true, features = [ default = [] defmt = [ "dep:defmt", + "battery-service-messages/defmt", + "debug-service-messages/defmt", "embedded-services/defmt", "embassy-time/defmt", "embassy-time/defmt-timestamp-uptime", "embassy-sync/defmt", "embassy-imxrt/defmt", "mctp-rs/defmt", + "thermal-service-messages/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 74d9c0e87..82f22ddb2 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -1,15 +1,13 @@ -use core::mem::offset_of; use core::slice; +use crate::mctp::{HostRequest, HostResponse, OdpHeader, OdpMessageType, OdpService}; use core::borrow::BorrowMut; use embassy_imxrt::espi; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embedded_services::buffer::OwnedRef; -use embedded_services::comms::{self, EndpointID, External, Internal}; -use embedded_services::ec_type::message::{HostMsg, NotificationMsg, StdHostMsg, StdHostPayload, StdHostRequest}; -use embedded_services::ec_type::protocols::mctp; +use embedded_services::comms::{self, EndpointID, External}; use embedded_services::{GlobalRawMutex, debug, ec_type, error, info, trace}; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -20,12 +18,17 @@ const HOST_TX_QUEUE_SIZE: usize = 5; // REVISIT: When adding support for other platforms, refactor this as they don't have a notion of port IDs const OOB_PORT_ID: usize = 1; -// Should be as large as the largest possible MCTP packet and it's metadata. +// Should be as large as the largest possible MCTP packet and its metadata. const ASSEMBLY_BUF_SIZE: usize = 256; embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; ASSEMBLY_BUF_SIZE]); -type HostMsgInternal = (EndpointID, StdHostMsg); +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct HostResponseMessage { + pub source_endpoint: EndpointID, + pub message: HostResponse, +} #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -36,8 +39,8 @@ pub enum Error { pub struct Service<'a> { endpoint: comms::Endpoint, - ec_memory: Mutex, - host_tx_queue: Channel, + _ec_memory: Mutex, + host_tx_queue: Channel, assembly_buf_owned_ref: OwnedRef<'a, u8>, } @@ -45,143 +48,39 @@ impl Service<'_> { pub fn new(ec_memory: &'static mut ec_type::structure::ECMemory) -> Self { Service { endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), - ec_memory: Mutex::new(ec_memory), + _ec_memory: Mutex::new(ec_memory), host_tx_queue: Channel::new(), assembly_buf_owned_ref: assembly_buf::get_mut().unwrap(), } } - async fn route_to_service(&self, offset: usize, length: usize) -> Result<(), ec_type::Error> { - let mut offset = offset; - let mut length = length; - - if offset + length > size_of::() { - return Err(ec_type::Error::InvalidLocation); - } - - while length > 0 { - if (offset >= offset_of!(ec_type::structure::ECMemory, ver) - && offset < offset_of!(ec_type::structure::ECMemory, ver) + size_of::()) - || (offset >= offset_of!(ec_type::structure::ECMemory, caps) - && offset - < offset_of!(ec_type::structure::ECMemory, caps) - + size_of::()) - { - // This is a read-only section. eSPI master should not write to it. - return Err(ec_type::Error::InvalidLocation); - } else if offset >= offset_of!(ec_type::structure::ECMemory, batt) - && offset < offset_of!(ec_type::structure::ECMemory, batt) + size_of::() - { - self.route_to_battery_service(&mut offset, &mut length).await?; - } else if offset >= offset_of!(ec_type::structure::ECMemory, therm) - && offset < offset_of!(ec_type::structure::ECMemory, therm) + size_of::() - { - self.route_to_thermal_service(&mut offset, &mut length).await?; - } else if offset >= offset_of!(ec_type::structure::ECMemory, alarm) - && offset < offset_of!(ec_type::structure::ECMemory, alarm) + size_of::() - { - self.route_to_time_alarm_service(&mut offset, &mut length).await?; - } - } - - Ok(()) - } - - async fn route_to_battery_service(&self, offset: &mut usize, length: &mut usize) -> Result<(), ec_type::Error> { - let msg = { - let memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - ec_type::mem_map_to_battery_msg(&memory_map, offset, length)? - }; - - comms::send( - EndpointID::External(External::Host), - EndpointID::Internal(Internal::Battery), - &msg, - ) - .await - .unwrap(); - - Ok(()) - } - - async fn route_to_thermal_service(&self, offset: &mut usize, length: &mut usize) -> Result<(), ec_type::Error> { - let msg = { - let memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - ec_type::mem_map_to_thermal_msg(&memory_map, offset, length)? - }; - - comms::send( - EndpointID::External(External::Host), - EndpointID::Internal(Internal::Thermal), - &msg, - ) - .await - .unwrap(); - - Ok(()) - } - - async fn route_to_time_alarm_service(&self, offset: &mut usize, length: &mut usize) -> Result<(), ec_type::Error> { - let msg = { - let memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - ec_type::mem_map_to_time_alarm_msg(&memory_map, offset, length)? - }; - - comms::send( - EndpointID::External(External::Host), - EndpointID::Internal(Internal::TimeAlarm), - &msg, - ) - .await - .unwrap(); - - Ok(()) - } - - pub(crate) async fn wait_for_subsystem_msg(&self) -> HostMsgInternal { + pub(crate) async fn wait_for_response(&self) -> HostResponseMessage { self.host_tx_queue.receive().await } - pub(crate) async fn process_subsystem_msg(&self, espi: &mut espi::Espi<'static>, host_msg: HostMsgInternal) { - let (endpoint, host_msg) = host_msg; - match host_msg { - HostMsg::Notification(notification_msg) => self.process_notification_to_host(espi, ¬ification_msg).await, - HostMsg::Response(acpi_msg_comms) => self.process_response_to_host(espi, &acpi_msg_comms, endpoint).await, - } - } - - async fn process_notification_to_host(&self, espi: &mut espi::Espi<'_>, notification: &NotificationMsg) { - espi.irq_push(notification.offset).await; - info!("espi: Notification id {} sent to Host!", notification.offset); - } + // TODO The notification system was not actually used, so this is currently dead code. + // We need to implement some interface for triggering notifications from other subsystems, and it may do something like this: + // + // async fn process_notification_to_host(&self, espi: &mut espi::Espi<'_>, notification: &NotificationMsg) { + // espi.irq_push(notification.offset).await; + // info!("espi: Notification id {} sent to Host!", notification.offset); + // } async fn serialize_packet_from_subsystem( &self, espi: &mut espi::Espi<'static>, - response: &StdHostRequest, - endpoint: EndpointID, + response: &HostResponseMessage, ) -> Result<(), Error> { let mut assembly_buf_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; let pkt_ctx_buf = assembly_buf_access.borrow_mut(); let mut mctp_ctx = mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, pkt_ctx_buf); + let source_service: OdpService = + OdpService::try_from(response.source_endpoint).map_err(|_| Error::Serialize)?; + let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { source_endpoint_id: mctp_rs::EndpointId::Id(0x80), - destination_endpoint_id: match endpoint { - EndpointID::Internal(Internal::Battery) => mctp_rs::EndpointId::Id(8), - EndpointID::Internal(Internal::Thermal) => mctp_rs::EndpointId::Id(9), - EndpointID::Internal(Internal::Debug) => mctp_rs::EndpointId::Id(10), - _ => mctp_rs::EndpointId::Id(0x80), - }, + destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), // TODO We're currently using this incorrectly - it should be the bus address of the host. Revisit once we have assigned a bus address to the host. packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); @@ -193,21 +92,17 @@ impl Service<'_> { }, // Medium-specific context }; - let header = mctp::OdpHeader { - request_bit: false, - datagram_bit: false, - service: match endpoint { - EndpointID::Internal(Internal::Battery) => mctp::OdpService::Battery, - EndpointID::Internal(Internal::Thermal) => mctp::OdpService::Thermal, - EndpointID::Internal(Internal::Debug) => mctp::OdpService::Debug, - _ => mctp::OdpService::Debug, + let header = OdpHeader { + message_type: OdpMessageType::Response { + is_error: !response.message.is_ok(), }, - command_code: response.command.into(), - completion_code: Default::default(), + is_datagram: false, + service: source_service, + message_id: response.message.discriminant(), }; let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, response.payload)) + .serialize_packet(reply_context, (header, response.message.clone())) .map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); Error::Serialize @@ -245,28 +140,25 @@ impl Service<'_> { espi.oob_write_data(OOB_PORT_ID, packet.len() as u8) } - fn send_mctp_error_response(&self, endpoint: EndpointID, espi: &mut espi::Espi<'static>) { - // SAFETY: Unwrap is safe here as battery will always be supported. - // Data is ACPI payload [version, instance, reserved (error status), command] - let (final_packet, final_packet_size) = mctp::build_mctp_header(&[0, 0, 0, 1], 4, endpoint, true, true) - .expect("Unexpected error building MCTP header"); - - if let Err(e) = self.write_to_hw(espi, &final_packet[..final_packet_size]) { - error!("Critical error sending error response: {:?}", e); - } + async fn send_mctp_error_response(&self, endpoint: EndpointID, espi: &mut espi::Espi<'static>) { + // TODO we may want to add more detail in future, but that will require more integration with the debug service + let error_msg = HostResponseMessage { + source_endpoint: endpoint, + message: HostResponse::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), + }; + self.serialize_packet_from_subsystem(espi, &error_msg) + .await + .unwrap_or_else(|_| { + error!("Critical error reporting MCTP protocol error to host!"); + }); } - async fn process_response_to_host( - &self, - espi: &mut espi::Espi<'static>, - response: &StdHostRequest, - endpoint: EndpointID, - ) { - match self.serialize_packet_from_subsystem(espi, response, endpoint).await { + pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResponseMessage) { + match self.serialize_packet_from_subsystem(espi, &response).await { Err(e) => { error!("Packet serialize error {:?}", e); - self.send_mctp_error_response(endpoint, espi); + self.send_mctp_error_response(response.source_endpoint, espi).await; } Ok(()) => { trace!("Full packet successfully sent to host!") @@ -277,35 +169,29 @@ impl Service<'_> { pub(crate) fn endpoint(&self) -> &comms::Endpoint { &self.endpoint } + + fn queue_response_to_host( + &self, + source_endpoint: EndpointID, + message: HostResponse, + ) -> Result<(), comms::MailboxDelegateError> { + debug!("Espi service: recvd response"); + self.host_tx_queue + .try_send(HostResponseMessage { + source_endpoint, + message, + }) + .map_err(|_| comms::MailboxDelegateError::BufferFull)?; + + Ok(()) + } } impl comms::MailboxDelegate for Service<'_> { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(msg) = message.data.get::() { - let host_msg = (message.from, *msg); - debug!("Espi service: recvd acpi response"); - if self.host_tx_queue.try_send(host_msg).is_err() { - return Err(comms::MailboxDelegateError::BufferFull); - } - } else { - let mut memory_map = self - .ec_memory - .try_lock() - .expect("Messages handled one after another, should be infallible."); - if let Some(msg) = message.data.get::() { - ec_type::update_capabilities_section(msg, &mut memory_map); - } else if let Some(msg) = message.data.get::() { - ec_type::update_battery_section(msg, &mut memory_map); - } else if let Some(msg) = message.data.get::() { - ec_type::update_thermal_section(msg, &mut memory_map); - } else if let Some(msg) = message.data.get::() { - ec_type::update_time_alarm_section(msg, &mut memory_map); - } else { - return Err(comms::MailboxDelegateError::MessageNotFound); - } - } - - Ok(()) + crate::mctp::try_route_request_to_comms(message, |source_endpoint, message| { + self.queue_response_to_host(source_endpoint, message) + }) } } @@ -323,19 +209,7 @@ pub(crate) async fn process_controller_event( port_event.port, port_event.direction, port_event.offset, port_event.base_addr, port_event.length, ); - // If it is a peripheral channel write, then we need to notify the service - if port_event.direction { - let res = espi_service - .route_to_service(port_event.offset, port_event.length) - .await; - - if res.is_err() { - error!( - "eSPI master send invalid offset: {} length: {}", - port_event.offset, port_event.length - ); - } - } + // We're not handling these - communication is all through OOB espi.complete_port(port_event.port); } @@ -358,99 +232,64 @@ pub(crate) async fn process_controller_event( #[cfg(feature = "defmt")] debug!("OOB message: {:02X}", &src_slice[0..]); - let host_request: StdHostRequest; - let endpoint: EndpointID; - - { - let mut assembly_access = espi_service - .assembly_buf_owned_ref - .borrow_mut() - .map_err(Error::Buffer)?; - // let mut comms_access = espi_service.comms_buf_owned_ref.borrow_mut(); - let mut mctp_ctx = mctp_rs::MctpPacketContext::::new( - SmbusEspiMedium, - assembly_access.borrow_mut(), - ); - - match mctp_ctx.deserialize_packet(with_pec) { - Ok(Some(message)) => { - #[cfg(feature = "defmt")] - trace!("MCTP packet successfully deserialized"); - - match message.parse_as::() { - Ok((header, body)) => { - host_request = StdHostRequest { - command: header.command_code.into(), - status: header.completion_code.into(), - payload: body, - }; - endpoint = match header.service { - mctp::OdpService::Battery => { - EndpointID::Internal(embedded_services::comms::Internal::Battery) - } - mctp::OdpService::Thermal => { - EndpointID::Internal(embedded_services::comms::Internal::Thermal) - } - mctp::OdpService::Debug => { - EndpointID::Internal(embedded_services::comms::Internal::Debug) - } - }; - #[cfg(feature = "defmt")] - trace!( - "Host Request: Service {:?}, Command {:?}, Status {:?}", - endpoint, host_request.command, host_request.status, - ); - } - Err(_e) => { - #[cfg(feature = "defmt")] - error!("MCTP ODP type malformed"); - espi.complete_port(port_event.port); - - // REVISIT: An error here means that we couldn't decode the incoming message, - // thus we don't know what subsystem the message was meant for. For now, - // hardcode Debug but we might need a special endpoint for error. - espi_service.send_mctp_error_response( - EndpointID::Internal(embedded_services::comms::Internal::Debug), - espi, - ); - return Err(Error::Serialize); - } + let mut assembly_access = espi_service + .assembly_buf_owned_ref + .borrow_mut() + .map_err(Error::Buffer)?; + let mut mctp_ctx = + mctp_rs::MctpPacketContext::::new(SmbusEspiMedium, assembly_access.borrow_mut()); + + match mctp_ctx.deserialize_packet(with_pec) { + Ok(Some(message)) => { + #[cfg(feature = "defmt")] + trace!("MCTP packet successfully deserialized"); + + match message.parse_as::() { + Ok((header, body)) => { + let target_endpoint = header.service.get_endpoint_id(); + #[cfg(feature = "defmt")] + trace!( + "Host Request: Service {:?}, Command {:?}", + target_endpoint, header.message_id, + ); + + drop(assembly_access); + + espi.complete_port(port_event.port); + + body.send_to_endpoint(&espi_service.endpoint, target_endpoint) + .await + .expect("result error type is infallible"); + info!("MCTP packet forwarded to service: {:?}", target_endpoint); + } + Err(_e) => { + #[cfg(feature = "defmt")] + error!("MCTP ODP type malformed: {}", _e); + + espi.complete_port(port_event.port); + + return Err(Error::Serialize); } - } - Ok(None) => { - // Partial message, waiting for more packets - error!("Partial msg, should not happen"); - espi.complete_port(OOB_PORT_ID); - - // REVISIT: An error here means that we couldn't decode the incoming message, - // thus we don't know what subsystem the message was meant for. For now, - // hardcode Debug but we might need a special endpoint for error. - espi_service.send_mctp_error_response( - EndpointID::Internal(embedded_services::comms::Internal::Debug), - espi, - ); - return Err(Error::Serialize); - } - Err(_e) => { - // Handle protocol or medium error - error!("MCTP packet malformed"); - espi.complete_port(OOB_PORT_ID); - - // REVISIT: An error here means that we couldn't decode the incoming message, - // thus we don't know what subsystem the message was meant for. For now, - // hardcode Debug but we might need a special endpoint for error. - espi_service.send_mctp_error_response( - EndpointID::Internal(embedded_services::comms::Internal::Debug), - espi, - ); - return Err(Error::Serialize); } } - } + Ok(None) => { + // Partial message, waiting for more packets + error!("Partial msg, should not happen"); + espi.complete_port(OOB_PORT_ID); - espi.complete_port(port_event.port); - espi_service.endpoint.send(endpoint, &host_request).await.unwrap(); - info!("MCTP packet forwarded to service: {:?}", endpoint); + return Err(Error::Serialize); + } + Err(_e) => { + // Handle protocol or medium error + error!("MCTP packet malformed"); + + #[cfg(feature = "defmt")] + error!("error code: {:?}", _e); + espi.complete_port(OOB_PORT_ID); + + return Err(Error::Serialize); + } + } } else { espi.complete_port(port_event.port); } diff --git a/espi-service/src/lib.rs b/espi-service/src/lib.rs index 658cb1c0b..88a69717e 100644 --- a/espi-service/src/lib.rs +++ b/espi-service/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::unwrap_used)] mod espi_service; +mod mctp; pub mod task; pub use espi_service::*; diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs new file mode 100644 index 000000000..9820f58d8 --- /dev/null +++ b/espi-service/src/mctp.rs @@ -0,0 +1,354 @@ +use bitfield::bitfield; +use core::convert::Infallible; +use embedded_services::{ + comms, + relay::{SerializableMessage, SerializableResponse}, +}; +use mctp_rs::smbus_espi::SmbusEspiMedium; +use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub(crate) enum OdpService { + Battery = 0x08, + Thermal = 0x09, + Debug = 0x0A, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum MctpError { + // The endpoint ID does not correspond to a known service + UnknownEndpointId, +} + +impl TryFrom for OdpService { + type Error = MctpError; + fn try_from(endpoint_id: comms::EndpointID) -> Result { + match endpoint_id { + comms::EndpointID::Internal(comms::Internal::Battery) => Ok(OdpService::Battery), + comms::EndpointID::Internal(comms::Internal::Thermal) => Ok(OdpService::Thermal), + comms::EndpointID::Internal(comms::Internal::Debug) => Ok(OdpService::Debug), + _ => Err(MctpError::UnknownEndpointId), + } + } +} + +impl OdpService { + pub fn get_endpoint_id(&self) -> comms::EndpointID { + match self { + OdpService::Battery => comms::EndpointID::Internal(comms::Internal::Battery), + OdpService::Thermal => comms::EndpointID::Internal(comms::Internal::Thermal), + OdpService::Debug => comms::EndpointID::Internal(comms::Internal::Debug), + } + } +} + +// TODO We'd ideally like these types to be passed in as a generic or something when the eSPI service is instantiated +// so the eSPI service can be extended to handle 3rd party message types without needing to fork the eSPI service, +// but that's dependant on us migrating to have storage for the eSPI service be allocated by the caller of init() +// rather than statically allocated inside this module, so for now we accept this hardcoded list of supported message +// types, and we can maybe convert this to a macro that accepts a list of types at some point. +// New services should follow the pattern of defining their own message crates using the request/response +// traits, and the only additions here should be the mapping between message types and endpoint IDs. +// +// Additionally, we probably want some sort of macro that can generate most or all of this from a table mapping service IDs +// to (request type, response type, comms endpoint) tuples for maintainability. +// +pub(crate) enum HostRequest { + Battery(battery_service_messages::AcpiBatteryRequest), + Debug(debug_service_messages::DebugRequest), + Thermal(thermal_service_messages::ThermalRequest), +} + +impl MctpMessageTrait<'_> for HostRequest { + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + type Header = OdpHeader; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + HostRequest::Battery(request) => request + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize battery request")), + + HostRequest::Debug(request) => request + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize debug request")), + + HostRequest::Thermal(request) => request + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize thermal request")), + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + Ok(match header.service { + OdpService::Battery => Self::Battery( + battery_service_messages::AcpiBatteryRequest::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError("Could not parse battery request"))?, + ), + OdpService::Debug => Self::Debug( + debug_service_messages::DebugRequest::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError("Could not parse debug request"))?, + ), + OdpService::Thermal => Self::Thermal( + thermal_service_messages::ThermalRequest::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError("Could not parse thermal request"))?, + ), + }) + } +} + +impl HostRequest { + pub(crate) async fn send_to_endpoint( + &self, + source_endpoint: &comms::Endpoint, + destination_endpoint_id: comms::EndpointID, + ) -> Result<(), Infallible> { + match self { + HostRequest::Battery(request) => source_endpoint.send(destination_endpoint_id, request).await, + HostRequest::Debug(request) => source_endpoint.send(destination_endpoint_id, request).await, + HostRequest::Thermal(request) => source_endpoint.send(destination_endpoint_id, request).await, + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum HostResponse { + Battery(Result), + Debug(Result), + Thermal(Result), +} + +impl MctpMessageTrait<'_> for HostResponse { + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + type Header = OdpHeader; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + HostResponse::Battery(response) => response + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize battery response")), + + HostResponse::Debug(response) => response + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize debug response")), + + HostResponse::Thermal(response) => response + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize thermal response")), + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + Ok(match header.service { + OdpService::Battery => { + if let Ok(success) = + battery_service_messages::AcpiBatteryResponse::deserialize(header.message_id, buffer) + { + Self::Battery(Ok(success)) + } else { + let error = battery_service_messages::AcpiBatteryError::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError("Could not parse battery error response"))?; + Self::Battery(Err(error)) + } + } + OdpService::Debug => { + if let Ok(success) = debug_service_messages::DebugResponse::deserialize(header.message_id, buffer) { + Self::Debug(Ok(success)) + } else { + let error = debug_service_messages::DebugError::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError("Could not parse debug error response"))?; + Self::Debug(Err(error)) + } + } + OdpService::Thermal => { + if let Ok(success) = thermal_service_messages::ThermalResponse::deserialize(header.message_id, buffer) { + Self::Thermal(Ok(success)) + } else { + let error = thermal_service_messages::ThermalError::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError("Could not parse thermal error response"))?; + Self::Thermal(Err(error)) + } + } + }) + } +} + +impl HostResponse { + pub(crate) fn discriminant(&self) -> u16 { + match self { + HostResponse::Battery(response) => response.discriminant(), + HostResponse::Debug(response) => response.discriminant(), + HostResponse::Thermal(response) => response.discriminant(), + } + } + + pub(crate) fn is_ok(&self) -> bool { + match self { + HostResponse::Battery(response) => response.is_ok(), + HostResponse::Debug(response) => response.is_ok(), + HostResponse::Thermal(response) => response.is_ok(), + } + } +} + +/// Attempt to route the provided message to the service that is registered to handle it based on its type. +pub(crate) fn try_route_request_to_comms( + message: &comms::Message, + send_fn: impl FnOnce(comms::EndpointID, HostResponse) -> Result<(), comms::MailboxDelegateError>, +) -> Result<(), comms::MailboxDelegateError> { + // TODO we're going to have a bunch of types that all implement the SerializableResponse trait; in C++ I'd reach for dynamic_cast or a pointer-to-interface, + // but not sure how to do that with Rust's Any - it seems like it requires a concrete type rather than a trait to cast. Is there a cleaner way to + // say "if the message implements the SerializableResponse trait" so we don't have to spell out all the types here? + // + if let Some(msg) = message + .data + .get::>() + { + send_fn( + comms::EndpointID::Internal(comms::Internal::Battery), + HostResponse::Battery(*msg), + )?; + Ok(()) + } else if let Some(msg) = message + .data + .get::>() + { + send_fn( + comms::EndpointID::Internal(comms::Internal::Debug), + HostResponse::Debug(*msg), + )?; + Ok(()) + } else if let Some(msg) = message + .data + .get::>() + { + send_fn( + comms::EndpointID::Internal(comms::Internal::Thermal), + HostResponse::Thermal(*msg), + )?; + Ok(()) + } else { + Err(comms::MailboxDelegateError::MessageNotFound) + } +} + +bitfield! { + /// Raw bitfield of possible port status events + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct OdpHeaderWireFormat(u32); + impl Debug; + impl new; + /// If true, represents a request; otherwise, represents a response + is_request, set_is_request: 25; + + // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + is_datagram, set_is_datagram: 24; + + /// The service ID that this message is related to + /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. + u8, service_id, set_service_id: 23, 16; + + /// On responses, indicates if the response message is an error. Unused on requests. + is_error, set_is_error: 15; + + /// The message type/discriminant + u16, message_id, set_message_id: 14, 0; + +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum OdpMessageType { + Request, + Response { is_error: bool }, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct OdpHeader { + pub message_type: OdpMessageType, + pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + pub service: OdpService, + pub message_id: u16, +} + +impl From for OdpHeaderWireFormat { + fn from(src: OdpHeader) -> Self { + Self::new( + matches!(src.message_type, OdpMessageType::Request), + src.is_datagram, + src.service.into(), + match src.message_type { + OdpMessageType::Request => false, // unused on requests + OdpMessageType::Response { is_error } => is_error, + }, + src.message_id, + ) + } +} + +impl TryFrom for OdpHeader { + type Error = MctpPacketError; + + fn try_from(src: OdpHeaderWireFormat) -> Result { + let service = OdpService::try_from(src.service_id()) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; + + let message_type = if src.is_request() { + OdpMessageType::Request + } else { + OdpMessageType::Response { + is_error: src.is_error(), + } + }; + + Ok(OdpHeader { + message_type, + is_datagram: src.is_datagram(), + service, + message_id: src.message_id(), + }) + } +} + +impl MctpMessageHeaderTrait for OdpHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let wire_format = OdpHeaderWireFormat::from(self); + let bytes = wire_format.0.to_be_bytes(); + buffer + .get_mut(0..bytes.len()) + .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? + .copy_from_slice(&bytes); + + Ok(bytes.len()) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + let bytes = buffer + .get(0..core::mem::size_of::()) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; + let raw = u32::from_be_bytes( + bytes + .try_into() + .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + ); + + let parsed_wire_format = OdpHeaderWireFormat(raw); + let header = OdpHeader::try_from(parsed_wire_format) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; + + Ok(( + header, + buffer + .get(core::mem::size_of::()..) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + )) + } +} diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs index ad1ee7fab..dc7ac53c4 100644 --- a/espi-service/src/task.rs +++ b/espi-service/src/task.rs @@ -34,14 +34,14 @@ pub async fn espi_service( .unwrap(); loop { - let event = select(espi.wait_for_event(), espi_service.wait_for_subsystem_msg()).await; + let event = select(espi.wait_for_event(), espi_service.wait_for_response()).await; match event { embassy_futures::select::Either::First(controller_event) => { process_controller_event(&mut espi, espi_service, controller_event).await? } embassy_futures::select::Either::Second(host_msg) => { - espi_service.process_subsystem_msg(&mut espi, host_msg).await + espi_service.process_response_to_host(&mut espi, host_msg).await } } } diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 8cba71697..c1215bae5 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -102,6 +102,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ + "battery-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -114,6 +115,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + [[package]] name = "bincode" version = "2.0.1" @@ -123,14 +134,6 @@ dependencies = [ "unty", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -351,6 +354,15 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "debug-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -779,9 +791,9 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags 2.9.1", "num-traits", "num_enum", @@ -793,8 +805,11 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ + "battery-service-messages", + "bitfield 0.17.0", "cortex-m", "cortex-m-rt", + "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-imxrt", @@ -802,6 +817,8 @@ dependencies = [ "embassy-time", "embedded-services", "mctp-rs", + "num_enum", + "thermal-service-messages", ] [[package]] @@ -1033,9 +1050,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1506,6 +1523,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "thermal-service-messages" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "num_enum", + "uuid", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index fc36a2460..d9e539e44 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -117,14 +117,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -769,9 +761,9 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags 2.9.4", "num-traits", "num_enum", @@ -1008,9 +1000,9 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", @@ -1353,6 +1345,7 @@ dependencies = [ "embedded-cfu-protocol", "embedded-hal 1.0.0", "embedded-hal-async", + "embedded-mcu-hal", "embedded-services", "embedded-usb-pd", "futures", diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 2bfd797bc..c27b34f6c 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -74,6 +74,7 @@ platform-service = { path = "../../platform-service", features = [ "defmt", "imxrt685", ] } +embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } # Needed otherwise cargo will pull from git [patch."https://github.com/OpenDevicePartnership/embedded-services"] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 0a917cedb..38bbfdb89 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -116,6 +116,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ + "battery-service-messages", "embassy-futures", "embassy-sync", "embassy-time", @@ -128,6 +129,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + [[package]] name = "bbq2" version = "0.4.2" @@ -389,6 +399,7 @@ version = "0.1.0" dependencies = [ "bbq2", "critical-section", + "debug-service-messages", "defmt 0.3.100", "embassy-sync", "embassy-time", @@ -397,6 +408,14 @@ dependencies = [ "rtt-target", ] +[[package]] +name = "debug-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-services", + "num_enum", +] + [[package]] name = "defmt" version = "0.3.100" @@ -1514,6 +1533,7 @@ dependencies = [ "cfu-service", "critical-section", "debug-service", + "debug-service-messages", "defmt 0.3.100", "embassy-executor", "embassy-futures", @@ -1534,6 +1554,7 @@ dependencies = [ "power-policy-service", "static_cell", "thermal-service", + "thermal-service-messages", "type-c-service", ] @@ -1607,6 +1628,16 @@ dependencies = [ "heapless", "log", "mctp-rs", + "thermal-service-messages", + "uuid", +] + +[[package]] +name = "thermal-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-services", + "num_enum", "uuid", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 4140ec02d..39ffeb396 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -39,6 +39,7 @@ type-c-service = { path = "../../type-c-service", features = ["log"] } embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log"] } +thermal-service-messages = { path = "../../thermal-service-messages" } env_logger = "0.9.0" log = "0.4.14" @@ -50,6 +51,7 @@ embedded-hal-mock = { version = "0.11.1", features = ["embedded-hal-async"] } critical-section = { version = "1.1", features = ["std"] } debug-service = { path = "../../debug-service" } +debug-service-messages = { path = "../../debug-service-messages" } [lib] name = "std_examples" diff --git a/examples/std/src/bin/debug.rs b/examples/std/src/bin/debug.rs index 9944d98dc..8899d3710 100644 --- a/examples/std/src/bin/debug.rs +++ b/examples/std/src/bin/debug.rs @@ -11,13 +11,11 @@ defmt::timestamp!("{=u64}", { 0u64 }); // Mock eSPI transport service mod espi_service { use core::borrow::BorrowMut; + use debug_service_messages::{DebugRequest, DebugResponse, DebugResult, STD_DEBUG_BUF_SIZE}; use embassy_sync::{once_lock::OnceLock, signal::Signal}; use embedded_services::GlobalRawMutex; use embedded_services::buffer::OwnedRef; use embedded_services::comms::{self, EndpointID, External, Internal}; - use embedded_services::ec_type::message::{ - HostMsg, NotificationMsg, STD_DEBUG_BUF_SIZE, StdHostMsg, StdHostPayload, StdHostRequest, - }; use log::{info, trace}; // Max defmt payload we expect to shuttle in this mock @@ -26,6 +24,9 @@ mod espi_service { // Static request buffer used to build the "GetDebugBuffer" payload embedded_services::define_static_buffer!(debug_req_buf, u8, [0u8; 32]); + // TODO Notifications are not currently implemented. Remove this and replace it with the real notification struct when we do implement it. + pub struct NotificationMsg; + pub struct Service { endpoint: comms::Endpoint, notify: &'static Signal, @@ -48,30 +49,31 @@ mod espi_service { impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(host_msg) = message.data.get::() { - match host_msg { - HostMsg::Notification(n) => { - info!( - "mock eSPI got Host Notification: offset={} from {:?}", - n.offset, message.from - ); - // Defer to async host task via signal (receive is not async) - self.notify.signal(*n); - Ok(()) - } - HostMsg::Response(acpi) => { - // Stage the response bytes into the mock OOB buffer for the host - let mut access = self.resp_owned.borrow_mut().unwrap(); - let buf: &mut [u8] = core::borrow::BorrowMut::borrow_mut(&mut access); - if let StdHostPayload::DebugGetMsgsResponse { debug_buf } = acpi.payload { - let copy_len = core::cmp::min(debug_buf.len(), buf.len()); - buf[..copy_len].copy_from_slice(&debug_buf[..copy_len]); - trace!("mock eSPI staged {copy_len} response bytes for host"); - self.resp_len.signal(copy_len); - } - Ok(()) + if let Some(debug_result) = message.data.get::() { + // Stage the response bytes into the mock OOB buffer for the host + let mut access = self.resp_owned.borrow_mut().unwrap(); + let buf: &mut [u8] = core::borrow::BorrowMut::borrow_mut(&mut access); + + let debug_response = debug_result.map_err(|_| comms::MailboxDelegateError::Other)?; + match debug_response { + DebugResponse::DebugGetMsgsResponse { debug_buf } => { + let copy_len = core::cmp::min(debug_buf.len(), buf.len()); + buf[..copy_len].copy_from_slice(&debug_buf[..copy_len]); + trace!("mock eSPI staged {copy_len} response bytes for host"); + self.resp_len.signal(copy_len); } } + + Ok(()) + // TODO notification functionality is currently not implemented. Restore this when we implement it. + // } else if let Some(debug_notification) = message.data.get::() { + // info!( + // "mock eSPI got Host Notification: offset={} from {:?}", + // n.offset, message.from + // ); + // // Defer to async host task via signal (receive is not async) + // self.notify.signal(*n); + // Ok(()) } else { Err(comms::MailboxDelegateError::MessageNotFound) } @@ -111,11 +113,8 @@ mod espi_service { loop { // Wait for a device notification via the mock eSPI transport - let n: NotificationMsg = wait_host_notification().await; - info!( - "eSPI: got Host Notification (offset={}), sending OOB request/ACK to Debug", - n.offset - ); + let _n: NotificationMsg = wait_host_notification().await; + info!("eSPI: got Host Notification, sending OOB request/ACK to Debug",); // Build the ACPI/MCTP-style request payload for the Debug service let request = b"GetDebugBuffer"; @@ -130,13 +129,7 @@ mod espi_service { let _ = comms::send( EndpointID::External(External::Host), EndpointID::Internal(Internal::Debug), - &StdHostRequest { - command: embedded_services::ec_type::message::OdpCommand::Debug( - embedded_services::ec_type::protocols::debug::DebugCmd::GetMsgs, - ), - status: 0, - payload: StdHostPayload::DebugGetMsgsRequest, - }, + &DebugRequest::DebugGetMsgsRequest {}, ) .await; diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 0cc765cc7..653ca8204 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -12,6 +12,7 @@ use static_cell::StaticCell; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use thermal_service as ts; +use thermal_service_messages::ThermalRequest; use ts::mptf; // Mock host service @@ -20,6 +21,7 @@ mod host { use embedded_services::comms::{self, Endpoint, EndpointID, External, MailboxDelegate}; use log::{info, warn}; use thermal_service as ts; + use thermal_service_messages::{ThermalResponse, ThermalResult}; use ts::mptf; pub struct Host { @@ -35,13 +37,13 @@ mod host { } } - fn handle_response(&self, response: mptf::Response) { - match response.data { - mptf::ResponseData::GetTmp(tmp) => { - info!("Host received temperature: {} °C", ts::utils::dk_to_c(tmp)) + fn handle_response(&self, response: ThermalResponse) { + match response { + ThermalResponse::ThermalGetTmpResponse { temperature } => { + info!("Host received temperature: {} °C", ts::utils::dk_to_c(temperature)) } - mptf::ResponseData::GetVar(_status, value) => { - info!("Host received fan RPM: {value}") + ThermalResponse::ThermalGetVarResponse { val } => { + info!("Host received fan RPM: {val}") } _ => info!("Received MPTF response: {response:?}"), } @@ -50,8 +52,8 @@ mod host { impl MailboxDelegate for Host { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(&response) = message.data.get::() { - self.handle_response(response); + if let Some(&result) = message.data.get::() { + self.handle_response(result.map_err(|_| comms::MailboxDelegateError::Other)?); Ok(()) } else if let Some(¬ification) = message.data.get::() { warn!("Received notification: {notification:?}"); @@ -215,7 +217,15 @@ async fn host() { // Set thresholds to 40 °C (3131 deciKelvin) host.tp - .send(thermal_id, &mptf::Request::SetThrs(0, 0, 0, 3131)) + .send( + thermal_id, + &ThermalRequest::ThermalSetThrsRequest { + instance_id: 0, + timeout: 0, + low: 0, + high: 3131, + }, + ) .await .unwrap(); Timer::after_millis(100).await; @@ -224,7 +234,12 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::SetVar(0, 4, mptf::uuid_standard::FAN_ON_TEMP, 3131), + &ThermalRequest::ThermalSetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_ON_TEMP, + set_var: 3131, + }, ) .await .unwrap(); @@ -234,7 +249,12 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::SetVar(0, 4, mptf::uuid_standard::FAN_RAMP_TEMP, 3231), + &ThermalRequest::ThermalSetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_RAMP_TEMP, + set_var: 3231, + }, ) .await .unwrap(); @@ -244,7 +264,12 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::SetVar(0, 4, mptf::uuid_standard::FAN_MAX_TEMP, 3531), + &ThermalRequest::ThermalSetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_MAX_TEMP, + set_var: 3531, + }, ) .await .unwrap(); @@ -255,7 +280,10 @@ async fn host() { host.alert.wait().await; info!("Host requesting temperature in response to threshold alert"); - host.tp.send(thermal_id, &mptf::Request::GetTmp(0)).await.unwrap(); + host.tp + .send(thermal_id, &ThermalRequest::ThermalGetTmpRequest { instance_id: 0 }) + .await + .unwrap(); // Need to wait briefly before send is fixed to propagate errors and we can handle retries Timer::after_millis(100).await; @@ -264,7 +292,11 @@ async fn host() { host.tp .send( thermal_id, - &mptf::Request::GetVar(0, 4, mptf::uuid_standard::FAN_CURRENT_RPM), + &ThermalRequest::ThermalGetVarRequest { + instance_id: 0, + len: 4, + var_uuid: mptf::uuid_standard::FAN_CURRENT_RPM, + }, ) .await .unwrap(); diff --git a/keyboard-service/src/lib.rs b/keyboard-service/src/lib.rs index 30d936a29..7f5f8612f 100644 --- a/keyboard-service/src/lib.rs +++ b/keyboard-service/src/lib.rs @@ -20,6 +20,7 @@ use embedded_services::hid; pub const HID_KB_ID: hid::DeviceId = hid::DeviceId(0); /// HID keyboard error. +#[derive(Debug)] pub enum KeyboardError { /// Rollover occurred Rollover, diff --git a/thermal-service-messages/Cargo.toml b/thermal-service-messages/Cargo.toml new file mode 100644 index 000000000..2d9dbc37e --- /dev/null +++ b/thermal-service-messages/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "thermal-service-messages" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embedded-services.workspace = true +num_enum.workspace = true +uuid.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt"] diff --git a/thermal-service-messages/src/lib.rs b/thermal-service-messages/src/lib.rs new file mode 100644 index 000000000..d0325a2fe --- /dev/null +++ b/thermal-service-messages/src/lib.rs @@ -0,0 +1,277 @@ +#![no_std] + +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// 16-bit variable length +pub type VarLen = u16; + +/// Instance ID +pub type InstanceId = u8; + +/// Time in milliseconds +pub type Milliseconds = u32; + +/// MPTF expects temperatures in tenth Kelvins +pub type DeciKelvin = u32; + +/// Standard MPTF requests expected by the thermal subsystem +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum ThermalCmd { + /// EC_THM_GET_TMP = 0x1 + GetTmp = 1, + /// EC_THM_SET_THRS = 0x2 + SetThrs = 2, + /// EC_THM_GET_THRS = 0x3 + GetThrs = 3, + /// EC_THM_SET_SCP = 0x4 + SetScp = 4, + /// EC_THM_GET_VAR = 0x5 + GetVar = 5, + /// EC_THM_SET_VAR = 0x6 + SetVar = 6, +} + +impl From<&ThermalRequest> for ThermalCmd { + fn from(request: &ThermalRequest) -> Self { + match request { + ThermalRequest::ThermalGetTmpRequest { .. } => ThermalCmd::GetTmp, + ThermalRequest::ThermalSetThrsRequest { .. } => ThermalCmd::SetThrs, + ThermalRequest::ThermalGetThrsRequest { .. } => ThermalCmd::GetThrs, + ThermalRequest::ThermalSetScpRequest { .. } => ThermalCmd::SetScp, + ThermalRequest::ThermalGetVarRequest { .. } => ThermalCmd::GetVar, + ThermalRequest::ThermalSetVarRequest { .. } => ThermalCmd::SetVar, + } + } +} + +impl From<&ThermalResponse> for ThermalCmd { + fn from(response: &ThermalResponse) -> Self { + match response { + ThermalResponse::ThermalGetTmpResponse { .. } => ThermalCmd::GetTmp, + ThermalResponse::ThermalSetThrsResponse => ThermalCmd::SetThrs, + ThermalResponse::ThermalGetThrsResponse { .. } => ThermalCmd::GetThrs, + ThermalResponse::ThermalSetScpResponse => ThermalCmd::SetScp, + ThermalResponse::ThermalGetVarResponse { .. } => ThermalCmd::GetVar, + ThermalResponse::ThermalSetVarResponse => ThermalCmd::SetVar, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ThermalRequest { + ThermalGetTmpRequest { + instance_id: u8, + }, + ThermalSetThrsRequest { + instance_id: u8, + timeout: Milliseconds, + low: DeciKelvin, + high: DeciKelvin, + }, + ThermalGetThrsRequest { + instance_id: u8, + }, + ThermalSetScpRequest { + instance_id: u8, + policy_id: u32, + acoustic_lim: u32, + power_lim: u32, + }, + ThermalGetVarRequest { + instance_id: u8, + len: VarLen, // TODO why is there a len here? as far as I can tell we're always discarding it, and I think values are only u32? + var_uuid: uuid::Bytes, + }, + ThermalSetVarRequest { + instance_id: u8, + len: VarLen, // TODO why is there a len here? as far as I can tell we're always discarding it, and I think values are only u32? + var_uuid: uuid::Bytes, + set_var: u32, + }, +} + +// TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? +impl SerializableMessage for ThermalRequest { + fn serialize(self, _buffer: &mut [u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to serialize requests on the EC side", + )) + } + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match ThermalCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + ThermalCmd::GetTmp => Self::ThermalGetTmpRequest { + instance_id: safe_get_u8(buffer, 0)?, + }, + ThermalCmd::SetThrs => Self::ThermalSetThrsRequest { + instance_id: safe_get_u8(buffer, 0)?, + timeout: safe_get_dword(buffer, 1)?, + low: safe_get_dword(buffer, 5)?, + high: safe_get_dword(buffer, 9)?, + }, + ThermalCmd::GetThrs => Self::ThermalGetThrsRequest { + instance_id: safe_get_u8(buffer, 0)?, + }, + ThermalCmd::SetScp => Self::ThermalSetScpRequest { + instance_id: safe_get_u8(buffer, 0)?, + policy_id: safe_get_dword(buffer, 1)?, + acoustic_lim: safe_get_dword(buffer, 5)?, + power_lim: safe_get_dword(buffer, 9)?, + }, + ThermalCmd::GetVar => Self::ThermalGetVarRequest { + instance_id: safe_get_u8(buffer, 0)?, + len: safe_get_u16(buffer, 1)?, + var_uuid: safe_get_uuid(buffer, 3)?, + }, + ThermalCmd::SetVar => Self::ThermalSetVarRequest { + instance_id: safe_get_u8(buffer, 0)?, + len: safe_get_u16(buffer, 1)?, + var_uuid: safe_get_uuid(buffer, 3)?, + set_var: safe_get_dword(buffer, 19)?, + }, + }, + ) + } + + fn discriminant(&self) -> u16 { + let cmd: ThermalCmd = self.into(); + cmd.into() + } +} + +#[derive(PartialEq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ThermalResponse { + ThermalGetTmpResponse { + temperature: DeciKelvin, + }, + ThermalSetThrsResponse, + ThermalGetThrsResponse { + timeout: Milliseconds, + low: DeciKelvin, + high: DeciKelvin, + }, + ThermalSetScpResponse, + ThermalGetVarResponse { + val: u32, + }, + ThermalSetVarResponse, +} + +impl SerializableMessage for ThermalResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::ThermalGetTmpResponse { temperature } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(temperature)); + + Ok(4) + } + Self::ThermalGetThrsResponse { timeout, low, high } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(timeout)); + buffer + .get_mut(4..8) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(low)); + buffer + .get_mut(8..12) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(high)); + + Ok(12) + } + + Self::ThermalGetVarResponse { val } => { + buffer + .get_mut(..4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&u32::to_le_bytes(val)); + Ok(4) + } + Self::ThermalSetVarResponse | Self::ThermalSetScpResponse | Self::ThermalSetThrsResponse => Ok(0), + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + ThermalCmd::from(self).into() + } +} + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum ThermalError { + InvalidParameter = 1, + UnsupportedRevision = 2, + HardwareError = 3, +} + +impl SerializableMessage for ThermalError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + Self::UnsupportedRevision | Self::InvalidParameter | Self::HardwareError => Ok(0), + } + } + + fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { + Err(MessageSerializationError::Other( + "unimplemented - don't need to deserialize responses on the EC side", + )) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +pub type ThermalResult = Result; + +fn safe_get_u8(buffer: &[u8], index: usize) -> Result { + buffer + .get(index) + .copied() + .ok_or(MessageSerializationError::BufferTooSmall) +} + +fn safe_get_u16(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 2) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u16::from_le_bytes(bytes)) +} + +fn safe_get_dword(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u32::from_le_bytes(bytes)) +} + +fn safe_get_uuid(buffer: &[u8], index: usize) -> Result { + buffer + .get(index..index + 16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall) +} diff --git a/thermal-service/Cargo.toml b/thermal-service/Cargo.toml index df9dd26f0..b54147689 100644 --- a/thermal-service/Cargo.toml +++ b/thermal-service/Cargo.toml @@ -17,6 +17,7 @@ embedded-hal-async.workspace = true embedded-hal.workspace = true embedded-services.workspace = true heapless.workspace = true +thermal-service-messages.workspace = true uuid.workspace = true embedded-fans-async = "0.2.0" embedded-sensors-hal-async = "0.3.0" @@ -32,6 +33,7 @@ defmt = [ "embedded-fans-async/defmt", "embedded-sensors-hal-async/defmt", "mctp-rs/defmt", + "thermal-service-messages/defmt", ] log = [ "dep:log", diff --git a/thermal-service/src/context.rs b/thermal-service/src/context.rs index 93e171356..35ed3a127 100644 --- a/thermal-service/src/context.rs +++ b/thermal-service/src/context.rs @@ -1,37 +1,26 @@ //! Thermal service context -use crate::mptf; use crate::{Error, Event, fan, sensor}; use embassy_sync::channel::Channel; use embedded_services::GlobalRawMutex; -use embedded_services::buffer::OwnedRef; -use embedded_services::ec_type::message::StdHostRequest; use embedded_services::{error, intrusive_list}; -embedded_services::define_static_buffer!(mctp_buf, u8, [0u8; 69]); - -pub(crate) struct Context<'a> { +pub(crate) struct Context { // Registered temperature sensors sensors: intrusive_list::IntrusiveList, // Registered fans fans: intrusive_list::IntrusiveList, - // MPTF Request Queue - mptf: Channel, - // Raw MCTP Payload Queue - mctp: Channel, - // MCTP message buffer - mctp_buf: OwnedRef<'a, u8>, + // Pending MPTF request queue + mptf: Channel, // Event queue events: Channel, } -impl<'a> Context<'a> { +impl Context { pub(crate) fn new() -> Self { Self { sensors: intrusive_list::IntrusiveList::new(), fans: intrusive_list::IntrusiveList::new(), mptf: Channel::new(), - mctp: Channel::new(), - mctp_buf: mctp_buf::get_mut().unwrap(), events: Channel::new(), } } @@ -102,28 +91,14 @@ impl<'a> Context<'a> { fan.execute_request(request).await } - pub(crate) fn send_mptf_request(&self, msg: mptf::Request) -> Result<(), Error> { - self.mptf.try_send(msg).map_err(|_| Error)?; - Ok(()) + pub(crate) fn queue_mptf_request(&self, msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { + self.mptf.try_send(msg).map_err(|_| Error) } - pub(crate) async fn wait_mptf_request(&self) -> mptf::Request { + pub(crate) async fn wait_mptf_request(&self) -> thermal_service_messages::ThermalRequest { self.mptf.receive().await } - pub(crate) fn send_mctp_payload(&self, msg: StdHostRequest) -> Result<(), Error> { - self.mctp.try_send(msg).map_err(|_| Error)?; - Ok(()) - } - - pub(crate) async fn wait_mctp_payload(&self) -> StdHostRequest { - self.mctp.receive().await - } - - pub(crate) fn get_mctp_buf(&self) -> &OwnedRef<'a, u8> { - &self.mctp_buf - } - pub(crate) async fn send_event(&self, event: Event) { self.events.send(event).await } diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index b43c7d7db..a0d075193 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -5,8 +5,6 @@ use embassy_sync::once_lock::OnceLock; use embedded_sensors_hal_async::temperature::DegreesCelsius; -use embedded_services::buffer::OwnedRef; -use embedded_services::ec_type::message::StdHostRequest; use embedded_services::{comms, error, info, intrusive_list}; mod context; @@ -35,12 +33,12 @@ pub enum Event { FanFailure(fan::DeviceId, fan::Error), } -struct Service<'a> { - context: context::Context<'a>, +struct Service { + context: context::Context, endpoint: comms::Endpoint, } -impl<'a> Service<'a> { +impl Service { fn new() -> Self { Self { context: context::Context::new(), @@ -49,16 +47,12 @@ impl<'a> Service<'a> { } } -impl<'a> comms::MailboxDelegate for Service<'a> { +impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { // Queue for later processing - if let Some(msg) = message.data.get::() { + if let Some(msg) = message.data.get::() { self.context - .send_mctp_payload(*msg) - .map_err(|_| comms::MailboxDelegateError::BufferFull) - } else if let Some(&msg) = message.data.get::() { - self.context - .send_mptf_request(msg) + .queue_mptf_request(*msg) .map_err(|_| comms::MailboxDelegateError::BufferFull) } else { Err(comms::MailboxDelegateError::InvalidData) @@ -93,24 +87,15 @@ pub async fn send_service_msg(to: comms::EndpointID, data: &impl embedded_servic } /// Send a MPTF request -pub async fn queue_mptf_request(msg: mptf::Request) -> Result<(), Error> { - SERVICE.get().await.context.send_mptf_request(msg) +pub async fn queue_mptf_request(msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { + SERVICE.get().await.context.queue_mptf_request(msg) } /// Wait for a MPTF request -pub async fn wait_mptf_request() -> mptf::Request { +pub(crate) async fn wait_mptf_request() -> thermal_service_messages::ThermalRequest { SERVICE.get().await.context.wait_mptf_request().await } -/// Wait for a MCTP payload -pub async fn wait_mctp_payload() -> StdHostRequest { - SERVICE.get().await.context.wait_mctp_payload().await -} - -pub fn get_mctp_buf<'a>() -> &'a OwnedRef<'a, u8> { - SERVICE.try_get().unwrap().context.get_mctp_buf() -} - /// Send a thermal event pub async fn send_event(event: Event) { SERVICE.get().await.context.send_event(event).await diff --git a/thermal-service/src/mptf.rs b/thermal-service/src/mptf.rs index ea1195003..dc9e39401 100644 --- a/thermal-service/src/mptf.rs +++ b/thermal-service/src/mptf.rs @@ -4,8 +4,9 @@ //! //! This interface is subject to change as the eSPI OOB service is developed use crate::{self as ts, fan, sensor, utils}; -use embedded_services::ec_type::message::{StdHostPayload, StdHostRequest}; -use embedded_services::{ec_type::protocols::mctp, error}; +use thermal_service_messages::{DeciKelvin, Milliseconds}; + +use embedded_services::error; /// MPTF Standard UUIDs which the thermal service understands pub mod uuid_standard { @@ -20,131 +21,6 @@ pub mod uuid_standard { pub const FAN_CURRENT_RPM: uuid::Bytes = uuid::uuid!("adf95492-0776-4ffc-84f3-b6c8b5269683").to_bytes_le(); } -/// Standard 32-bit DWORD -pub type Dword = u32; - -/// 16-bit variable length -pub type VarLen = u16; - -/// Instance ID -pub type InstanceId = u8; - -/// Time in milliseconds -pub type Milliseconds = Dword; - -/// MPTF expects temperatures in tenth Kelvins -pub type DeciKelvin = Dword; - -/// MPTF Response -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Response { - // Status code (not necessarily related to Status code in response) - // This is used because some commands can fail but don't contain Status output as part of MPTF spec - pub status: Status, - // Response data - pub data: ResponseData, -} - -impl Response { - fn new(status: Status, data: ResponseData) -> Self { - Self { status, data } - } -} - -/// MPTF Status -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Status { - /// Success - Success, - /// Invalid parameter was used - InvalidParameter, - /// Revision is not supported - UnsupportedRevision, - /// A hardware error occurred - HardwareError, -} - -impl From for u32 { - fn from(status: Status) -> Self { - match status { - Status::Success => 0, - Status::InvalidParameter => 1, - Status::UnsupportedRevision => 2, - Status::HardwareError => 3, - } - } -} - -impl From for u8 { - fn from(status: Status) -> Self { - u32::from(status) as u8 - } -} - -/// Standard MPTF requests expected by the thermal subsystem -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Request { - /// EC_THM_GET_TMP = 0x1 - GetTmp(InstanceId), - /// EC_THM_SET_THRS = 0x2 - SetThrs(InstanceId, Milliseconds, DeciKelvin, DeciKelvin), - /// EC_THM_GET_THRS = 0x3 - GetThrs(InstanceId), - /// EC_THM_SET_SCP = 0x4 - SetScp(InstanceId, Dword, Dword, Dword), - /// EC_THM_GET_VAR = 0x5 - GetVar(InstanceId, VarLen, uuid::Bytes), - /// EC_THM_SET_VAR = 0x6 - SetVar(InstanceId, VarLen, uuid::Bytes, Dword), -} - -impl From for u8 { - fn from(request: Request) -> Self { - match request { - Request::GetTmp(_) => 1, - Request::SetThrs(_, _, _, _) => 2, - Request::GetThrs(_) => 3, - Request::SetScp(_, _, _, _) => 4, - Request::GetVar(_, _, _) => 5, - Request::SetVar(_, _, _, _) => 6, - } - } -} - -/// Data returned by thermal subsystem in response to MPTF requests -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// EC_THM_GET_TMP = 0x1 - GetTmp(DeciKelvin), - /// EC_THM_SET_THRS = 0x2 - SetThrs(Status), - /// EC_THM_GET_THRS = 0x3 - GetThrs(Status, Milliseconds, DeciKelvin, DeciKelvin), - /// EC_THM_SET_SCP = 0x4 - SetScp(Status), - /// EC_THM_GET_VAR = 0x5 - GetVar(Status, Dword), - /// EC_THM_SET_VAR = 0x6 - SetVar(Status), -} - -impl From for u8 { - fn from(response: ResponseData) -> Self { - match response { - ResponseData::GetTmp(_) => 1, - ResponseData::SetThrs(_) => 2, - ResponseData::GetThrs(_, _, _, _) => 3, - ResponseData::SetScp(_) => 4, - ResponseData::GetVar(_, _) => 5, - ResponseData::SetVar(_) => 6, - } - } -} - /// Notifications to Host #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -157,410 +33,211 @@ pub enum Notify { Critical, } -async fn sensor_get_tmp(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalGetTmpRequest { instance_id } => { - match ts::execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp).await { - Ok(ts::sensor::ResponseData::Temp(temp)) => { - request.payload = StdHostPayload::ThermalGetTmpResponse { - temperature: utils::c_to_dk(temp), - }; - request.status = 0; - } - _ => { - request.payload = StdHostPayload::ErrorResponse {}; - request.status = 1; - } - } - } - _ => error!("Thermal Service: Host message header and payload mismatch"), +async fn sensor_get_tmp(instance_id: u8) -> thermal_service_messages::ThermalResult { + if let Ok(ts::sensor::ResponseData::Temp(temp)) = + ts::execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp).await + { + Ok(thermal_service_messages::ThermalResponse::ThermalGetTmpResponse { + temperature: utils::c_to_dk(temp), + }) + } else { + Err(thermal_service_messages::ThermalError::InvalidParameter) } } -async fn get_var_handler(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalGetVarRequest { - instance_id, - len: _, - var_uuid, - } => match var_uuid { - uuid_standard::CRT_TEMP => { - let Response { status: _, data } = sensor_get_thrs(instance_id, sensor::ThresholdType::Critical).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::PROC_HOT_TEMP => { - let Response { status: _, data } = sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - // TODO: Add a SetProfileId request type? But for sensor or fan? - uuid_standard::PROFILE_TYPE => { - todo!() - } - uuid_standard::FAN_ON_TEMP => { - let Response { status: _, data } = fan_get_temp(instance_id, fan::Request::GetOnTemp).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_RAMP_TEMP => { - let Response { status: _, data } = fan_get_temp(instance_id, fan::Request::GetRampTemp).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_MAX_TEMP => { - let Response { status: _, data } = fan_get_temp(instance_id, fan::Request::GetMaxTemp).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_MIN_RPM => { - let Response { status: _, data } = fan_get_rpm(instance_id, fan::Request::GetMinRpm).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_MAX_RPM => { - let Response { status: _, data } = fan_get_rpm(instance_id, fan::Request::GetMaxRpm).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.status = error.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - uuid_standard::FAN_CURRENT_RPM => { - let Response { status: _, data } = fan_get_rpm(instance_id, fan::Request::GetRpm).await; - if let ResponseData::GetVar(Status::Success, val) = data { - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::Success.into(), - val, - } - } else if let ResponseData::GetVar(error, val) = data { - request.status = error.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: error.into(), - val, - } - } - } - // TODO: Allow OEM to handle these? - uuid => { - error!("Received GetVar for unrecognized UUID: {:?}", uuid); - request.status = Status::InvalidParameter.into(); - request.payload = mctp::Odp::ThermalGetVarResponse { - status: Status::InvalidParameter.into(), - val: 0, - } - } - }, - _ => error!("Thermal Service: Host message header and payload mismatch"), +async fn get_var_handler(instance_id: u8, var_uuid: uuid::Bytes) -> thermal_service_messages::ThermalResult { + match var_uuid { + uuid_standard::CRT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Critical).await, + uuid_standard::PROC_HOT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot).await, + // TODO: Add a SetProfileId request type? But for sensor or fan? + uuid_standard::PROFILE_TYPE => { + todo!() + } + uuid_standard::FAN_ON_TEMP => fan_get_temp(instance_id, fan::Request::GetOnTemp).await, + + uuid_standard::FAN_RAMP_TEMP => fan_get_temp(instance_id, fan::Request::GetRampTemp).await, + uuid_standard::FAN_MAX_TEMP => fan_get_temp(instance_id, fan::Request::GetMaxTemp).await, + uuid_standard::FAN_MIN_RPM => fan_get_rpm(instance_id, fan::Request::GetMinRpm).await, + uuid_standard::FAN_MAX_RPM => fan_get_rpm(instance_id, fan::Request::GetMaxRpm).await, + uuid_standard::FAN_CURRENT_RPM => fan_get_rpm(instance_id, fan::Request::GetRpm).await, + // TODO: Allow OEM to handle these? + uuid => { + error!("Received GetVar for unrecognized UUID: {:?}", uuid); + Err(thermal_service_messages::ThermalError::InvalidParameter) + } } } -async fn set_var_handler(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalSetVarRequest { - instance_id, - len: _, - var_uuid, - set_var, - } => match var_uuid { - uuid_standard::CRT_TEMP => { - let Response { status: _, data } = - sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - uuid_standard::PROC_HOT_TEMP => { - let Response { status: _, data } = - sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - // TODO: Add a SetProfileId request type? But for sensor or fan? - uuid_standard::PROFILE_TYPE => { - todo!() - } - uuid_standard::FAN_ON_TEMP => { - let Response { status: _, data } = - fan_set_var(instance_id, fan::Request::SetOnTemp(utils::dk_to_c(set_var))).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - uuid_standard::FAN_RAMP_TEMP => { - let Response { status: _, data } = - fan_set_var(instance_id, fan::Request::SetRampTemp(utils::dk_to_c(set_var))).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - uuid_standard::FAN_MAX_TEMP => { - let Response { status: _, data } = - fan_set_var(instance_id, fan::Request::SetMaxTemp(utils::dk_to_c(set_var))).await; - if let ResponseData::SetVar(Status::Success) = data { - request.status = Status::Success.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? - uuid_standard::FAN_MIN_RPM => { - todo!() - } - // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? - uuid_standard::FAN_MAX_RPM => { - todo!() - } - uuid_standard::FAN_CURRENT_RPM => { - let Response { status: _, data } = fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16)).await; - if let ResponseData::SetVar(Status::Success) = data { - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::Success.into(), - } - } else if let ResponseData::SetVar(error) = data { - request.status = error.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { status: error.into() } - } - } - // TODO: Allow OEM to handle these? - uuid => { - error!("Received SetVar for unrecognized UUID: {:?}", uuid); - request.status = Status::InvalidParameter.into(); - request.payload = mctp::Odp::ThermalSetVarResponse { - status: Status::InvalidParameter.into(), - } - } - }, - _ => error!("Thermal Service: Host message header and payload mismatch"), +async fn set_var_handler( + instance_id: u8, + var_uuid: uuid::Bytes, + set_var: u32, +) -> thermal_service_messages::ThermalResult { + match var_uuid { + uuid_standard::CRT_TEMP => sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var).await, + uuid_standard::PROC_HOT_TEMP => sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var).await, + // TODO: Add a SetProfileId request type? But for sensor or fan? + uuid_standard::PROFILE_TYPE => { + todo!() + } + uuid_standard::FAN_ON_TEMP => fan_set_var(instance_id, fan::Request::SetOnTemp(utils::dk_to_c(set_var))).await, + uuid_standard::FAN_RAMP_TEMP => { + fan_set_var(instance_id, fan::Request::SetRampTemp(utils::dk_to_c(set_var))).await + } + uuid_standard::FAN_MAX_TEMP => { + fan_set_var(instance_id, fan::Request::SetMaxTemp(utils::dk_to_c(set_var))).await + } + // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? + uuid_standard::FAN_MIN_RPM => { + todo!() + } + // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? + uuid_standard::FAN_MAX_RPM => { + todo!() + } + uuid_standard::FAN_CURRENT_RPM => fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16)).await, + // TODO: Allow OEM to handle these? + uuid => { + error!("Received SetVar for unrecognized UUID: {:?}", uuid); + Err(thermal_service_messages::ThermalError::InvalidParameter) + } } } -async fn sensor_get_warn_thrs(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalGetThrsRequest { instance_id } => { - let low = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), - ) - .await; - let high = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), - ) - .await; - - match (low, high) { - (Ok(sensor::ResponseData::Threshold(low)), Ok(sensor::ResponseData::Threshold(high))) => { - request.payload = StdHostPayload::ThermalGetThrsResponse { - status: 0, - timeout: 0, - low: utils::c_to_dk(low), - high: utils::c_to_dk(high), - }; - request.status = 0; - } - _ => { - request.payload = StdHostPayload::ThermalGetThrsResponse { - status: 1, - timeout: 0, - low: 0, - high: 0, - }; - request.status = 1; - } - } +async fn sensor_get_warn_thrs(instance_id: u8) -> thermal_service_messages::ThermalResult { + let low = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), + ) + .await; + let high = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), + ) + .await; + + match (low, high) { + (Ok(sensor::ResponseData::Threshold(low)), Ok(sensor::ResponseData::Threshold(high))) => { + Ok(thermal_service_messages::ThermalResponse::ThermalGetThrsResponse { + timeout: 0, + low: utils::c_to_dk(low), + high: utils::c_to_dk(high), + }) } - _ => error!("Thermal Service: Host message header and payload mismatch"), + _ => Err(thermal_service_messages::ThermalError::InvalidParameter), } } -async fn sensor_set_warn_thrs(request: &mut StdHostRequest) { - match request.payload { - mctp::Odp::ThermalSetThrsRequest { - instance_id, - timeout: _, - low, - high, - } => { - let low_res = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), - ) - .await; - let high_res = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), - ) - .await; +async fn sensor_set_warn_thrs( + instance_id: u8, + _timeout: Milliseconds, + low: DeciKelvin, + high: DeciKelvin, +) -> thermal_service_messages::ThermalResult { + let low_res = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), + ) + .await; + let high_res = ts::execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), + ) + .await; - if low_res.is_ok() && high_res.is_ok() { - request.payload = mctp::Odp::ThermalSetThrsResponse { status: 0 }; - request.status = 0; - } else { - request.payload = mctp::Odp::ThermalSetThrsResponse { status: 1 }; - request.status = 1; - } - } - _ => error!("Thermal Service: Host message header and payload mismatch"), + if low_res.is_ok() && high_res.is_ok() { + Ok(thermal_service_messages::ThermalResponse::ThermalSetThrsResponse) + } else { + Err(thermal_service_messages::ThermalError::InvalidParameter) } } -async fn sensor_get_thrs(instance: u8, threshold_type: sensor::ThresholdType) -> Response { +async fn sensor_get_thrs( + instance: u8, + threshold_type: sensor::ThresholdType, +) -> thermal_service_messages::ThermalResult { match ts::execute_sensor_request( sensor::DeviceId(instance), sensor::Request::GetThreshold(threshold_type), ) .await { - Ok(sensor::ResponseData::Temp(temp)) => Response::new( - Status::Success, - ResponseData::GetVar(Status::Success, utils::c_to_dk(temp)), - ), - _ => Response::new(Status::Success, ResponseData::GetVar(Status::HardwareError, 0)), + Ok(sensor::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { + val: utils::c_to_dk(temp), + }), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_get_temp(instance: u8, fan_request: fan::Request) -> Response { +async fn fan_get_temp(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { - Ok(fan::ResponseData::Temp(temp)) => Response::new( - Status::Success, - ResponseData::GetVar(Status::Success, utils::c_to_dk(temp)), - ), - _ => Response::new(Status::Success, ResponseData::GetVar(Status::HardwareError, 0)), + Ok(fan::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { + val: utils::c_to_dk(temp), + }), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_get_rpm(instance: u8, fan_request: fan::Request) -> Response { +async fn fan_get_rpm(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { Ok(fan::ResponseData::Rpm(rpm)) => { - Response::new(Status::Success, ResponseData::GetVar(Status::Success, rpm as u32)) + Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) } - _ => Response::new(Status::Success, ResponseData::GetVar(Status::HardwareError, 0)), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn sensor_set_thrs(instance: u8, threshold_type: sensor::ThresholdType, threshold_dk: Dword) -> Response { +async fn sensor_set_thrs( + instance: u8, + threshold_type: sensor::ThresholdType, + threshold_dk: u32, +) -> thermal_service_messages::ThermalResult { match ts::execute_sensor_request( sensor::DeviceId(instance), sensor::Request::SetThreshold(threshold_type, utils::dk_to_c(threshold_dk)), ) .await { - Ok(sensor::ResponseData::Success) => Response::new(Status::Success, ResponseData::SetVar(Status::Success)), - _ => Response::new(Status::Success, ResponseData::SetVar(Status::HardwareError)), + Ok(sensor::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_set_var(instance: u8, fan_request: fan::Request) -> Response { +async fn fan_set_var(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { - Ok(fan::ResponseData::Success) => Response::new(Status::Success, ResponseData::SetVar(Status::Success)), - _ => Response::new(Status::Success, ResponseData::SetVar(Status::HardwareError)), + Ok(fan::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), + _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -pub(crate) async fn process_request(request: &mut StdHostRequest) { - match request.command { - embedded_services::ec_type::message::OdpCommand::Thermal(thermal_msg) => match thermal_msg { - embedded_services::ec_type::protocols::mptf::ThermalCmd::GetTmp => sensor_get_tmp(request).await, - embedded_services::ec_type::protocols::mptf::ThermalCmd::SetThrs => sensor_set_warn_thrs(request).await, - embedded_services::ec_type::protocols::mptf::ThermalCmd::GetThrs => sensor_get_warn_thrs(request).await, - // TODO: How do we handle this genericly? - embedded_services::ec_type::protocols::mptf::ThermalCmd::SetScp => todo!(), - embedded_services::ec_type::protocols::mptf::ThermalCmd::GetVar => get_var_handler(request).await, - embedded_services::ec_type::protocols::mptf::ThermalCmd::SetVar => set_var_handler(request).await, - }, - _ => error!("Thermal Service: Recvd other subsystem host message"), +pub(crate) async fn process_request( + request: &thermal_service_messages::ThermalRequest, +) -> thermal_service_messages::ThermalResult { + match request { + thermal_service_messages::ThermalRequest::ThermalGetTmpRequest { instance_id } => { + sensor_get_tmp(*instance_id).await + } + thermal_service_messages::ThermalRequest::ThermalSetThrsRequest { + instance_id, + timeout, + low, + high, + } => sensor_set_warn_thrs(*instance_id, *timeout, *low, *high).await, + thermal_service_messages::ThermalRequest::ThermalGetThrsRequest { instance_id } => { + sensor_get_warn_thrs(*instance_id).await + } + // TODO: How do we handle this generically? + thermal_service_messages::ThermalRequest::ThermalSetScpRequest { .. } => todo!(), + thermal_service_messages::ThermalRequest::ThermalGetVarRequest { + instance_id, + len: _len, + var_uuid, + } => get_var_handler(*instance_id, *var_uuid).await, + thermal_service_messages::ThermalRequest::ThermalSetVarRequest { + instance_id, + len: _len, + var_uuid, + set_var, + } => set_var_handler(*instance_id, *var_uuid, *set_var).await, } } diff --git a/thermal-service/src/task.rs b/thermal-service/src/task.rs index 82d214276..d66bc95bb 100644 --- a/thermal-service/src/task.rs +++ b/thermal-service/src/task.rs @@ -4,11 +4,12 @@ use crate::{self as ts, mptf::process_request}; pub async fn handle_requests() { loop { - let mut request = ts::wait_mctp_payload().await; - process_request(&mut request).await; + let request = ts::wait_mptf_request().await; + let result = process_request(&request).await; let send_result = ts::send_service_msg( + // TODO we should probably respond to the endpoint that requested us rather than hardcoding the return address like this comms::EndpointID::External(comms::External::Host), - &embedded_services::ec_type::message::HostMsg::Response(request), + &result, ) .await; diff --git a/thermal-service/src/utils.rs b/thermal-service/src/utils.rs index 4497fc982..58c70c127 100644 --- a/thermal-service/src/utils.rs +++ b/thermal-service/src/utils.rs @@ -1,5 +1,4 @@ //! Helpful utilities for the thermal service -use crate::mptf; use heapless::Deque; /// Buffer for storing samples @@ -42,11 +41,11 @@ impl SampleBuf { } /// Convert deciKelvin to degrees Celsius -pub const fn dk_to_c(dk: mptf::DeciKelvin) -> f32 { +pub const fn dk_to_c(dk: thermal_service_messages::DeciKelvin) -> f32 { (dk as f32 / 10.0) - 273.15 } /// Convert degrees Celsius to deciKelvin -pub const fn c_to_dk(c: f32) -> mptf::DeciKelvin { - ((c + 273.15) * 10.0) as mptf::DeciKelvin +pub const fn c_to_dk(c: f32) -> thermal_service_messages::DeciKelvin { + ((c + 273.15) * 10.0) as thermal_service_messages::DeciKelvin } From 07d89d1280bd21bcdb8384061e6d050bd0d307c7 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:36:10 -0800 Subject: [PATCH 04/79] Refactor MCTP relay boilerplate into macro, clarify result nomenclature (#673) This change factors out the boilerplate associated with generating relay types from the information about the set of types that are to be relayed. This should enable easier bringup of additional message relay services (e.g. UART). In the course of this work, I noticed that some of the nomenclature around results vs responses was a bit conflicting in the relay code, so also standardized that on Result meaning 'a specific generic type of the Result enum' and Response meaning 'the success case of a Result'. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- battery-service-messages/src/lib.rs | 2 + embedded-service/src/relay/mod.rs | 345 +++++++++++++++++++++++++- espi-service/src/espi_service.rs | 35 ++- espi-service/src/mctp.rs | 370 ++-------------------------- 4 files changed, 370 insertions(+), 382 deletions(-) diff --git a/battery-service-messages/src/lib.rs b/battery-service-messages/src/lib.rs index 3c856b9a8..a219730e7 100644 --- a/battery-service-messages/src/lib.rs +++ b/battery-service-messages/src/lib.rs @@ -685,6 +685,8 @@ pub enum AcpiBatteryError { UnspecifiedFailure = 2, } +pub type AcpiBatteryResult = Result; + impl SerializableMessage for AcpiBatteryError { fn serialize(self, _buffer: &mut [u8]) -> Result { match self { diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index 6531fcf67..00d9e5d02 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -30,7 +30,7 @@ pub trait SerializableMessage: Sized { fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; } -// Prevent other types from implementing SerializableResponse - they should instead use SerializableMessage on a Response type and an Error type +// Prevent other types from implementing SerializableResult - they should instead use SerializableMessage on a Response type and an Error type #[doc(hidden)] mod private { pub trait Sealed {} @@ -38,30 +38,30 @@ mod private { impl Sealed for Result {} } -/// Responses are of type Result where T and E both implement SerializableMessage -pub trait SerializableResponse: private::Sealed + Sized { - /// The type of the response when the operation being responsed to succeeded +/// Responses sent over MCTP are called "Results" and are of type Result where T and E both implement SerializableMessage +pub trait SerializableResult: private::Sealed + Sized { + /// The type of the result when the operation being responded to succeeded type SuccessType: SerializableMessage; - /// The type of the response when the operation being responsed to failed + /// The type of the result when the operation being responded to failed type ErrorType: SerializableMessage; - /// Returns true if the response represents a successful operation, false otherwise + /// Returns true if the result represents a successful operation, false otherwise fn is_ok(&self) -> bool; - /// Returns a unique discriminant that can be used to deserialize the specific type of response. + /// Returns a unique discriminant that can be used to deserialize the specific type of result. /// Discriminants can be reused for success and error messages. fn discriminant(&self) -> u16; - /// Writes the response into the provided buffer. + /// Writes the result into the provided buffer. /// On success, returns the number of bytes written fn serialize(self, buffer: &mut [u8]) -> Result; - /// Attempts to deserialize the response from the provided buffer. + /// Attempts to deserialize the result from the provided buffer. fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; } -impl SerializableResponse for Result +impl SerializableResult for Result where T: SerializableMessage, E: SerializableMessage, @@ -95,3 +95,328 @@ where } } } + +pub mod mctp { + //! Contains helper functions for services that relay comms messages over MCTP + + /// Error type for MCTP relay operations + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MctpError { + /// The endpoint ID does not correspond to a known service + UnknownEndpointId, + } + + /// This macro generates the necessary types and impls to support relaying ODP messages to and from the comms system. + /// It takes as input a list of (service name, service ID, comms endpoint ID, request type, result type) tuples and + /// emits the following types: + /// - enum OdpService - a mapping from service name to MCTP endpoint ID + /// - enum HostRequest - an enum containing all the possible request types that were passed into the macro + /// - enum HostResult - an enum containing all the possible result types that were passed into the macro + /// - struct OdpHeader - a type representing the ODP MCTP header. + /// - fn send_to_comms(&comms::Message, impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, + /// a function that takes a received message and sends it to the appropriate service based on its type using the provided send function. + /// + /// Because this macro emits a number of types, it is recommended to invoke it inside a dedicated module. + /// + /// Arguments: + /// $service_name (identifier) - the name that this service will have in the emitted OdpService enum + /// $service_id (u8) - the service ID that will be used in the ODP MCTP header for messages related to this service. + /// $endpoint_id (comms::EndpointID value) - the comms endpoint ID that this service corresponds to. + /// NOTE: due to technical limitations in Rust macros, this must be surrounded with parentheses. + /// $request_type (type implementing SerializableMessage) - the type that represents requests for this service + /// $result_type (type implementing SerializableResult) - the type that represents results for this service + /// + /// Example usage: + /// + /// impl_odp_relay_types!( + /// Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + /// Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + /// Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + /// ); + /// ^ ^ + /// note the above parentheses - these are required + #[macro_export] + macro_rules! impl_odp_mctp_relay_types { + ($($service_name:ident, + $service_id:expr, + ($($endpoint_id:tt)+), + $request_type:ty, + $result_type:ty; + )+) => { + + use bitfield::bitfield; + use core::convert::Infallible; + use mctp_rs::smbus_espi::SmbusEspiMedium; + use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; + + #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(u8)] + pub(crate) enum OdpService { + $( + $service_name = $service_id, + )+ + } + + impl TryFrom for OdpService { + type Error = embedded_services::relay::mctp::MctpError; + fn try_from(endpoint_id_value: comms::EndpointID) -> Result { + match endpoint_id_value { + $( + $($endpoint_id)+ => Ok(OdpService::$service_name), + )+ + _ => Err(embedded_services::relay::mctp::MctpError::UnknownEndpointId), + } + } + } + + impl OdpService { + pub fn get_endpoint_id(&self) -> comms::EndpointID { + match self { + $( + OdpService::$service_name => $($endpoint_id)+, + )+ + } + } + } + + pub(crate) enum HostRequest { + $( + $service_name($request_type), + )+ + } + + impl HostRequest { + pub(crate) async fn send_to_endpoint(&self, source_endpoint: &comms::Endpoint, destination_endpoint_id: comms::EndpointID) -> Result<(), Infallible> { + match self { + $( + HostRequest::$service_name(request) => source_endpoint.send(destination_endpoint_id, request).await, + )+ + } + } + } + + impl MctpMessageTrait<'_> for HostRequest { + type Header = OdpHeader; + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostRequest::$service_name(request) => request + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + Ok(match header.service { + $( + OdpService::$service_name => Self::$service_name( + <$request_type>::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, + ), + )+ + }) + } + } + + #[derive(Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) enum HostResult { + $( + $service_name($result_type), + )+ + } + + impl HostResult { + pub(crate) fn discriminant(&self) -> u16 { + match self { + $( + HostResult::$service_name(result) => result.discriminant(), + )+ + } + } + + pub(crate) fn is_ok(&self) -> bool { + match self { + $( + HostResult::$service_name(result) => result.is_ok(), + )+ + } + } + } + + impl MctpMessageTrait<'_> for HostResult { + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + type Header = OdpHeader; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostResult::$service_name(result) => result + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + match header.service { + $( + OdpService::$service_name => { + match header.message_type { + OdpMessageType::Request => { + Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) + } + OdpMessageType::Result { is_error } => { + Ok(HostResult::$service_name(<$result_type as SerializableResult>::deserialize(is_error, header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) + } + } + }, + )+ + } + } + } + + bitfield! { + /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct OdpHeaderWireFormat(u32); + impl Debug; + impl new; + /// If true, represents a request; otherwise, represents a result + is_request, set_is_request: 25; + + // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + is_datagram, set_is_datagram: 24; + + /// The service ID that this message is related to + /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. + u8, service_id, set_service_id: 23, 16; + + /// On results, indicates if the result message is an error. Unused on requests. + is_error, set_is_error: 15; + + /// The message type/discriminant + u16, message_id, set_message_id: 14, 0; + } + + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) enum OdpMessageType { + Request, + Result { is_error: bool }, + } + + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) struct OdpHeader { + pub message_type: OdpMessageType, + pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + pub service: OdpService, + pub message_id: u16, + } + + impl From for OdpHeaderWireFormat { + fn from(src: OdpHeader) -> Self { + Self::new( + matches!(src.message_type, OdpMessageType::Request), + src.is_datagram, + src.service.into(), + match src.message_type { + OdpMessageType::Request => false, // unused on requests + OdpMessageType::Result { is_error } => is_error, + }, + src.message_id, + ) + } + } + + impl TryFrom for OdpHeader { + type Error = MctpPacketError; + + fn try_from(src: OdpHeaderWireFormat) -> Result { + let service = OdpService::try_from(src.service_id()) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; + + let message_type = if src.is_request() { + OdpMessageType::Request + } else { + OdpMessageType::Result { + is_error: src.is_error(), + } + }; + + Ok(OdpHeader { + message_type, + is_datagram: src.is_datagram(), + service, + message_id: src.message_id(), + }) + } + } + + impl MctpMessageHeaderTrait for OdpHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let wire_format = OdpHeaderWireFormat::from(self); + let bytes = wire_format.0.to_be_bytes(); + buffer + .get_mut(0..bytes.len()) + .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? + .copy_from_slice(&bytes); + + Ok(bytes.len()) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + let bytes = buffer + .get(0..core::mem::size_of::()) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; + let raw = u32::from_be_bytes( + bytes + .try_into() + .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + ); + + let parsed_wire_format = OdpHeaderWireFormat(raw); + let header = OdpHeader::try_from(parsed_wire_format) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; + + Ok(( + header, + buffer + .get(core::mem::size_of::()..) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + )) + } + } + + /// Attempt to route the provided message to the service that is registered to handle it based on its type. + pub(crate) fn send_to_comms( + message: &comms::Message, + send_fn: impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, + ) -> Result<(), comms::MailboxDelegateError> { + $( + if let Some(msg) = message.data.get::<$result_type>() { + send_fn( + $($endpoint_id)+, + HostResult::$service_name(*msg), + )?; + Ok(()) + } else + )+ + { + Err(comms::MailboxDelegateError::MessageNotFound) + } + } + }; +} + + pub use impl_odp_mctp_relay_types; +} diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 82f22ddb2..55dcd55e7 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -1,6 +1,6 @@ use core::slice; -use crate::mctp::{HostRequest, HostResponse, OdpHeader, OdpMessageType, OdpService}; +use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService}; use core::borrow::BorrowMut; use embassy_imxrt::espi; use embassy_sync::channel::Channel; @@ -25,9 +25,9 @@ embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; ASSEMBLY_BUF_SI #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct HostResponseMessage { +pub(crate) struct HostResultMessage { pub source_endpoint: EndpointID, - pub message: HostResponse, + pub message: HostResult, } #[derive(Debug, Clone, Copy)] @@ -40,7 +40,7 @@ pub enum Error { pub struct Service<'a> { endpoint: comms::Endpoint, _ec_memory: Mutex, - host_tx_queue: Channel, + host_tx_queue: Channel, assembly_buf_owned_ref: OwnedRef<'a, u8>, } @@ -54,7 +54,7 @@ impl Service<'_> { } } - pub(crate) async fn wait_for_response(&self) -> HostResponseMessage { + pub(crate) async fn wait_for_response(&self) -> HostResultMessage { self.host_tx_queue.receive().await } @@ -69,14 +69,13 @@ impl Service<'_> { async fn serialize_packet_from_subsystem( &self, espi: &mut espi::Espi<'static>, - response: &HostResponseMessage, + result: &HostResultMessage, ) -> Result<(), Error> { let mut assembly_buf_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; let pkt_ctx_buf = assembly_buf_access.borrow_mut(); let mut mctp_ctx = mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, pkt_ctx_buf); - let source_service: OdpService = - OdpService::try_from(response.source_endpoint).map_err(|_| Error::Serialize)?; + let source_service: OdpService = OdpService::try_from(result.source_endpoint).map_err(|_| Error::Serialize)?; let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { source_endpoint_id: mctp_rs::EndpointId::Id(0x80), @@ -93,16 +92,16 @@ impl Service<'_> { }; let header = OdpHeader { - message_type: OdpMessageType::Response { - is_error: !response.message.is_ok(), + message_type: OdpMessageType::Result { + is_error: !result.message.is_ok(), }, is_datagram: false, service: source_service, - message_id: response.message.discriminant(), + message_id: result.message.discriminant(), }; let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, response.message.clone())) + .serialize_packet(reply_context, (header, result.message.clone())) .map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); Error::Serialize @@ -142,9 +141,9 @@ impl Service<'_> { async fn send_mctp_error_response(&self, endpoint: EndpointID, espi: &mut espi::Espi<'static>) { // TODO we may want to add more detail in future, but that will require more integration with the debug service - let error_msg = HostResponseMessage { + let error_msg = HostResultMessage { source_endpoint: endpoint, - message: HostResponse::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), + message: HostResult::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), }; self.serialize_packet_from_subsystem(espi, &error_msg) .await @@ -153,7 +152,7 @@ impl Service<'_> { }); } - pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResponseMessage) { + pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResultMessage) { match self.serialize_packet_from_subsystem(espi, &response).await { Err(e) => { error!("Packet serialize error {:?}", e); @@ -173,11 +172,11 @@ impl Service<'_> { fn queue_response_to_host( &self, source_endpoint: EndpointID, - message: HostResponse, + message: HostResult, ) -> Result<(), comms::MailboxDelegateError> { debug!("Espi service: recvd response"); self.host_tx_queue - .try_send(HostResponseMessage { + .try_send(HostResultMessage { source_endpoint, message, }) @@ -189,7 +188,7 @@ impl Service<'_> { impl comms::MailboxDelegate for Service<'_> { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - crate::mctp::try_route_request_to_comms(message, |source_endpoint, message| { + crate::mctp::send_to_comms(message, |source_endpoint, message| { self.queue_response_to_host(source_endpoint, message) }) } diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs index 9820f58d8..7ef3968e0 100644 --- a/espi-service/src/mctp.rs +++ b/espi-service/src/mctp.rs @@ -1,354 +1,16 @@ -use bitfield::bitfield; -use core::convert::Infallible; -use embedded_services::{ - comms, - relay::{SerializableMessage, SerializableResponse}, -}; -use mctp_rs::smbus_espi::SmbusEspiMedium; -use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; - -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub(crate) enum OdpService { - Battery = 0x08, - Thermal = 0x09, - Debug = 0x0A, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum MctpError { - // The endpoint ID does not correspond to a known service - UnknownEndpointId, -} - -impl TryFrom for OdpService { - type Error = MctpError; - fn try_from(endpoint_id: comms::EndpointID) -> Result { - match endpoint_id { - comms::EndpointID::Internal(comms::Internal::Battery) => Ok(OdpService::Battery), - comms::EndpointID::Internal(comms::Internal::Thermal) => Ok(OdpService::Thermal), - comms::EndpointID::Internal(comms::Internal::Debug) => Ok(OdpService::Debug), - _ => Err(MctpError::UnknownEndpointId), - } - } -} - -impl OdpService { - pub fn get_endpoint_id(&self) -> comms::EndpointID { - match self { - OdpService::Battery => comms::EndpointID::Internal(comms::Internal::Battery), - OdpService::Thermal => comms::EndpointID::Internal(comms::Internal::Thermal), - OdpService::Debug => comms::EndpointID::Internal(comms::Internal::Debug), - } - } -} - -// TODO We'd ideally like these types to be passed in as a generic or something when the eSPI service is instantiated -// so the eSPI service can be extended to handle 3rd party message types without needing to fork the eSPI service, -// but that's dependant on us migrating to have storage for the eSPI service be allocated by the caller of init() -// rather than statically allocated inside this module, so for now we accept this hardcoded list of supported message -// types, and we can maybe convert this to a macro that accepts a list of types at some point. -// New services should follow the pattern of defining their own message crates using the request/response -// traits, and the only additions here should be the mapping between message types and endpoint IDs. -// -// Additionally, we probably want some sort of macro that can generate most or all of this from a table mapping service IDs -// to (request type, response type, comms endpoint) tuples for maintainability. -// -pub(crate) enum HostRequest { - Battery(battery_service_messages::AcpiBatteryRequest), - Debug(debug_service_messages::DebugRequest), - Thermal(thermal_service_messages::ThermalRequest), -} - -impl MctpMessageTrait<'_> for HostRequest { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - HostRequest::Battery(request) => request - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize battery request")), - - HostRequest::Debug(request) => request - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize debug request")), - - HostRequest::Thermal(request) => request - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize thermal request")), - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - OdpService::Battery => Self::Battery( - battery_service_messages::AcpiBatteryRequest::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse battery request"))?, - ), - OdpService::Debug => Self::Debug( - debug_service_messages::DebugRequest::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse debug request"))?, - ), - OdpService::Thermal => Self::Thermal( - thermal_service_messages::ThermalRequest::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse thermal request"))?, - ), - }) - } -} - -impl HostRequest { - pub(crate) async fn send_to_endpoint( - &self, - source_endpoint: &comms::Endpoint, - destination_endpoint_id: comms::EndpointID, - ) -> Result<(), Infallible> { - match self { - HostRequest::Battery(request) => source_endpoint.send(destination_endpoint_id, request).await, - HostRequest::Debug(request) => source_endpoint.send(destination_endpoint_id, request).await, - HostRequest::Thermal(request) => source_endpoint.send(destination_endpoint_id, request).await, - } - } -} - -#[derive(Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum HostResponse { - Battery(Result), - Debug(Result), - Thermal(Result), -} - -impl MctpMessageTrait<'_> for HostResponse { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - HostResponse::Battery(response) => response - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize battery response")), - - HostResponse::Debug(response) => response - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize debug response")), - - HostResponse::Thermal(response) => response - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError("Failed to serialize thermal response")), - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - OdpService::Battery => { - if let Ok(success) = - battery_service_messages::AcpiBatteryResponse::deserialize(header.message_id, buffer) - { - Self::Battery(Ok(success)) - } else { - let error = battery_service_messages::AcpiBatteryError::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse battery error response"))?; - Self::Battery(Err(error)) - } - } - OdpService::Debug => { - if let Ok(success) = debug_service_messages::DebugResponse::deserialize(header.message_id, buffer) { - Self::Debug(Ok(success)) - } else { - let error = debug_service_messages::DebugError::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse debug error response"))?; - Self::Debug(Err(error)) - } - } - OdpService::Thermal => { - if let Ok(success) = thermal_service_messages::ThermalResponse::deserialize(header.message_id, buffer) { - Self::Thermal(Ok(success)) - } else { - let error = thermal_service_messages::ThermalError::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError("Could not parse thermal error response"))?; - Self::Thermal(Err(error)) - } - } - }) - } -} - -impl HostResponse { - pub(crate) fn discriminant(&self) -> u16 { - match self { - HostResponse::Battery(response) => response.discriminant(), - HostResponse::Debug(response) => response.discriminant(), - HostResponse::Thermal(response) => response.discriminant(), - } - } - - pub(crate) fn is_ok(&self) -> bool { - match self { - HostResponse::Battery(response) => response.is_ok(), - HostResponse::Debug(response) => response.is_ok(), - HostResponse::Thermal(response) => response.is_ok(), - } - } -} - -/// Attempt to route the provided message to the service that is registered to handle it based on its type. -pub(crate) fn try_route_request_to_comms( - message: &comms::Message, - send_fn: impl FnOnce(comms::EndpointID, HostResponse) -> Result<(), comms::MailboxDelegateError>, -) -> Result<(), comms::MailboxDelegateError> { - // TODO we're going to have a bunch of types that all implement the SerializableResponse trait; in C++ I'd reach for dynamic_cast or a pointer-to-interface, - // but not sure how to do that with Rust's Any - it seems like it requires a concrete type rather than a trait to cast. Is there a cleaner way to - // say "if the message implements the SerializableResponse trait" so we don't have to spell out all the types here? - // - if let Some(msg) = message - .data - .get::>() - { - send_fn( - comms::EndpointID::Internal(comms::Internal::Battery), - HostResponse::Battery(*msg), - )?; - Ok(()) - } else if let Some(msg) = message - .data - .get::>() - { - send_fn( - comms::EndpointID::Internal(comms::Internal::Debug), - HostResponse::Debug(*msg), - )?; - Ok(()) - } else if let Some(msg) = message - .data - .get::>() - { - send_fn( - comms::EndpointID::Internal(comms::Internal::Thermal), - HostResponse::Thermal(*msg), - )?; - Ok(()) - } else { - Err(comms::MailboxDelegateError::MessageNotFound) - } -} - -bitfield! { - /// Raw bitfield of possible port status events - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct OdpHeaderWireFormat(u32); - impl Debug; - impl new; - /// If true, represents a request; otherwise, represents a response - is_request, set_is_request: 25; - - // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - is_datagram, set_is_datagram: 24; - - /// The service ID that this message is related to - /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. - u8, service_id, set_service_id: 23, 16; - - /// On responses, indicates if the response message is an error. Unused on requests. - is_error, set_is_error: 15; - - /// The message type/discriminant - u16, message_id, set_message_id: 14, 0; - -} - -#[derive(Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) enum OdpMessageType { - Request, - Response { is_error: bool }, -} - -#[derive(Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct OdpHeader { - pub message_type: OdpMessageType, - pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - pub service: OdpService, - pub message_id: u16, -} - -impl From for OdpHeaderWireFormat { - fn from(src: OdpHeader) -> Self { - Self::new( - matches!(src.message_type, OdpMessageType::Request), - src.is_datagram, - src.service.into(), - match src.message_type { - OdpMessageType::Request => false, // unused on requests - OdpMessageType::Response { is_error } => is_error, - }, - src.message_id, - ) - } -} - -impl TryFrom for OdpHeader { - type Error = MctpPacketError; - - fn try_from(src: OdpHeaderWireFormat) -> Result { - let service = OdpService::try_from(src.service_id()) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; - - let message_type = if src.is_request() { - OdpMessageType::Request - } else { - OdpMessageType::Response { - is_error: src.is_error(), - } - }; - - Ok(OdpHeader { - message_type, - is_datagram: src.is_datagram(), - service, - message_id: src.message_id(), - }) - } -} - -impl MctpMessageHeaderTrait for OdpHeader { - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - let wire_format = OdpHeaderWireFormat::from(self); - let bytes = wire_format.0.to_be_bytes(); - buffer - .get_mut(0..bytes.len()) - .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? - .copy_from_slice(&bytes); - - Ok(bytes.len()) - } - - fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { - let bytes = buffer - .get(0..core::mem::size_of::()) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; - let raw = u32::from_be_bytes( - bytes - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - ); - - let parsed_wire_format = OdpHeaderWireFormat(raw); - let header = OdpHeader::try_from(parsed_wire_format) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; - - Ok(( - header, - buffer - .get(core::mem::size_of::()..) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - )) - } -} +use embedded_services::{ + comms, + relay::{SerializableMessage, SerializableResult, mctp::impl_odp_mctp_relay_types}, +}; + +// TODO We'd ideally like these types to be passed in as a generic or something when the eSPI service is instantiated +// so the eSPI service can be extended to handle 3rd party message types without needing to fork the eSPI service, +// but that's dependant on us migrating to have storage for the eSPI service be allocated by the caller of init() +// rather than statically allocated inside this module, so for now we accept this hardcoded list of supported message +// types. + +impl_odp_mctp_relay_types!( + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; +); From 6615aed1282de848f3d3b5234c12fbbe55e57c4c Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Wed, 14 Jan 2026 11:37:48 -0800 Subject: [PATCH 05/79] Add uart-service (#674) Adds a basic uart service which can act a host service when eSPI is not available. Still uses the `SmbusEspiMedium` even though the SMBUS header isn't necessary just to keep code changes on the host side minimal when switching between UART/ESPI. Did some testing on the IMXRT board and can send/receive MCTP packets from host over a COM port successfully. Might catch additional bugs as I work on getting the ratatui app working over UART and will fix them as they come. Resolves #605 --- Cargo.lock | 19 +++++ Cargo.toml | 1 + uart-service/Cargo.toml | 45 ++++++++++ uart-service/src/lib.rs | 173 +++++++++++++++++++++++++++++++++++++++ uart-service/src/mctp.rs | 12 +++ uart-service/src/task.rs | 31 +++++++ 6 files changed, 281 insertions(+) create mode 100644 uart-service/Cargo.toml create mode 100644 uart-service/src/lib.rs create mode 100644 uart-service/src/mctp.rs create mode 100644 uart-service/src/task.rs diff --git a/Cargo.lock b/Cargo.lock index a48728f17..ee0606d4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2240,6 +2240,25 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "uart-service" +version = "0.1.0" +dependencies = [ + "battery-service-messages", + "bitfield 0.17.0", + "debug-service-messages", + "defmt 0.3.100", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-io-async", + "embedded-services", + "log", + "mctp-rs", + "num_enum", + "thermal-service-messages", +] + [[package]] name = "ufmt-write" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3f7b0142a..c71070098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "cfu-service", "embedded-service", "espi-service", + "uart-service", "hid-service", "partition-manager/generation", "partition-manager/macros", diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml new file mode 100644 index 000000000..f90ff1e86 --- /dev/null +++ b/uart-service/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "uart-service" +version = "0.1.0" +edition = "2024" +description = "UART embedded service implementation" +repository = "https://github.com/OpenDevicePartnership/embedded-services" +rust-version = "1.88" +license = "MIT" + +[lints] +workspace = true + +[dependencies] +bitfield.workspace = true +embedded-services.workspace = true +defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } +embassy-time.workspace = true +embassy-sync.workspace = true +embassy-futures.workspace = true +mctp-rs = { workspace = true } +embedded-io-async.workspace = true +num_enum.workspace = true + +# TODO Service message type crates are a temporary dependency until we can parameterize +# the supported messages types at UART service creation time. +battery-service-messages.workspace = true +debug-service-messages.workspace = true +thermal-service-messages.workspace = true + +[features] +default = [] +defmt = [ + "dep:defmt", + "embedded-services/defmt", + "embassy-time/defmt", + "embassy-time/defmt-timestamp-uptime", + "embassy-sync/defmt", + "mctp-rs/defmt", + "thermal-service-messages/defmt", + "battery-service-messages/defmt", + "debug-service-messages/defmt", +] + +log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/uart-service/src/lib.rs b/uart-service/src/lib.rs new file mode 100644 index 000000000..afac706fc --- /dev/null +++ b/uart-service/src/lib.rs @@ -0,0 +1,173 @@ +//! uart-service +//! +//! To keep things consistent with eSPI service, this also uses the `SmbusEspiMedium` (though not +//! strictly necessary, this helps minimize code changes on the host side when swicthing between +//! eSPI or UART). +//! +//! Revisit: Will also need to consider how to handle notifications (likely need to have user +//! provide GPIO pin we can use). +#![no_std] + +mod mctp; +pub mod task; + +use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService}; +use core::borrow::BorrowMut; +use embassy_sync::channel::Channel; +use embedded_io_async::Read as UartRead; +use embedded_io_async::Write as UartWrite; +use embedded_services::GlobalRawMutex; +use embedded_services::buffer::OwnedRef; +use embedded_services::comms::{self, Endpoint, EndpointID, External}; +use embedded_services::trace; +use mctp_rs::smbus_espi::SmbusEspiMedium; +use mctp_rs::smbus_espi::SmbusEspiReplyContext; + +// Should be as large as the largest possible MCTP packet and its metadata. +const BUF_SIZE: usize = 256; +const HOST_TX_QUEUE_SIZE: usize = 5; +const SMBUS_HEADER_SIZE: usize = 4; +const SMBUS_LEN_IDX: usize = 2; + +embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; BUF_SIZE]); + +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct HostResponseMessage { + pub source_endpoint: EndpointID, + pub message: HostResult, +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Comms error. + Comms, + /// UART error. + Uart, + /// MCTP serialization error. + Mctp(mctp_rs::MctpPacketError), + /// Other serialization error. + Serialize(&'static str), + /// Index/slice error. + IndexSlice, + /// Buffer error. + Buffer(embedded_services::buffer::Error), +} + +pub struct Service<'a> { + endpoint: Endpoint, + host_tx_queue: Channel, + assembly_buf_owned_ref: OwnedRef<'a, u8>, +} + +impl Service<'_> { + pub fn new() -> Result { + Ok(Self { + endpoint: Endpoint::uninit(EndpointID::External(External::Host)), + host_tx_queue: Channel::new(), + assembly_buf_owned_ref: assembly_buf::get_mut() + .ok_or(Error::Buffer(embedded_services::buffer::Error::InvalidRange))?, + }) + } + + async fn process_response(&self, uart: &mut T, response: &HostResponseMessage) -> Result<(), Error> { + let mut assembly_buf_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; + let pkt_ctx_buf = assembly_buf_access.borrow_mut(); + let mut mctp_ctx = mctp_rs::MctpPacketContext::new(SmbusEspiMedium, pkt_ctx_buf); + + let source_service: OdpService = OdpService::try_from(response.source_endpoint).map_err(|_| Error::Comms)?; + + let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { + source_endpoint_id: mctp_rs::EndpointId::Id(0x80), + destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), + packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), + message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(Error::Serialize)?, + medium_context: SmbusEspiReplyContext { + destination_slave_address: 1, + source_slave_address: 0, + }, // Medium-specific context + }; + + let header = OdpHeader { + message_type: OdpMessageType::Result { + is_error: !response.message.is_ok(), + }, + is_datagram: false, + service: source_service, + message_id: response.message.discriminant(), + }; + + let mut packet_state = mctp_ctx + .serialize_packet(reply_context, (header, response.message.clone())) + .map_err(Error::Mctp)?; + + while let Some(packet_result) = packet_state.next() { + let packet = packet_result.map_err(Error::Mctp)?; + // Last byte is PEC, ignore for now + let packet = packet.get(..packet.len() - 1).ok_or(Error::IndexSlice)?; + + // Then actually send the response packet (which includes 4-byte SMBUS header containing payload size) + uart.write_all(packet).await.map_err(|_| Error::Uart)?; + } + + Ok(()) + } + + async fn wait_for_request(&self, uart: &mut T) -> Result<(), Error> { + let mut assembly_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; + let mut mctp_ctx = + mctp_rs::MctpPacketContext::::new(SmbusEspiMedium, assembly_access.borrow_mut()); + + // First wait for SMBUS header, which tells us how big the incoming packet is + let mut buf = [0; BUF_SIZE]; + uart.read_exact(buf.get_mut(..SMBUS_HEADER_SIZE).ok_or(Error::IndexSlice)?) + .await + .map_err(|_| Error::Uart)?; + + // Then wait until we've received the full payload + let len = *buf.get(SMBUS_LEN_IDX).ok_or(Error::IndexSlice)? as usize; + uart.read_exact( + buf.get_mut(SMBUS_HEADER_SIZE..SMBUS_HEADER_SIZE + len) + .ok_or(Error::IndexSlice)?, + ) + .await + .map_err(|_| Error::Uart)?; + + let message = mctp_ctx + .deserialize_packet(&buf) + .map_err(Error::Mctp)? + .ok_or(Error::Serialize("Partial message not supported"))?; + + let (header, host_request) = message.parse_as::().map_err(Error::Mctp)?; + let target_endpoint: EndpointID = header.service.get_endpoint_id(); + trace!( + "Host Request: Service {:?}, Command {:?}", + target_endpoint, header.message_id, + ); + + host_request + .send_to_endpoint(&self.endpoint, target_endpoint) + .await + .map_err(|_| Error::Comms)?; + + Ok(()) + } + + async fn wait_for_response(&self) -> HostResponseMessage { + self.host_tx_queue.receive().await + } +} + +impl comms::MailboxDelegate for Service<'_> { + fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { + crate::mctp::send_to_comms(message, |source_endpoint, message| { + self.host_tx_queue + .try_send(HostResponseMessage { + source_endpoint, + message, + }) + .map_err(|_| comms::MailboxDelegateError::BufferFull) + }) + } +} diff --git a/uart-service/src/mctp.rs b/uart-service/src/mctp.rs new file mode 100644 index 000000000..3c9fe10c4 --- /dev/null +++ b/uart-service/src/mctp.rs @@ -0,0 +1,12 @@ +use embedded_services::{ + comms, + relay::{SerializableMessage, SerializableResult, mctp::impl_odp_mctp_relay_types}, +}; + +// TODO We'd ideally like these types to be passed in as a generic or something when the UART service is instantiated +// so the UART service can be extended to handle 3rd party message types without needing to fork the UART service +impl_odp_mctp_relay_types!( + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; +); diff --git a/uart-service/src/task.rs b/uart-service/src/task.rs new file mode 100644 index 000000000..d77e4fccf --- /dev/null +++ b/uart-service/src/task.rs @@ -0,0 +1,31 @@ +use crate::{Error, Service}; +use embedded_io_async::Read as UartRead; +use embedded_io_async::Write as UartWrite; +use embedded_services::comms; +use embedded_services::error; + +pub async fn uart_service( + uart_service: &'static Service<'_>, + mut uart: T, +) -> Result { + // Register uart-service as the host service + comms::register_endpoint(uart_service, &uart_service.endpoint) + .await + .map_err(|_| Error::Comms)?; + + // Note: eSPI service uses `select!` to seemingly allow asyncrhonous `responses` from services, + // but there are concerns around async cancellation here at least for UART service. + // + // Thus this assumes services will only send messages in response to requests from the host, + // so we handle this in order. + loop { + if let Err(e) = uart_service.wait_for_request(&mut uart).await { + error!("uart-service request error: {:?}", e); + } else { + let host_msg = uart_service.wait_for_response().await; + if let Err(e) = uart_service.process_response(&mut uart, &host_msg).await { + error!("uart-service response error: {:?}", e) + } + } + } +} From e2a466fbaaa4bdd1e26ce4d5b226e8722303d214 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Tue, 20 Jan 2026 11:16:07 -0800 Subject: [PATCH 06/79] embedded-services: Add missing serialization to service messages (#679) Some serialization/deserialization was omitted because it wouldn't be used by the EC, however the service-message crates can be used outside the EC, so add them in. I experimented with zerocopy for this in #678 but it's a bit clunky and getting it to work for Battery messages will be challenging, so just rolled it by hand for now since that's what we are already doing. Can revisit later once we've decided on a serialization strategy. Tested these by using the service message crates to serialize/deserialize messages over UART in the ratatui and works fine for the ones I could test (though many of the battery commands are not yet supported by the ratatui app). Was going to wait until https://github.com/OpenDevicePartnership/embedded-batteries/pull/39 is merged, but it seems relying on an updated `embedded-batteries` causes a ton of breakage and headache, so just have functions for converting in here and can update a later time. --- battery-service-messages/src/lib.rs | 738 +++++++++++++--------------- debug-service-messages/src/lib.rs | 26 +- thermal-service-messages/src/lib.rs | 141 ++++-- 3 files changed, 463 insertions(+), 442 deletions(-) diff --git a/battery-service-messages/src/lib.rs b/battery-service-messages/src/lib.rs index a219730e7..c00bf352e 100644 --- a/battery-service-messages/src/lib.rs +++ b/battery-service-messages/src/lib.rs @@ -1,11 +1,62 @@ #![no_std] -use embedded_batteries_async::acpi::{ - BCT_RETURN_SIZE_BYTES, BMD_RETURN_SIZE_BYTES, BPC_RETURN_SIZE_BYTES, BPS_RETURN_SIZE_BYTES, BST_RETURN_SIZE_BYTES, - BTM_RETURN_SIZE_BYTES, PSR_RETURN_SIZE_BYTES, STA_RETURN_SIZE_BYTES, +use embedded_batteries_async::acpi::ThresholdId; +pub use embedded_batteries_async::acpi::{ + BatteryState, BatterySwapCapability, BatteryTechnology, Bct, BctReturnResult, Bma, Bmc, BmcControlFlags, Bmd, + BmdCapabilityFlags, BmdStatusFlags, Bms, Bpc, Bps, Bpt, BstReturn, Btm, BtmReturnResult, Btp, PowerSource, + PowerSourceState, PowerThresholdSupport, PowerUnit, PsrReturn, StaReturn, }; use embedded_services::relay::{MessageSerializationError, SerializableMessage}; +// Unfortunately `TryFrom` is not implemented by embedded-batteries for these types + +/// Attempt to convert a `u32` to a `PowerUnit`. +pub fn power_unit_try_from_u32(value: u32) -> Result { + match value { + 0 => Ok(PowerUnit::MilliWatts), + 1 => Ok(PowerUnit::MilliAmps), + _ => Err(MessageSerializationError::InvalidPayload("Invalid PowerUnit")), + } +} + +/// Attempt to convert a `u32` to a `BatteryTechnology`. +pub fn bat_tech_try_from_u32(value: u32) -> Result { + match value { + 0 => Ok(BatteryTechnology::Primary), + 1 => Ok(BatteryTechnology::Secondary), + _ => Err(MessageSerializationError::InvalidPayload("Invalid BatteryTechnology")), + } +} + +/// Attempt to convert a `u32` to a `BatterySwapCapability`. +pub fn bat_swap_try_from_u32(value: u32) -> Result { + match value { + 0 => Ok(BatterySwapCapability::NonSwappable), + 1 => Ok(BatterySwapCapability::ColdSwappable), + 2 => Ok(BatterySwapCapability::HotSwappable), + _ => Err(MessageSerializationError::InvalidPayload("Invalid BatteryTechnology")), + } +} + +/// Attempt to convert a `u32` to a `ThresholdId`. +pub fn thres_id_try_from_u32(value: u32) -> Result { + match value { + 0 => Ok(ThresholdId::ClearAll), + 1 => Ok(ThresholdId::InstantaneousPeakPower), + 2 => Ok(ThresholdId::SustainablePeakPower), + _ => Err(MessageSerializationError::InvalidPayload("Invalid ThresholdId")), + } +} + +/// Attempt to convert a `u32` to a `PowerSource`. +pub fn pwr_src_try_from_u32(value: u32) -> Result { + match value { + 0 => Ok(PowerSource::Offline), + 1 => Ok(PowerSource::Online), + _ => Err(MessageSerializationError::InvalidPayload("Invalid PowerSource")), + } +} + /// Standard Battery Service Model Number String Size pub const STD_BIX_MODEL_SIZE: usize = 8; /// Standard Battery Service Serial Number String Size @@ -102,19 +153,28 @@ impl From<&AcpiBatteryResponse> for BatteryCmd { } } -#[derive(PartialEq, Clone, Copy)] +const BIX_MODEL_NUM_START_IDX: usize = 64; +const BIX_MODEL_NUM_END_IDX: usize = BIX_MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; +const BIX_SERIAL_NUM_START_IDX: usize = BIX_MODEL_NUM_END_IDX; +const BIX_SERIAL_NUM_END_IDX: usize = BIX_SERIAL_NUM_START_IDX + STD_BIX_SERIAL_SIZE; +const BIX_BATTERY_TYPE_START_IDX: usize = BIX_SERIAL_NUM_END_IDX; +const BIX_BATTERY_TYPE_END_IDX: usize = BIX_BATTERY_TYPE_START_IDX + STD_BIX_BATTERY_SIZE; +const BIX_OEM_INFO_START_IDX: usize = BIX_BATTERY_TYPE_END_IDX; +const BIX_OEM_INFO_END_IDX: usize = BIX_OEM_INFO_START_IDX + STD_BIX_OEM_SIZE; + +#[derive(PartialEq, Clone, Copy, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct BixFixedStrings { /// Revision of the BIX structure. Current revision is 1. pub revision: u32, /// Unit used for capacity and rate values. - pub power_unit: embedded_batteries_async::acpi::PowerUnit, + pub power_unit: PowerUnit, /// Design capacity of the battery (in mWh or mAh). pub design_capacity: u32, /// Last full charge capacity (in mWh or mAh). pub last_full_charge_capacity: u32, /// Battery technology type. - pub battery_technology: embedded_batteries_async::acpi::BatteryTechnology, + pub battery_technology: BatteryTechnology, /// Design voltage (in mV). pub design_voltage: u32, /// Warning capacity threshold (in mWh or mAh). @@ -146,118 +206,78 @@ pub struct BixFixedStrings { /// OEM-specific information (ASCIIZ). pub oem_info: [u8; STD_BIX_OEM_SIZE], /// Battery swapping capability. - pub battery_swapping_capability: embedded_batteries_async::acpi::BatterySwapCapability, + pub battery_swapping_capability: BatterySwapCapability, } // TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? impl BixFixedStrings { - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), MessageSerializationError> { - const MODEL_NUM_START_IDX: usize = 64; - let model_num_end_idx: usize = MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; - let serial_num_start_idx = model_num_end_idx; - let serial_num_end_idx = serial_num_start_idx + STD_BIX_SERIAL_SIZE; - let battery_type_start_idx = serial_num_end_idx; - let battery_type_end_idx = battery_type_start_idx + STD_BIX_BATTERY_SIZE; - let oem_info_start_idx = battery_type_end_idx; - let oem_info_end_idx = oem_info_start_idx + STD_BIX_OEM_SIZE; - - if dst_slice.len() < oem_info_end_idx { + pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result { + if dst_slice.len() < BIX_OEM_INFO_END_IDX { return Err(MessageSerializationError::BufferTooSmall); } - dst_slice - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.revision)); - dst_slice - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.power_unit.into())); - dst_slice - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.design_capacity)); - dst_slice - .get_mut(12..16) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.last_full_charge_capacity)); - dst_slice - .get_mut(16..20) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.battery_technology.into())); - dst_slice - .get_mut(20..24) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.design_voltage)); - dst_slice - .get_mut(24..28) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.design_cap_of_warning)); - dst_slice - .get_mut(28..32) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.design_cap_of_low)); - dst_slice - .get_mut(32..36) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.cycle_count)); - dst_slice - .get_mut(36..40) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.measurement_accuracy)); - dst_slice - .get_mut(40..44) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.max_sampling_time)); - dst_slice - .get_mut(44..48) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.min_sampling_time)); - dst_slice - .get_mut(48..52) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.max_averaging_interval)); - dst_slice - .get_mut(52..56) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.min_averaging_interval)); - dst_slice - .get_mut(56..60) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_1)); - dst_slice - .get_mut(60..64) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.battery_capacity_granularity_2)); - dst_slice - .get_mut(MODEL_NUM_START_IDX..model_num_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.model_number); - dst_slice - .get_mut(serial_num_start_idx..serial_num_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.serial_number); - dst_slice - .get_mut(battery_type_start_idx..battery_type_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.battery_type); - dst_slice - .get_mut(oem_info_start_idx..oem_info_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.oem_info); - dst_slice - .get_mut(oem_info_end_idx..oem_info_end_idx + 4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.battery_swapping_capability.into())); - Ok(()) + Ok(safe_put_dword(dst_slice, 0, self.revision)? + + safe_put_dword(dst_slice, 4, self.power_unit.into())? + + safe_put_dword(dst_slice, 8, self.design_capacity)? + + safe_put_dword(dst_slice, 12, self.last_full_charge_capacity)? + + safe_put_dword(dst_slice, 16, self.battery_technology.into())? + + safe_put_dword(dst_slice, 20, self.design_voltage)? + + safe_put_dword(dst_slice, 24, self.design_cap_of_warning)? + + safe_put_dword(dst_slice, 28, self.design_cap_of_low)? + + safe_put_dword(dst_slice, 32, self.cycle_count)? + + safe_put_dword(dst_slice, 36, self.measurement_accuracy)? + + safe_put_dword(dst_slice, 40, self.max_sampling_time)? + + safe_put_dword(dst_slice, 44, self.min_sampling_time)? + + safe_put_dword(dst_slice, 48, self.max_averaging_interval)? + + safe_put_dword(dst_slice, 52, self.min_averaging_interval)? + + safe_put_dword(dst_slice, 56, self.battery_capacity_granularity_1)? + + safe_put_dword(dst_slice, 60, self.battery_capacity_granularity_2)? + + safe_put_bytes(dst_slice, BIX_MODEL_NUM_START_IDX, &self.model_number)? + + safe_put_bytes(dst_slice, BIX_SERIAL_NUM_START_IDX, &self.serial_number)? + + safe_put_bytes(dst_slice, BIX_BATTERY_TYPE_START_IDX, &self.battery_type)? + + safe_put_bytes(dst_slice, BIX_OEM_INFO_START_IDX, &self.oem_info)? + + safe_put_dword(dst_slice, BIX_OEM_INFO_END_IDX, self.battery_swapping_capability.into())?) + } + + pub fn from_bytes(src_slice: &[u8]) -> Result { + Ok(Self { + revision: safe_get_dword(src_slice, 0)?, + power_unit: power_unit_try_from_u32(safe_get_dword(src_slice, 4)?)?, + design_capacity: safe_get_dword(src_slice, 8)?, + last_full_charge_capacity: safe_get_dword(src_slice, 12)?, + battery_technology: bat_tech_try_from_u32(safe_get_dword(src_slice, 16)?)?, + design_voltage: safe_get_dword(src_slice, 20)?, + design_cap_of_warning: safe_get_dword(src_slice, 24)?, + design_cap_of_low: safe_get_dword(src_slice, 28)?, + cycle_count: safe_get_dword(src_slice, 32)?, + measurement_accuracy: safe_get_dword(src_slice, 36)?, + max_sampling_time: safe_get_dword(src_slice, 40)?, + min_sampling_time: safe_get_dword(src_slice, 44)?, + max_averaging_interval: safe_get_dword(src_slice, 48)?, + min_averaging_interval: safe_get_dword(src_slice, 52)?, + battery_capacity_granularity_1: safe_get_dword(src_slice, 56)?, + battery_capacity_granularity_2: safe_get_dword(src_slice, 60)?, + model_number: safe_get_bytes::(src_slice, BIX_MODEL_NUM_START_IDX)?, + serial_number: safe_get_bytes::(src_slice, BIX_SERIAL_NUM_START_IDX)?, + battery_type: safe_get_bytes::(src_slice, BIX_BATTERY_TYPE_START_IDX)?, + oem_info: safe_get_bytes::(src_slice, BIX_OEM_INFO_START_IDX)?, + battery_swapping_capability: bat_swap_try_from_u32(safe_get_dword(src_slice, BIX_OEM_INFO_END_IDX)?)?, + }) } } +const PIF_MODEL_NUM_START_IDX: usize = 12; +const PIF_MODEL_NUM_END_IDX: usize = PIF_MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; +const PIF_SERIAL_NUM_START_IDX: usize = PIF_MODEL_NUM_END_IDX; +const PIF_SERIAL_NUM_END_IDX: usize = PIF_SERIAL_NUM_START_IDX + STD_BIX_SERIAL_SIZE; +const PIF_OEM_INFO_START_IDX: usize = PIF_SERIAL_NUM_END_IDX; +const PIF_OEM_INFO_END_IDX: usize = PIF_OEM_INFO_START_IDX + STD_BIX_OEM_SIZE; + #[derive(PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PifFixedStrings { /// Bitfield describing the state and characteristics of the power source. - pub power_source_state: embedded_batteries_async::acpi::PowerSourceState, + pub power_source_state: PowerSourceState, /// Maximum rated output power in milliwatts (mW). /// /// 0xFFFFFFFF indicates the value is unavailable. @@ -275,109 +295,89 @@ pub struct PifFixedStrings { } impl PifFixedStrings { - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result<(), MessageSerializationError> { - const MODEL_NUM_START_IDX: usize = 12; - let model_num_end_idx: usize = MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; - let serial_num_start_idx = model_num_end_idx; - let serial_num_end_idx = serial_num_start_idx + STD_BIX_SERIAL_SIZE; - let oem_info_start_idx = serial_num_end_idx; - let oem_info_end_idx = oem_info_start_idx + STD_BIX_OEM_SIZE; - - if dst_slice.len() < oem_info_end_idx { + pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result { + if dst_slice.len() < PIF_OEM_INFO_END_IDX { return Err(MessageSerializationError::BufferTooSmall); } - dst_slice - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.power_source_state.bits())); - dst_slice - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.max_output_power)); - dst_slice - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(self.max_input_power)); - dst_slice - .get_mut(MODEL_NUM_START_IDX..model_num_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.model_number); - dst_slice - .get_mut(serial_num_start_idx..serial_num_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.serial_number); - dst_slice - .get_mut(oem_info_start_idx..oem_info_end_idx) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&self.oem_info); - Ok(()) + Ok(safe_put_dword(dst_slice, 0, self.power_source_state.bits())? + + safe_put_dword(dst_slice, 4, self.max_output_power)? + + safe_put_dword(dst_slice, 8, self.max_input_power)? + + safe_put_bytes(dst_slice, PIF_MODEL_NUM_START_IDX, &self.model_number)? + + safe_put_bytes(dst_slice, PIF_SERIAL_NUM_START_IDX, &self.serial_number)? + + safe_put_bytes(dst_slice, PIF_OEM_INFO_START_IDX, &self.oem_info)?) + } + + pub fn from_bytes(src_slice: &[u8]) -> Result { + Ok(Self { + power_source_state: PowerSourceState::from_bits(safe_get_dword(src_slice, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid PowerSourceState"))?, + max_output_power: safe_get_dword(src_slice, 4)?, + max_input_power: safe_get_dword(src_slice, 8)?, + model_number: safe_get_bytes::(src_slice, PIF_MODEL_NUM_START_IDX)?, + serial_number: safe_get_bytes::(src_slice, PIF_SERIAL_NUM_START_IDX)?, + oem_info: safe_get_bytes::(src_slice, PIF_OEM_INFO_START_IDX)?, + }) } } #[derive(PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcpiBatteryRequest { - BatteryGetBixRequest { - battery_id: u8, - }, - BatteryGetBstRequest { - battery_id: u8, - }, - BatteryGetPsrRequest { - battery_id: u8, - }, - BatteryGetPifRequest { - battery_id: u8, - }, - BatteryGetBpsRequest { - battery_id: u8, - }, - BatterySetBtpRequest { - battery_id: u8, - btp: embedded_batteries_async::acpi::Btp, - }, - BatterySetBptRequest { - battery_id: u8, - bpt: embedded_batteries_async::acpi::Bpt, - }, - BatteryGetBpcRequest { - battery_id: u8, - }, - BatterySetBmcRequest { - battery_id: u8, - bmc: embedded_batteries_async::acpi::Bmc, - }, - BatteryGetBmdRequest { - battery_id: u8, - }, - BatteryGetBctRequest { - battery_id: u8, - bct: embedded_batteries_async::acpi::Bct, - }, - BatteryGetBtmRequest { - battery_id: u8, - btm: embedded_batteries_async::acpi::Btm, - }, - BatterySetBmsRequest { - battery_id: u8, - bms: embedded_batteries_async::acpi::Bms, - }, - BatterySetBmaRequest { - battery_id: u8, - bma: embedded_batteries_async::acpi::Bma, - }, - BatteryGetStaRequest { - battery_id: u8, - }, + BatteryGetBixRequest { battery_id: u8 }, + BatteryGetBstRequest { battery_id: u8 }, + BatteryGetPsrRequest { battery_id: u8 }, + BatteryGetPifRequest { battery_id: u8 }, + BatteryGetBpsRequest { battery_id: u8 }, + BatterySetBtpRequest { battery_id: u8, btp: Btp }, + BatterySetBptRequest { battery_id: u8, bpt: Bpt }, + BatteryGetBpcRequest { battery_id: u8 }, + BatterySetBmcRequest { battery_id: u8, bmc: Bmc }, + BatteryGetBmdRequest { battery_id: u8 }, + BatteryGetBctRequest { battery_id: u8, bct: Bct }, + BatteryGetBtmRequest { battery_id: u8, btm: Btm }, + BatterySetBmsRequest { battery_id: u8, bms: Bms }, + BatterySetBmaRequest { battery_id: u8, bma: Bma }, + BatteryGetStaRequest { battery_id: u8 }, } impl SerializableMessage for AcpiBatteryRequest { - fn serialize(self, _buffer: &mut [u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to serialize requests on the EC side", - )) + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::BatteryGetBixRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatteryGetBstRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatteryGetPsrRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatteryGetPifRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatteryGetBpsRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatterySetBtpRequest { battery_id, btp } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, btp.trip_point)?) + } + Self::BatterySetBptRequest { battery_id, bpt } => Ok(safe_put_u8(buffer, 0, battery_id)? + + safe_put_dword(buffer, 1, bpt.revision)? + + safe_put_dword(buffer, 5, bpt.threshold_id as u32)? + + safe_put_dword(buffer, 9, bpt.threshold_value)?), + Self::BatteryGetBpcRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatterySetBmcRequest { battery_id, bmc } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + + safe_put_dword(buffer, 1, bmc.maintenance_control_flags.bits())?) + } + Self::BatteryGetBmdRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::BatteryGetBctRequest { battery_id, bct } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bct.charge_level_percent)?) + } + Self::BatteryGetBtmRequest { battery_id, btm } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, btm.discharge_rate)?) + } + Self::BatterySetBmsRequest { battery_id, bms } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bms.sampling_time_ms)?) + } + Self::BatterySetBmaRequest { battery_id, bma } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bma.averaging_interval_ms)?) + } + Self::BatteryGetStaRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), + } } + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { Ok( match BatteryCmd::try_from(discriminant) @@ -400,22 +400,15 @@ impl SerializableMessage for AcpiBatteryRequest { }, BatteryCmd::SetBtp => Self::BatterySetBtpRequest { battery_id: safe_get_u8(buffer, 0)?, - btp: embedded_batteries_async::acpi::Btp { + btp: Btp { trip_point: safe_get_dword(buffer, 1)?, }, }, BatteryCmd::SetBpt => Self::BatterySetBptRequest { battery_id: safe_get_u8(buffer, 0)?, - bpt: embedded_batteries_async::acpi::Bpt { + bpt: Bpt { revision: safe_get_dword(buffer, 1)?, - threshold_id: match safe_get_dword(buffer, 5)? { - 0 => embedded_batteries_async::acpi::ThresholdId::ClearAll, - 1 => embedded_batteries_async::acpi::ThresholdId::InstantaneousPeakPower, - 2 => embedded_batteries_async::acpi::ThresholdId::SustainablePeakPower, - _ => { - return Err(MessageSerializationError::InvalidPayload("Unsupported threshold id")); - } - }, + threshold_id: thres_id_try_from_u32(safe_get_dword(buffer, 5)?)?, threshold_value: safe_get_dword(buffer, 9)?, }, }, @@ -424,10 +417,8 @@ impl SerializableMessage for AcpiBatteryRequest { }, BatteryCmd::SetBmc => Self::BatterySetBmcRequest { battery_id: safe_get_u8(buffer, 0)?, - bmc: embedded_batteries_async::acpi::Bmc { - maintenance_control_flags: embedded_batteries_async::acpi::BmcControlFlags::from_bits_retain( - safe_get_dword(buffer, 1)?, - ), + bmc: Bmc { + maintenance_control_flags: BmcControlFlags::from_bits_retain(safe_get_dword(buffer, 1)?), }, }, BatteryCmd::GetBmd => Self::BatteryGetBmdRequest { @@ -435,25 +426,25 @@ impl SerializableMessage for AcpiBatteryRequest { }, BatteryCmd::GetBct => Self::BatteryGetBctRequest { battery_id: safe_get_u8(buffer, 0)?, - bct: embedded_batteries_async::acpi::Bct { + bct: Bct { charge_level_percent: safe_get_dword(buffer, 1)?, }, }, BatteryCmd::GetBtm => Self::BatteryGetBtmRequest { battery_id: safe_get_u8(buffer, 0)?, - btm: embedded_batteries_async::acpi::Btm { + btm: Btm { discharge_rate: safe_get_dword(buffer, 1)?, }, }, BatteryCmd::SetBms => Self::BatterySetBmsRequest { battery_id: safe_get_u8(buffer, 0)?, - bms: embedded_batteries_async::acpi::Bms { + bms: Bms { sampling_time_ms: safe_get_dword(buffer, 1)?, }, }, BatteryCmd::SetBma => Self::BatterySetBmaRequest { battery_id: safe_get_u8(buffer, 0)?, - bma: embedded_batteries_async::acpi::Bma { + bma: Bma { averaging_interval_ms: safe_get_dword(buffer, 1)?, }, }, @@ -472,199 +463,135 @@ impl SerializableMessage for AcpiBatteryRequest { #[derive(PartialEq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcpiBatteryResponse { - BatteryGetBixResponse { - bix: BixFixedStrings, - }, - BatteryGetBstResponse { - bst: embedded_batteries_async::acpi::BstReturn, - }, - BatteryGetPsrResponse { - psr: embedded_batteries_async::acpi::PsrReturn, - }, - BatteryGetPifResponse { - pif: PifFixedStrings, - }, - BatteryGetBpsResponse { - bps: embedded_batteries_async::acpi::Bps, - }, + BatteryGetBixResponse { bix: BixFixedStrings }, + BatteryGetBstResponse { bst: BstReturn }, + BatteryGetPsrResponse { psr: PsrReturn }, + BatteryGetPifResponse { pif: PifFixedStrings }, + BatteryGetBpsResponse { bps: Bps }, BatterySetBtpResponse {}, BatterySetBptResponse {}, - BatteryGetBpcResponse { - bpc: embedded_batteries_async::acpi::Bpc, - }, + BatteryGetBpcResponse { bpc: Bpc }, BatterySetBmcResponse {}, - BatteryGetBmdResponse { - bmd: embedded_batteries_async::acpi::Bmd, - }, - BatteryGetBctResponse { - bct_response: embedded_batteries_async::acpi::BctReturnResult, - }, - BatteryGetBtmResponse { - btm_response: embedded_batteries_async::acpi::BtmReturnResult, - }, - BatterySetBmsResponse { - status: u32, - }, - BatterySetBmaResponse { - status: u32, - }, - BatteryGetStaResponse { - sta: embedded_batteries_async::acpi::StaReturn, - }, + BatteryGetBmdResponse { bmd: Bmd }, + BatteryGetBctResponse { bct_response: BctReturnResult }, + BatteryGetBtmResponse { btm_response: BtmReturnResult }, + BatterySetBmsResponse { status: u32 }, + BatterySetBmaResponse { status: u32 }, + BatteryGetStaResponse { sta: StaReturn }, } impl SerializableMessage for AcpiBatteryResponse { fn serialize(self, buffer: &mut [u8]) -> Result { match self { - Self::BatteryGetBixResponse { bix } => bix.to_bytes(buffer).map(|_| 100), - Self::BatteryGetBstResponse { bst } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bst.battery_state.bits())); - buffer - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bst.battery_present_rate)); - buffer - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bst.battery_remaining_capacity)); - buffer - .get_mut(12..16) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bst.battery_present_voltage)); - - Ok(BST_RETURN_SIZE_BYTES) - } - Self::BatteryGetPsrResponse { psr } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(psr.power_source.into())); - - Ok(PSR_RETURN_SIZE_BYTES) - } - - Self::BatteryGetPifResponse { pif } => pif.to_bytes(buffer).map(|_| 36), - Self::BatteryGetBpsResponse { bps } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bps.revision)); - buffer - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_level)); - buffer - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bps.instantaneous_peak_power_period)); - buffer - .get_mut(12..16) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_level)); - buffer - .get_mut(16..20) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bps.sustainable_peak_power_period)); - - Ok(BPS_RETURN_SIZE_BYTES) - } + Self::BatteryGetBixResponse { bix } => bix.to_bytes(buffer), + Self::BatteryGetBstResponse { bst } => Ok(safe_put_dword(buffer, 0, bst.battery_state.bits())? + + safe_put_dword(buffer, 4, bst.battery_present_rate)? + + safe_put_dword(buffer, 8, bst.battery_remaining_capacity)? + + safe_put_dword(buffer, 12, bst.battery_present_voltage)?), + Self::BatteryGetPsrResponse { psr } => safe_put_dword(buffer, 0, psr.power_source.into()), + + Self::BatteryGetPifResponse { pif } => pif.to_bytes(buffer), + Self::BatteryGetBpsResponse { bps } => Ok(safe_put_dword(buffer, 0, bps.revision)? + + safe_put_dword(buffer, 4, bps.instantaneous_peak_power_level)? + + safe_put_dword(buffer, 8, bps.instantaneous_peak_power_period)? + + safe_put_dword(buffer, 12, bps.sustainable_peak_power_level)? + + safe_put_dword(buffer, 16, bps.sustainable_peak_power_period)?), Self::BatterySetBtpResponse {} => Ok(0), Self::BatterySetBptResponse {} => Ok(0), - Self::BatteryGetBpcResponse { bpc } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bpc.revision)); - buffer - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bpc.power_threshold_support.bits())); - buffer - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bpc.max_instantaneous_peak_power_threshold)); - buffer - .get_mut(12..16) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bpc.max_sustainable_peak_power_threshold)); - - Ok(BPC_RETURN_SIZE_BYTES) - } + Self::BatteryGetBpcResponse { bpc } => Ok(safe_put_dword(buffer, 0, bpc.revision)? + + safe_put_dword(buffer, 4, bpc.power_threshold_support.bits())? + + safe_put_dword(buffer, 8, bpc.max_instantaneous_peak_power_threshold)? + + safe_put_dword(buffer, 12, bpc.max_sustainable_peak_power_threshold)?), Self::BatterySetBmcResponse {} => Ok(0), - Self::BatteryGetBmdResponse { bmd } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bmd.status_flags.bits())); - buffer - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bmd.capability_flags.bits())); - buffer - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bmd.recalibrate_count)); - buffer - .get_mut(12..16) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bmd.quick_recalibrate_time)); - buffer - .get_mut(16..20) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bmd.slow_recalibrate_time)); - - Ok(BMD_RETURN_SIZE_BYTES) - } - Self::BatteryGetBctResponse { bct_response } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(bct_response.into())); - - Ok(BCT_RETURN_SIZE_BYTES) - } - Self::BatteryGetBtmResponse { btm_response } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(btm_response.into())); - - Ok(BTM_RETURN_SIZE_BYTES) - } - Self::BatterySetBmsResponse { status } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::BatterySetBmaResponse { status } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(status)); - - Ok(4) - } - Self::BatteryGetStaResponse { sta } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(sta.bits())); - - Ok(STA_RETURN_SIZE_BYTES) - } + Self::BatteryGetBmdResponse { bmd } => Ok(safe_put_dword(buffer, 0, bmd.status_flags.bits())? + + safe_put_dword(buffer, 4, bmd.capability_flags.bits())? + + safe_put_dword(buffer, 8, bmd.recalibrate_count)? + + safe_put_dword(buffer, 12, bmd.quick_recalibrate_time)? + + safe_put_dword(buffer, 16, bmd.slow_recalibrate_time)?), + Self::BatteryGetBctResponse { bct_response } => safe_put_dword(buffer, 0, bct_response.into()), + Self::BatteryGetBtmResponse { btm_response } => safe_put_dword(buffer, 0, btm_response.into()), + Self::BatterySetBmsResponse { status } => safe_put_dword(buffer, 0, status), + Self::BatterySetBmaResponse { status } => safe_put_dword(buffer, 0, status), + Self::BatteryGetStaResponse { sta } => safe_put_dword(buffer, 0, sta.bits()), } } - fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to deserialize responses on the EC side", - )) + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match BatteryCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + BatteryCmd::GetBix => Self::BatteryGetBixResponse { + bix: BixFixedStrings::from_bytes(buffer)?, + }, + BatteryCmd::GetBst => { + let bst = BstReturn { + battery_state: BatteryState::from_bits(safe_get_dword(buffer, 0)?) + .ok_or(MessageSerializationError::BufferTooSmall)?, + battery_present_rate: safe_get_dword(buffer, 4)?, + battery_remaining_capacity: safe_get_dword(buffer, 8)?, + battery_present_voltage: safe_get_dword(buffer, 12)?, + }; + Self::BatteryGetBstResponse { bst } + } + BatteryCmd::GetPsr => Self::BatteryGetPsrResponse { + psr: PsrReturn { + power_source: pwr_src_try_from_u32(safe_get_dword(buffer, 0)?)?, + }, + }, + BatteryCmd::GetPif => Self::BatteryGetPifResponse { + pif: PifFixedStrings::from_bytes(buffer)?, + }, + BatteryCmd::GetBps => Self::BatteryGetBpsResponse { + bps: Bps { + revision: safe_get_dword(buffer, 0)?, + instantaneous_peak_power_level: safe_get_dword(buffer, 4)?, + instantaneous_peak_power_period: safe_get_dword(buffer, 8)?, + sustainable_peak_power_level: safe_get_dword(buffer, 12)?, + sustainable_peak_power_period: safe_get_dword(buffer, 16)?, + }, + }, + BatteryCmd::SetBtp => Self::BatterySetBtpResponse {}, + BatteryCmd::SetBpt => Self::BatterySetBptResponse {}, + BatteryCmd::GetBpc => Self::BatteryGetBpcResponse { + bpc: Bpc { + revision: safe_get_dword(buffer, 0)?, + power_threshold_support: PowerThresholdSupport::from_bits(safe_get_dword(buffer, 4)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BpcThresholdSupport"))?, + max_instantaneous_peak_power_threshold: safe_get_dword(buffer, 8)?, + max_sustainable_peak_power_threshold: safe_get_dword(buffer, 12)?, + }, + }, + BatteryCmd::SetBmc => Self::BatterySetBmcResponse {}, + BatteryCmd::GetBmd => Self::BatteryGetBmdResponse { + bmd: Bmd { + status_flags: BmdStatusFlags::from_bits(safe_get_dword(buffer, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BmdStatusFlags"))?, + capability_flags: BmdCapabilityFlags::from_bits(safe_get_dword(buffer, 4)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BmdCapabilityFlags"))?, + recalibrate_count: safe_get_dword(buffer, 8)?, + quick_recalibrate_time: safe_get_dword(buffer, 12)?, + slow_recalibrate_time: safe_get_dword(buffer, 16)?, + }, + }, + BatteryCmd::GetBct => Self::BatteryGetBctResponse { + bct_response: safe_get_dword(buffer, 0)?.into(), + }, + BatteryCmd::GetBtm => Self::BatteryGetBtmResponse { + btm_response: safe_get_dword(buffer, 0)?.into(), + }, + BatteryCmd::SetBms => Self::BatterySetBmsResponse { + status: safe_get_dword(buffer, 0)?, + }, + BatteryCmd::SetBma => Self::BatterySetBmaResponse { + status: safe_get_dword(buffer, 0)?, + }, + BatteryCmd::GetSta => Self::BatteryGetStaResponse { + sta: StaReturn::from_bits(safe_get_dword(buffer, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid STA flags"))?, + }, + }, + ) } fn discriminant(&self) -> u16 { @@ -719,3 +646,32 @@ fn safe_get_dword(buffer: &[u8], index: usize) -> Result(buffer: &[u8], index: usize) -> Result<[u8; N], MessageSerializationError> { + buffer + .get(index..index + N) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall) +} + +fn safe_put_u8(buffer: &mut [u8], index: usize, val: u8) -> Result { + *buffer.get_mut(index).ok_or(MessageSerializationError::BufferTooSmall)? = val; + Ok(1) +} + +fn safe_put_dword(buffer: &mut [u8], index: usize, val: u32) -> Result { + buffer + .get_mut(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&val.to_le_bytes()); + Ok(4) +} + +fn safe_put_bytes(buffer: &mut [u8], index: usize, bytes: &[u8]) -> Result { + buffer + .get_mut(index..index + bytes.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(bytes); + Ok(bytes.len()) +} diff --git a/debug-service-messages/src/lib.rs b/debug-service-messages/src/lib.rs index c8b1262ee..a4dafc673 100644 --- a/debug-service-messages/src/lib.rs +++ b/debug-service-messages/src/lib.rs @@ -38,10 +38,11 @@ pub enum DebugRequest { impl SerializableMessage for DebugRequest { fn serialize(self, _buffer: &mut [u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to serialize requests on the EC side", - )) + match self { + Self::DebugGetMsgsRequest => Ok(0), + } } + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { Ok( match DebugCmd::try_from(discriminant) @@ -76,10 +77,21 @@ impl SerializableMessage for DebugResponse { } } } - fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to serialize requests on the EC side", - )) + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match DebugCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + DebugCmd::GetMsgs => Self::DebugGetMsgsResponse { + debug_buf: buffer + .get(0..STD_DEBUG_BUF_SIZE) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?, + }, + }, + ) } fn discriminant(&self) -> u16 { diff --git a/thermal-service-messages/src/lib.rs b/thermal-service-messages/src/lib.rs index d0325a2fe..c2b371ab9 100644 --- a/thermal-service-messages/src/lib.rs +++ b/thermal-service-messages/src/lib.rs @@ -95,10 +95,45 @@ pub enum ThermalRequest { // TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? impl SerializableMessage for ThermalRequest { - fn serialize(self, _buffer: &mut [u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to serialize requests on the EC side", - )) + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::ThermalGetTmpRequest { instance_id } => safe_put_u8(buffer, 0, instance_id), + Self::ThermalSetThrsRequest { + instance_id, + timeout, + low, + high, + } => Ok(safe_put_u8(buffer, 0, instance_id)? + + safe_put_dword(buffer, 1, timeout)? + + safe_put_dword(buffer, 5, low)? + + safe_put_dword(buffer, 9, high)?), + Self::ThermalGetThrsRequest { instance_id } => safe_put_u8(buffer, 0, instance_id), + Self::ThermalSetScpRequest { + instance_id, + policy_id, + acoustic_lim, + power_lim, + } => Ok(safe_put_u8(buffer, 0, instance_id)? + + safe_put_dword(buffer, 1, policy_id)? + + safe_put_dword(buffer, 5, acoustic_lim)? + + safe_put_dword(buffer, 9, power_lim)?), + Self::ThermalGetVarRequest { + instance_id, + len, + var_uuid, + } => Ok(safe_put_u8(buffer, 0, instance_id)? + + safe_put_u16(buffer, 1, len)? + + safe_put_uuid(buffer, 3, var_uuid)?), + Self::ThermalSetVarRequest { + instance_id, + len, + var_uuid, + set_var, + } => Ok(safe_put_u8(buffer, 0, instance_id)? + + safe_put_u16(buffer, 1, len)? + + safe_put_uuid(buffer, 3, var_uuid)? + + safe_put_dword(buffer, 19, set_var)?), + } } fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { @@ -167,46 +202,36 @@ pub enum ThermalResponse { impl SerializableMessage for ThermalResponse { fn serialize(self, buffer: &mut [u8]) -> Result { match self { - Self::ThermalGetTmpResponse { temperature } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(temperature)); - - Ok(4) - } - Self::ThermalGetThrsResponse { timeout, low, high } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(timeout)); - buffer - .get_mut(4..8) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(low)); - buffer - .get_mut(8..12) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(high)); - - Ok(12) - } - - Self::ThermalGetVarResponse { val } => { - buffer - .get_mut(..4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&u32::to_le_bytes(val)); - Ok(4) - } + Self::ThermalGetTmpResponse { temperature } => safe_put_dword(buffer, 0, temperature), + Self::ThermalGetThrsResponse { timeout, low, high } => Ok(safe_put_dword(buffer, 0, timeout)? + + safe_put_dword(buffer, 4, low)? + + safe_put_dword(buffer, 8, high)?), + Self::ThermalGetVarResponse { val } => safe_put_dword(buffer, 0, val), Self::ThermalSetVarResponse | Self::ThermalSetScpResponse | Self::ThermalSetThrsResponse => Ok(0), } } - fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to deserialize responses on the EC side", - )) + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match ThermalCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + ThermalCmd::GetTmp => Self::ThermalGetTmpResponse { + temperature: safe_get_dword(buffer, 0)?, + }, + ThermalCmd::SetThrs => Self::ThermalSetThrsResponse, + ThermalCmd::GetThrs => Self::ThermalGetThrsResponse { + timeout: safe_get_dword(buffer, 0)?, + low: safe_get_dword(buffer, 4)?, + high: safe_get_dword(buffer, 8)?, + }, + ThermalCmd::SetScp => Self::ThermalSetScpResponse, + ThermalCmd::GetVar => Self::ThermalGetVarResponse { + val: safe_get_dword(buffer, 0)?, + }, + ThermalCmd::SetVar => Self::ThermalSetVarResponse, + }, + ) } fn discriminant(&self) -> u16 { @@ -230,10 +255,9 @@ impl SerializableMessage for ThermalError { } } - fn deserialize(_discriminant: u16, _buffer: &[u8]) -> Result { - Err(MessageSerializationError::Other( - "unimplemented - don't need to deserialize responses on the EC side", - )) + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + ThermalError::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant)) } fn discriminant(&self) -> u16 { @@ -275,3 +299,32 @@ fn safe_get_uuid(buffer: &[u8], index: usize) -> Result Result { + *buffer.get_mut(index).ok_or(MessageSerializationError::BufferTooSmall)? = val; + Ok(1) +} + +fn safe_put_u16(buffer: &mut [u8], index: usize, val: u16) -> Result { + buffer + .get_mut(index..index + 2) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&val.to_le_bytes()); + Ok(2) +} + +fn safe_put_dword(buffer: &mut [u8], index: usize, val: u32) -> Result { + buffer + .get_mut(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&val.to_le_bytes()); + Ok(4) +} + +fn safe_put_uuid(buffer: &mut [u8], index: usize, uuid: uuid::Bytes) -> Result { + buffer + .get_mut(index..index + 16) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&uuid); + Ok(16) +} From 0ada106a172a9ed077de1d2cbd21d7f00a010811 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Thu, 22 Jan 2026 08:31:12 -0800 Subject: [PATCH 07/79] [v0.2.0 Refactor] [Type-C] Remove static_cell dependency and improve context handling (#668) - Removed `static_cell` dependency from Cargo.lock and Cargo.toml in examples and type-c-service. - Updated Type-C service methods to accept a reference to `intrusive_list::IntrusiveList` for better context management. - Modified various service methods to pass the controllers list where necessary, ensuring proper context usage. - Commented out unused code related to power policy channels and service initialization. - Adjusted the task closure to work with the updated service structure. - Enhanced error handling and logging throughout the service methods. --- embedded-service/src/lib.rs | 1 - embedded-service/src/type_c/controller.rs | 451 +++++++++------ embedded-service/src/type_c/external.rs | 542 ++++++++++--------- examples/rt685s-evk/Cargo.lock | 1 - examples/rt685s-evk/src/bin/type_c.rs | 78 ++- examples/rt685s-evk/src/bin/type_c_cfu.rs | 60 +- examples/std/Cargo.lock | 1 - examples/std/src/bin/type_c/basic.rs | 31 +- examples/std/src/bin/type_c/external.rs | 166 ++++-- examples/std/src/bin/type_c/service.rs | 150 +++-- examples/std/src/bin/type_c/ucsi.rs | 257 +++++---- examples/std/src/bin/type_c/unconstrained.rs | 202 ++++--- type-c-service/Cargo.toml | 2 +- type-c-service/src/lib.rs | 1 - type-c-service/src/service/controller.rs | 6 +- type-c-service/src/service/mod.rs | 44 +- type-c-service/src/service/pd.rs | 9 +- type-c-service/src/service/port.rs | 165 ++++-- type-c-service/src/service/power.rs | 12 +- type-c-service/src/service/ucsi.rs | 24 +- type-c-service/src/service/vdm.rs | 17 +- type-c-service/src/task.rs | 59 +- type-c-service/src/wrapper/backing.rs | 14 +- type-c-service/src/wrapper/mod.rs | 22 +- type-c-service/src/wrapper/vdm.rs | 4 +- 25 files changed, 1448 insertions(+), 871 deletions(-) diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index e43b63481..7facbbe0d 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -79,5 +79,4 @@ pub async fn init() { cfu::init(); keyboard::init(); power::policy::init(); - type_c::controller::init(); } diff --git a/embedded-service/src/type_c/controller.rs b/embedded-service/src/type_c/controller.rs index 4d3b55873..380987d09 100644 --- a/embedded-service/src/type_c/controller.rs +++ b/embedded-service/src/type_c/controller.rs @@ -1,6 +1,5 @@ //! PD controller related code use core::future::Future; -use core::sync::atomic::{AtomicBool, Ordering}; use embassy_sync::signal::Signal; use embassy_time::{Duration, with_timeout}; @@ -495,14 +494,19 @@ impl<'a> Device<'a> { } /// Notify that there are pending events on one or more ports - pub fn notify_ports(&self, pending: PortPending) { - CONTEXT.notify_ports(pending); + pub fn notify_ports(&self, ctx: &Context, pending: PortPending) { + ctx.notify_ports(pending); } /// Number of ports on this controller pub fn num_ports(&self) -> usize { self.num_ports } + + /// Slice of global ports on the Device + pub fn ports(&self) -> &'a [GlobalPortId] { + self.ports + } } /// Trait for types that contain a controller struct @@ -665,8 +669,7 @@ pub trait Controller { } /// Internal context for managing PD controllers -struct Context { - controllers: intrusive_list::IntrusiveList, +pub struct Context { port_events: Signal, /// Channel for receiving commands to the type-C service external_command: deferred::Channel>, @@ -674,10 +677,16 @@ struct Context { broadcaster: broadcaster::Immediate, } +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + impl Context { - const fn new() -> Self { + /// Create new Context + pub const fn new() -> Self { Self { - controllers: intrusive_list::IntrusiveList::new(), port_events: Signal::new(), external_command: deferred::Channel::new(), broadcaster: broadcaster::Immediate::new(), @@ -686,7 +695,7 @@ impl Context { /// Notify that there are pending events on one or more ports /// Each bit corresponds to a global port ID - fn notify_ports(&self, pending: PortPending) { + pub fn notify_ports(&self, pending: PortPending) { let raw_pending: u32 = pending.into(); trace!("Notify ports: {:#x}", raw_pending); // Early exit if no events @@ -701,69 +710,15 @@ impl Context { pending }); } -} - -static CONTEXT: Context = Context::new(); - -/// Initialize the PD controller context -pub fn init() {} - -/// Register a PD controller -pub fn register_controller(controller: &'static impl DeviceContainer) -> Result<(), intrusive_list::Error> { - CONTEXT.controllers.push(controller.get_pd_controller_device()) -} - -pub(super) async fn lookup_controller(controller_id: ControllerId) -> Result<&'static Device<'static>, PdError> { - CONTEXT - .controllers - .into_iter() - .filter_map(|node| node.data::()) - .find(|controller| controller.id == controller_id) - .ok_or(PdError::InvalidController) -} - -/// Get total number of ports on the system -pub(super) fn get_num_ports() -> usize { - CONTEXT - .controllers - .iter_only::() - .fold(0, |acc, controller| acc + controller.num_ports()) -} - -/// Register a message receiver for type-C messages -pub fn register_message_receiver( - receiver: &'static broadcaster::Receiver<'_, CommsMessage>, -) -> intrusive_list::Result<()> { - CONTEXT.broadcaster.register_receiver(receiver) -} - -/// 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); - -/// Type to provide access to the PD controller context for service implementations -pub struct ContextToken(()); - -impl ContextToken { - /// Create a new context token, returning None if this function has been called before - pub fn create() -> Option { - static INIT: AtomicBool = AtomicBool::new(false); - if INIT.load(Ordering::SeqCst) { - return None; - } - - INIT.store(true, Ordering::SeqCst); - Some(ContextToken(())) - } /// Send a command to the given controller with no timeout pub async fn send_controller_command_no_timeout( &self, + controllers: &intrusive_list::IntrusiveList, controller_id: ControllerId, command: InternalCommandData, ) -> Result, PdError> { - let node = CONTEXT - .controllers + let node = controllers .into_iter() .find(|node| { if let Some(controller) = node.data::() { @@ -791,12 +746,13 @@ impl ContextToken { /// Send a command to the given controller with a timeout pub async fn send_controller_command( &self, + controllers: &intrusive_list::IntrusiveList, controller_id: ControllerId, command: InternalCommandData, ) -> Result, PdError> { match with_timeout( DEFAULT_TIMEOUT, - self.send_controller_command_no_timeout(controller_id, command), + self.send_controller_command_no_timeout(controllers, controller_id, command), ) .await { @@ -806,15 +762,22 @@ impl ContextToken { } /// Reset the given controller - pub async fn reset_controller(&self, controller_id: ControllerId) -> Result<(), PdError> { - self.send_controller_command(controller_id, InternalCommandData::Reset) + pub async fn reset_controller( + &self, + controllers: &intrusive_list::IntrusiveList, + controller_id: ControllerId, + ) -> Result<(), PdError> { + self.send_controller_command(controllers, controller_id, InternalCommandData::Reset) .await .map(|_| ()) } - fn find_node_by_port(&self, port_id: GlobalPortId) -> Result<&IntrusiveNode, PdError> { - CONTEXT - .controllers + fn find_node_by_port( + &self, + controllers: &intrusive_list::IntrusiveList, + port_id: GlobalPortId, + ) -> Result<&IntrusiveNode, PdError> { + controllers .into_iter() .find(|node| { if let Some(controller) = node.data::() { @@ -829,10 +792,11 @@ impl ContextToken { /// Send a command to the given port pub async fn send_port_command_ucsi_no_timeout( &self, + controllers: &intrusive_list::IntrusiveList, port_id: GlobalPortId, command: lpm::CommandData, ) -> Result { - let node = self.find_node_by_port(port_id)?; + let node = self.find_node_by_port(controllers, port_id)?; match node .data::() @@ -851,12 +815,13 @@ impl ContextToken { /// Send a command to the given port with a timeout pub async fn send_port_command_ucsi( &self, + controllers: &intrusive_list::IntrusiveList, port_id: GlobalPortId, command: lpm::CommandData, ) -> Result { match with_timeout( DEFAULT_TIMEOUT, - self.send_port_command_ucsi_no_timeout(port_id, command), + self.send_port_command_ucsi_no_timeout(controllers, port_id, command), ) .await { @@ -868,10 +833,11 @@ impl ContextToken { /// Send a command to the given port with no timeout pub async fn send_port_command_no_timeout( &self, + controllers: &intrusive_list::IntrusiveList, port_id: GlobalPortId, command: PortCommandData, ) -> Result { - let node = self.find_node_by_port(port_id)?; + let node = self.find_node_by_port(controllers, port_id)?; match node .data::() @@ -893,10 +859,16 @@ impl ContextToken { /// Send a command to the given port with a timeout pub async fn send_port_command( &self, + controllers: &intrusive_list::IntrusiveList, port_id: GlobalPortId, command: PortCommandData, ) -> Result { - match with_timeout(DEFAULT_TIMEOUT, self.send_port_command_no_timeout(port_id, command)).await { + match with_timeout( + DEFAULT_TIMEOUT, + self.send_port_command_no_timeout(controllers, port_id, command), + ) + .await + { Ok(response) => response, Err(_) => Err(PdError::Timeout), } @@ -904,12 +876,19 @@ impl ContextToken { /// Get the current port events pub async fn get_unhandled_events(&self) -> PortPending { - CONTEXT.port_events.wait().await + self.port_events.wait().await } /// Get the unhandled events for the given port - pub async fn get_port_event(&self, port: GlobalPortId) -> Result { - match self.send_port_command(port, PortCommandData::ClearEvents).await? { + pub async fn get_port_event( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result { + match self + .send_port_command(controllers, port, PortCommandData::ClearEvents) + .await? + { PortResponseData::ClearEvents(event) => Ok(event), r => { error!("Invalid response: expected clear events, got {:?}", r); @@ -919,9 +898,14 @@ impl ContextToken { } /// Get the current port status - pub async fn get_port_status(&self, port: GlobalPortId, cached: Cached) -> Result { + pub async fn get_port_status( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + cached: Cached, + ) -> Result { match self - .send_port_command(port, PortCommandData::PortStatus(cached)) + .send_port_command(controllers, port, PortCommandData::PortStatus(cached)) .await? { PortResponseData::PortStatus(status) => Ok(status), @@ -933,8 +917,15 @@ impl ContextToken { } /// Get the oldest unhandled PD alert for the given port - pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { - match self.send_port_command(port, PortCommandData::GetPdAlert).await? { + pub async fn get_pd_alert( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result, PdError> { + match self + .send_port_command(controllers, port, PortCommandData::GetPdAlert) + .await? + { PortResponseData::PdAlert(alert) => Ok(alert), r => { error!("Invalid response: expected PD alert, got {:?}", r); @@ -944,9 +935,13 @@ impl ContextToken { } /// Get the retimer fw update status - pub async fn get_rt_fw_update_status(&self, port: GlobalPortId) -> Result { + pub async fn get_rt_fw_update_status( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result { match self - .send_port_command(port, PortCommandData::RetimerFwUpdateGetState) + .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateGetState) .await? { PortResponseData::RtFwUpdateStatus(status) => Ok(status), @@ -955,9 +950,13 @@ impl ContextToken { } /// Set the retimer fw update state - pub async fn set_rt_fw_update_state(&self, port: GlobalPortId) -> Result<(), PdError> { + pub async fn set_rt_fw_update_state( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::RetimerFwUpdateSetState) + .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateSetState) .await? { PortResponseData::Complete => Ok(()), @@ -966,9 +965,13 @@ impl ContextToken { } /// Clear the retimer fw update state - pub async fn clear_rt_fw_update_state(&self, port: GlobalPortId) -> Result<(), PdError> { + pub async fn clear_rt_fw_update_state( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::RetimerFwUpdateClearState) + .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateClearState) .await? { PortResponseData::Complete => Ok(()), @@ -977,9 +980,13 @@ impl ContextToken { } /// Set the retimer compliance - pub async fn set_rt_compliance(&self, port: GlobalPortId) -> Result<(), PdError> { + pub async fn set_rt_compliance( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetRetimerCompliance) + .send_port_command(controllers, port, PortCommandData::SetRetimerCompliance) .await? { PortResponseData::Complete => Ok(()), @@ -988,9 +995,13 @@ impl ContextToken { } /// Reconfigure the retimer for the given port. - pub async fn reconfigure_retimer(&self, port: GlobalPortId) -> Result<(), PdError> { + pub async fn reconfigure_retimer( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::ReconfigureRetimer) + .send_port_command(controllers, port, PortCommandData::ReconfigureRetimer) .await? { PortResponseData::Complete => Ok(()), @@ -1001,9 +1012,14 @@ impl ContextToken { /// 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> { + pub async fn set_max_sink_voltage( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + max_voltage_mv: Option, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetMaxSinkVoltage(max_voltage_mv)) + .send_port_command(controllers, port, PortCommandData::SetMaxSinkVoltage(max_voltage_mv)) .await? { PortResponseData::Complete => Ok(()), @@ -1012,9 +1028,13 @@ impl ContextToken { } /// Clear the dead battery flag for the given port. - pub async fn clear_dead_battery_flag(&self, port: GlobalPortId) -> Result<(), PdError> { + pub async fn clear_dead_battery_flag( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::ClearDeadBatteryFlag) + .send_port_command(controllers, port, PortCommandData::ClearDeadBatteryFlag) .await? { PortResponseData::Complete => Ok(()), @@ -1025,10 +1045,11 @@ impl ContextToken { /// Get current controller status pub async fn get_controller_status( &self, + controllers: &intrusive_list::IntrusiveList, controller_id: ControllerId, ) -> Result, PdError> { match self - .send_controller_command(controller_id, InternalCommandData::Status) + .send_controller_command(controllers, controller_id, InternalCommandData::Status) .await? { InternalResponseData::Status(status) => Ok(status), @@ -1040,9 +1061,14 @@ impl ContextToken { } /// Set unconstrained power for the given port - pub async fn set_unconstrained_power(&self, port: GlobalPortId, unconstrained: bool) -> Result<(), PdError> { + pub async fn set_unconstrained_power( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + unconstrained: bool, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetUnconstrainedPower(unconstrained)) + .send_port_command(controllers, port, PortCommandData::SetUnconstrainedPower(unconstrained)) .await? { PortResponseData::Complete => Ok(()), @@ -1051,9 +1077,13 @@ impl ContextToken { } /// Sync controller state - pub async fn sync_controller_state(&self, controller_id: ControllerId) -> Result<(), PdError> { + pub async fn sync_controller_state( + &self, + controllers: &intrusive_list::IntrusiveList, + controller_id: ControllerId, + ) -> Result<(), PdError> { match self - .send_controller_command(controller_id, InternalCommandData::SyncState) + .send_controller_command(controllers, controller_id, InternalCommandData::SyncState) .await? { InternalResponseData::Complete => Ok(()), @@ -1068,22 +1098,24 @@ impl ContextToken { pub async fn wait_external_command( &self, ) -> deferred::Request<'_, GlobalRawMutex, external::Command, external::Response<'static>> { - CONTEXT.external_command.receive().await - } - - /// Notify that there are pending events on one or more ports - pub fn notify_ports(&self, pending: PortPending) { - CONTEXT.notify_ports(pending); + self.external_command.receive().await } /// Get the number of ports on the system - pub fn get_num_ports(&self) -> usize { - get_num_ports() + pub fn get_num_ports(&self, controllers: &intrusive_list::IntrusiveList) -> usize { + get_num_ports(controllers) } /// Get the other vdm for the given port - pub async fn get_other_vdm(&self, port: GlobalPortId) -> Result { - match self.send_port_command(port, PortCommandData::GetOtherVdm).await? { + pub async fn get_other_vdm( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result { + match self + .send_port_command(controllers, port, PortCommandData::GetOtherVdm) + .await? + { PortResponseData::OtherVdm(vdm) => Ok(vdm), r => { error!("Invalid response: expected other VDM, got {:?}", r); @@ -1093,8 +1125,15 @@ impl ContextToken { } /// 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? { + pub async fn get_attn_vdm( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result { + match self + .send_port_command(controllers, port, PortCommandData::GetAttnVdm) + .await? + { PortResponseData::AttnVdm(vdm) => Ok(vdm), r => { error!("Invalid response: expected attention VDM, got {:?}", r); @@ -1104,17 +1143,30 @@ impl ContextToken { } /// 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? { + pub async fn send_vdm( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + tx_vdm: SendVdm, + ) -> Result<(), PdError> { + match self + .send_port_command(controllers, port, PortCommandData::SendVdm(tx_vdm)) + .await? + { PortResponseData::Complete => Ok(()), _ => Err(PdError::InvalidResponse), } } /// Set USB control configuration for the given port - pub async fn set_usb_control(&self, port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { + pub async fn set_usb_control( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + config: UsbControlConfig, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetUsbControl(config)) + .send_port_command(controllers, port, PortCommandData::SetUsbControl(config)) .await? { PortResponseData::Complete => Ok(()), @@ -1123,8 +1175,15 @@ impl ContextToken { } /// 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? { + pub async fn get_dp_status( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result { + match self + .send_port_command(controllers, port, PortCommandData::GetDpStatus) + .await? + { PortResponseData::DpStatus(status) => Ok(status), r => { error!("Invalid response: expected DP status, got {:?}", r); @@ -1134,9 +1193,14 @@ impl ContextToken { } /// Set DisplayPort configuration for the given port - pub async fn set_dp_config(&self, port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { + pub async fn set_dp_config( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + config: DpConfig, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetDpConfig(config)) + .send_port_command(controllers, port, PortCommandData::SetDpConfig(config)) .await? { PortResponseData::Complete => Ok(()), @@ -1145,17 +1209,29 @@ impl ContextToken { } /// 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? { + pub async fn execute_drst( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result<(), PdError> { + match self + .send_port_command(controllers, port, PortCommandData::ExecuteDrst) + .await? + { PortResponseData::Complete => Ok(()), _ => Err(PdError::InvalidResponse), } } /// Set Thunderbolt configuration for the given port - pub async fn set_tbt_config(&self, port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { + pub async fn set_tbt_config( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + config: TbtConfig, + ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetTbtConfig(config)) + .send_port_command(controllers, port, PortCommandData::SetTbtConfig(config)) .await? { PortResponseData::Complete => Ok(()), @@ -1166,11 +1242,12 @@ impl ContextToken { /// Set PD state-machine configuration for the given port pub async fn set_pd_state_machine_config( &self, + controllers: &intrusive_list::IntrusiveList, port: GlobalPortId, config: PdStateMachineConfig, ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetPdStateMachineConfig(config)) + .send_port_command(controllers, port, PortCommandData::SetPdStateMachineConfig(config)) .await? { PortResponseData::Complete => Ok(()), @@ -1181,11 +1258,12 @@ impl ContextToken { /// Set Type-C state-machine configuration for the given port pub async fn set_type_c_state_machine_config( &self, + controllers: &intrusive_list::IntrusiveList, port: GlobalPortId, state: TypeCStateMachineState, ) -> Result<(), PdError> { match self - .send_port_command(port, PortCommandData::SetTypeCStateMachineConfig(state)) + .send_port_command(controllers, port, PortCommandData::SetTypeCStateMachineConfig(state)) .await? { PortResponseData::Complete => Ok(()), @@ -1196,10 +1274,15 @@ impl ContextToken { /// Execute the given UCSI command pub async fn execute_ucsi_command( &self, + controllers: &intrusive_list::IntrusiveList, command: lpm::GlobalCommand, ) -> Result, PdError> { match self - .send_port_command(command.port(), PortCommandData::ExecuteUcsiCommand(command.operation())) + .send_port_command( + controllers, + command.port(), + PortCommandData::ExecuteUcsiCommand(command.operation()), + ) .await? { PortResponseData::UcsiResponse(response) => response, @@ -1207,50 +1290,90 @@ impl ContextToken { } } - /// Broadcast a type-C message to all subscribers - pub async fn broadcast_message(&self, message: CommsMessage) { - CONTEXT.broadcaster.broadcast(message).await; - } -} - -/// Execute an external port command -pub(super) async fn execute_external_port_command( - command: external::Command, -) -> Result { - match CONTEXT.external_command.execute(command).await { - external::Response::Port(response) => response, - r => { - error!("Invalid response: expected external port, got {:?}", r); - Err(PdError::InvalidResponse) + /// Execute an external port command + pub(super) async fn execute_external_port_command( + &self, + command: external::Command, + ) -> Result { + match self.external_command.execute(command).await { + external::Response::Port(response) => response, + r => { + error!("Invalid response: expected external port, got {:?}", r); + Err(PdError::InvalidResponse) + } } } -} -/// Execute an external controller command -pub(super) async fn execute_external_controller_command( - command: external::Command, -) -> Result, PdError> { - match CONTEXT.external_command.execute(command).await { - external::Response::Controller(response) => response, - r => { - error!("Invalid response: expected external controller, got {:?}", r); - Err(PdError::InvalidResponse) + /// Execute an external UCSI command + pub(super) async fn execute_external_ucsi_command(&self, command: ucsi::GlobalCommand) -> external::UcsiResponse { + match self.external_command.execute(external::Command::Ucsi(command)).await { + external::Response::Ucsi(response) => response, + r => { + error!("Invalid response: expected external UCSI, got {:?}", r); + external::UcsiResponse { + // Always notify OPM of an error + notify_opm: true, + cci: ucsi::cci::GlobalCci::new_error(), + data: Err(PdError::InvalidResponse), + } + } } } -} -/// Execute an external UCSI command -pub(super) async fn execute_external_ucsi_command(command: ucsi::GlobalCommand) -> external::UcsiResponse { - match CONTEXT.external_command.execute(external::Command::Ucsi(command)).await { - external::Response::Ucsi(response) => response, - r => { - error!("Invalid response: expected external UCSI, got {:?}", r); - external::UcsiResponse { - // Always notify OPM of an error - notify_opm: true, - cci: ucsi::cci::GlobalCci::new_error(), - data: Err(PdError::InvalidResponse), + /// Execute an external controller command + pub(super) async fn execute_external_controller_command( + &self, + command: external::Command, + ) -> Result, PdError> { + match self.external_command.execute(command).await { + external::Response::Controller(response) => response, + r => { + error!("Invalid response: expected external controller, got {:?}", r); + Err(PdError::InvalidResponse) } } } + + /// Register a message receiver for type-C messages + pub async fn register_message_receiver( + &self, + receiver: &'static broadcaster::Receiver<'_, CommsMessage>, + ) -> intrusive_list::Result<()> { + self.broadcaster.register_receiver(receiver) + } + + /// Broadcast a type-C message to all subscribers + pub async fn broadcast_message(&self, message: CommsMessage) { + self.broadcaster.broadcast(message).await; + } +} + +/// Default command timeout +/// set to high value since this is intended to prevent an unresponsive device from blocking the service implementation +const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); + +/// Register a PD controller +pub fn register_controller( + controllers: &intrusive_list::IntrusiveList, + controller: &'static impl DeviceContainer, +) -> Result<(), intrusive_list::Error> { + controllers.push(controller.get_pd_controller_device()) +} + +pub(super) fn lookup_controller( + controllers: &intrusive_list::IntrusiveList, + controller_id: ControllerId, +) -> Result<&'static Device<'static>, PdError> { + controllers + .into_iter() + .filter_map(|node| node.data::()) + .find(|controller| controller.id == controller_id) + .ok_or(PdError::InvalidController) +} + +/// Get total number of ports on the system +pub(super) fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { + controllers + .iter_only::() + .fold(0, |acc, controller| acc + controller.num_ports()) } diff --git a/embedded-service/src/type_c/external.rs b/embedded-service/src/type_c/external.rs index 6bdbccc6e..7216efd2b 100644 --- a/embedded-service/src/type_c/external.rs +++ b/embedded-service/src/type_c/external.rs @@ -1,19 +1,17 @@ //! Message definitions for external type-C commands use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError, ucsi}; -use crate::type_c::{ - Cached, - controller::{ - PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig, execute_external_ucsi_command, +use crate::{ + intrusive_list, + type_c::{ + Cached, + controller::{Context, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, }, }; use super::{ ControllerId, - controller::{ - ControllerStatus, DpConfig, DpStatus, PortStatus, RetimerFwUpdateState, SendVdm, - execute_external_controller_command, execute_external_port_command, lookup_controller, - }, + controller::{ControllerStatus, DpConfig, DpStatus, PortStatus, RetimerFwUpdateState, SendVdm, lookup_controller}, }; /// Data for controller-specific commands @@ -168,289 +166,331 @@ pub enum Response<'a> { Ucsi(UcsiResponse), } -/// Get the status of the given port. -/// -/// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. -pub async fn get_port_status(port: GlobalPortId, cached: Cached) -> Result { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::PortStatus(cached), - })) - .await? - { - PortResponseData::PortStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), +impl Context { + /// Get the status of the given port. + /// + /// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. + pub async fn get_port_status_external(&self, port: GlobalPortId, cached: Cached) -> Result { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::PortStatus(cached), + })) + .await? + { + PortResponseData::PortStatus(status) => Ok(status), + _ => Err(PdError::InvalidResponse), + } } -} -/// Get the status of the given port by its controller and local port ID. -/// -/// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. -pub async fn get_controller_port_status( - controller: ControllerId, - port: LocalPortId, - cached: Cached, -) -> Result { - let global_port = controller_port_to_global_id(controller, port).await?; - get_port_status(global_port, cached).await -} + /// Get the status of the given port by its controller and local port ID. + /// + /// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. + pub async fn get_controller_port_status_external( + &self, + controllers: &intrusive_list::IntrusiveList, + controller: ControllerId, + port: LocalPortId, + cached: Cached, + ) -> Result { + let global_port = controller_port_to_global_id(controllers, controller, port)?; + self.get_port_status_external(global_port, cached).await + } -/// Reset the given controller. -pub async fn reset_controller(controller_id: ControllerId) -> Result<(), PdError> { - match execute_external_controller_command(Command::Controller(ControllerCommand { - id: controller_id, - data: ControllerCommandData::Reset, - })) - .await? - { - ControllerResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Reset the given controller. + pub async fn reset_controller_external(&self, controller_id: ControllerId) -> Result<(), PdError> { + match self + .execute_external_controller_command(Command::Controller(ControllerCommand { + id: controller_id, + data: ControllerCommandData::Reset, + })) + .await? + { + ControllerResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Get the status of the given controller -#[allow(unreachable_patterns)] -pub async fn get_controller_status(id: ControllerId) -> Result, PdError> { - match execute_external_controller_command(Command::Controller(ControllerCommand { - id, - data: ControllerCommandData::ControllerStatus, - })) - .await? - { - ControllerResponseData::ControllerStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), + /// Get the status of the given controller + #[allow(unreachable_patterns)] + pub async fn get_controller_status_external(&self, id: ControllerId) -> Result, PdError> { + match self + .execute_external_controller_command(Command::Controller(ControllerCommand { + id, + data: ControllerCommandData::ControllerStatus, + })) + .await? + { + ControllerResponseData::ControllerStatus(status) => Ok(status), + _ => Err(PdError::InvalidResponse), + } } -} -/// Get the number of ports on the given controller -pub async fn get_controller_num_ports(controller_id: ControllerId) -> Result { - Ok(lookup_controller(controller_id).await?.num_ports()) -} + /// Get the retimer fw update status of the given port + pub async fn port_get_rt_fw_update_status_external( + &self, + port: GlobalPortId, + ) -> Result { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::RetimerFwUpdateGetState, + })) + .await? + { + PortResponseData::RetimerFwUpdateGetState(status) => Ok(status), + _ => Err(PdError::InvalidResponse), + } + } -/// Convert a (controller ID, local port ID) to a global port ID -pub async fn controller_port_to_global_id( - controller_id: ControllerId, - port_id: LocalPortId, -) -> Result { - lookup_controller(controller_id).await?.lookup_global_port(port_id) -} + /// Set the retimer fw update state of the given port + pub async fn port_set_rt_fw_update_state_external(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::RetimerFwUpdateSetState, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } -/// Get the retimer fw update status of the given port -pub async fn port_get_rt_fw_update_status(port: GlobalPortId) -> Result { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateGetState, - })) - .await? - { - PortResponseData::RetimerFwUpdateGetState(status) => Ok(status), - _ => Err(PdError::InvalidResponse), + /// Clear the retimer fw update state of the given port + pub async fn port_clear_rt_fw_update_state_external(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::RetimerFwUpdateClearState, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Set the retimer fw update state of the given port -pub async fn port_set_rt_fw_update_state(port: GlobalPortId) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateSetState, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Set the retimer comliance state of the given port + pub async fn port_set_rt_compliance_external(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetRetimerCompliance, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Clear the retimer fw update state of the given port -pub async fn port_clear_rt_fw_update_state(port: GlobalPortId) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateClearState, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Trigger a sync of the controller state + pub async fn sync_controller_state_external(&self, id: ControllerId) -> Result<(), PdError> { + match self + .execute_external_controller_command(Command::Controller(ControllerCommand { + id, + data: ControllerCommandData::SyncState, + })) + .await? + { + ControllerResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Set the retimer comliance state of the given port -pub async fn port_set_rt_compliance(port: GlobalPortId) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetRetimerCompliance, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Set the maximum voltage for the given port to a specific value. + /// + /// See [`PortCommandData::SetMaxSinkVoltage::max_voltage_mv`] for details on the `max_voltage_mv` parameter. + pub async fn set_max_sink_voltage_external( + &self, + port: GlobalPortId, + max_voltage_mv: Option, + ) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetMaxSinkVoltage { max_voltage_mv }, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Trigger a sync of the controller state -pub async fn sync_controller_state(id: ControllerId) -> Result<(), PdError> { - match execute_external_controller_command(Command::Controller(ControllerCommand { - id, - data: ControllerCommandData::SyncState, - })) - .await? - { - ControllerResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Clear the dead battery flag for the given port. + pub async fn clear_dead_battery_flag_external(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::ClearDeadBatteryFlag, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Get number of ports on the system -pub fn get_num_ports() -> usize { - super::controller::get_num_ports() -} + /// Reconfigure the retimer for the given port. + pub async fn reconfigure_retimer_external(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::ReconfigureRetimer, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } -/// Set the maximum voltage for the given port to a specific value. -/// -/// See [`PortCommandData::SetMaxSinkVoltage::max_voltage_mv`] for details on the `max_voltage_mv` parameter. -pub async fn set_max_sink_voltage(port: GlobalPortId, max_voltage_mv: Option) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetMaxSinkVoltage { max_voltage_mv }, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Execute a UCSI command + pub async fn execute_ucsi_command_external(&self, command: ucsi::GlobalCommand) -> UcsiResponse { + self.execute_external_ucsi_command(command).await } -} -/// Clear the dead battery flag for the given port. -pub async fn clear_dead_battery_flag(port: GlobalPortId) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ClearDeadBatteryFlag, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Send vdm to the given port + pub async fn send_vdm_external(&self, port: GlobalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SendVdm(tx_vdm), + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Reconfigure the retimer for the given port. -pub async fn reconfigure_retimer(port: GlobalPortId) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ReconfigureRetimer, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Set USB control configuration + pub async fn set_usb_control_external(&self, port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetUsbControl(config), + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Execute a UCSI command -pub async fn execute_ucsi_command(command: ucsi::GlobalCommand) -> UcsiResponse { - execute_external_ucsi_command(command).await -} + /// Get DisplayPort status for the given port + pub async fn get_dp_status_external(&self, port: GlobalPortId) -> Result { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::GetDpStatus, + })) + .await? + { + PortResponseData::GetDpStatus(status) => Ok(status), + _ => Err(PdError::InvalidResponse), + } + } -/// Send vdm to the given port -pub async fn send_vdm(port: GlobalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SendVdm(tx_vdm), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Set DisplayPort configuration for the given port + pub async fn set_dp_config_external(&self, port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetDpConfig(config), + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Set USB control configuration -pub async fn set_usb_control(port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetUsbControl(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Execute DisplayPort reset for the given port + pub async fn execute_drst_external(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::ExecuteDrst, + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Get DisplayPort status for the given port -pub async fn get_dp_status(port: GlobalPortId) -> Result { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::GetDpStatus, - })) - .await? - { - PortResponseData::GetDpStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), + /// Set Thunderbolt configuration for the given port + pub async fn set_tbt_config_external(&self, port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetTbtConfig(config), + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Set DisplayPort configuration for the given port -pub async fn set_dp_config(port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetDpConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Set PD state-machine configuration for the given port + pub async fn set_pd_state_machine_config_external( + &self, + port: GlobalPortId, + config: PdStateMachineConfig, + ) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetPdStateMachineConfig(config), + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } -} -/// Execute DisplayPort reset for the given port -pub async fn execute_drst(port: GlobalPortId) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ExecuteDrst, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), + /// Set Type-C state-machine configuration for the given port + pub async fn set_type_c_state_machine_config_external( + &self, + port: GlobalPortId, + state: TypeCStateMachineState, + ) -> Result<(), PdError> { + match self + .execute_external_port_command(Command::Port(PortCommand { + port, + data: PortCommandData::SetTypeCStateMachineConfig(state), + })) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } } } -/// Set Thunderbolt configuration for the given port -pub async fn set_tbt_config(port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetTbtConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } +/// Get the number of ports on the given controller +pub fn get_controller_num_ports( + controllers: &intrusive_list::IntrusiveList, + controller_id: ControllerId, +) -> Result { + Ok(lookup_controller(controllers, controller_id)?.num_ports()) } -/// Set PD state-machine configuration for the given port -pub async fn set_pd_state_machine_config(port: GlobalPortId, config: PdStateMachineConfig) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetPdStateMachineConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } +/// Convert a (controller ID, local port ID) to a global port ID +pub fn controller_port_to_global_id( + controllers: &intrusive_list::IntrusiveList, + controller_id: ControllerId, + port_id: LocalPortId, +) -> Result { + lookup_controller(controllers, controller_id)?.lookup_global_port(port_id) } -/// Set Type-C state-machine configuration for the given port -pub async fn set_type_c_state_machine_config(port: GlobalPortId, state: TypeCStateMachineState) -> Result<(), PdError> { - match execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetTypeCStateMachineConfig(state), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } +/// Get number of ports on the system +pub fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { + super::controller::get_num_ports(controllers) } diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index d9e539e44..52cc130ee 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1591,7 +1591,6 @@ dependencies = [ "embedded-services", "embedded-usb-pd", "heapless", - "static_cell", "tps6699x", ] diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index a59b66975..3d46d274d 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -9,21 +9,24 @@ use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::mutex::Mutex; +use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; -use embedded_services::GlobalRawMutex; -use embedded_services::power::policy::DeviceId as PowerId; -use embedded_services::type_c::{self, Cached, ControllerId}; +use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; +use embedded_services::type_c::{Cached, ControllerId}; +use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; +use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; extern crate rt685s_evk_example; +const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); @@ -64,12 +67,6 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st tps6699x::task::interrupt_task(&mut int_in, &mut [&mut interrupt]).await; } -#[embassy_executor::task] -async fn type_c_service_task() -> ! { - type_c_service::task(Default::default()).await; - unreachable!() -} - #[embassy_executor::task] async fn power_policy_service_task() { power_policy_service::task::task(Default::default()) @@ -77,6 +74,36 @@ async fn power_policy_service_task() { .expect("Failed to start power policy service task"); } +#[embassy_executor::task] +async fn service_task( + controller_context: &'static embedded_services::type_c::controller::Context, + controllers: &'static IntrusiveList, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], +) { + info!("Starting type-c task"); + + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + let service = Service::create( + type_c_service::service::config::Config::default(), + controller_context, + controllers, + power_policy_publisher, + power_policy_subscriber, + ); + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(service); + + type_c_service::task::task(service, wrappers).await; +} + #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_imxrt::init(Default::default()); @@ -84,13 +111,13 @@ async fn main(spawner: Spawner) { info!("Embedded service init"); embedded_services::init().await; - type_c::controller::init(); - info!("Spawining power policy task"); spawner.must_spawn(power_policy_service_task()); - info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task()); + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); let int_in = Input::new(p.PIO1_7, Pull::Up, Inverter::Disabled); static BUS: StaticCell>> = StaticCell::new(); @@ -127,6 +154,7 @@ async fn main(spawner: Spawner) { static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( + controller_context, CONTROLLER0_ID, 0, // CFU component ID [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], @@ -147,21 +175,35 @@ async fn main(spawner: Spawner) { let wrapper = WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); - wrapper.register().await.unwrap(); + info!("Spawining type-c service task"); + spawner.must_spawn(service_task(controller_context, controllers, [wrapper])); + spawner.must_spawn(pd_controller_task(wrapper)); // Sync our internal state with the hardware - type_c::external::sync_controller_state(CONTROLLER0_ID).await.unwrap(); + controller_context + .sync_controller_state_external(CONTROLLER0_ID) + .await + .unwrap(); embassy_time::Timer::after_secs(10).await; - let status = type_c::external::get_controller_status(CONTROLLER0_ID).await.unwrap(); + let status = controller_context + .get_controller_status_external(CONTROLLER0_ID) + .await + .unwrap(); info!("Controller status: {:?}", status); - let status = type_c::external::get_port_status(PORT0_ID, Cached(true)).await.unwrap(); + let status = controller_context + .get_port_status_external(PORT0_ID, Cached(true)) + .await + .unwrap(); info!("Port status: {:?}", status); - let status = type_c::external::get_port_status(PORT1_ID, Cached(true)).await.unwrap(); + let status = controller_context + .get_port_status_external(PORT1_ID, Cached(true)) + .await + .unwrap(); info!("Port status: {:?}", status); } diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 4e992915b..87cfed3a4 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -9,20 +9,23 @@ use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::mutex::Mutex; +use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::cfu::component::InternalResponseData; use embedded_services::cfu::component::RequestData; -use embedded_services::power::policy::DeviceId as PowerId; -use embedded_services::type_c::{self, ControllerId}; -use embedded_services::{GlobalRawMutex, cfu}; +use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; +use embedded_services::type_c::ControllerId; +use embedded_services::type_c::controller::Context; +use embedded_services::{GlobalRawMutex, IntrusiveList, cfu}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; +use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; @@ -48,6 +51,7 @@ type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Vali type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -149,12 +153,6 @@ async fn fw_update_task() { info!("Got version: {:#x}", version); } -#[embassy_executor::task] -async fn type_c_service_task() -> ! { - type_c_service::task(Default::default()).await; - unreachable!() -} - #[embassy_executor::task] async fn power_policy_service_task() { power_policy_service::task::task(Default::default()) @@ -162,6 +160,37 @@ async fn power_policy_service_task() { .expect("Failed to start power policy service task"); } +#[embassy_executor::task] +async fn service_task( + controller_context: &'static Context, + controllers: &'static IntrusiveList, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], +) -> ! { + info!("Starting type-c task"); + + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + let service = Service::create( + type_c_service::service::config::Config::default(), + controller_context, + controllers, + power_policy_publisher, + power_policy_subscriber, + ); + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(service); + + type_c_service::task::task(service, wrappers).await; + unreachable!() +} + #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_imxrt::init(Default::default()); @@ -169,13 +198,13 @@ async fn main(spawner: Spawner) { info!("Embedded service init"); embedded_services::init().await; - type_c::controller::init(); - info!("Spawining power policy task"); spawner.must_spawn(power_policy_service_task()); - info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task()); + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); let int_in = Input::new(p.PIO1_7, Pull::Up, Inverter::Disabled); static BUS: StaticCell>> = StaticCell::new(); @@ -211,6 +240,7 @@ async fn main(spawner: Spawner) { static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( + controller_context, CONTROLLER0_ID, CONTROLLER0_CFU_ID, [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], @@ -231,7 +261,9 @@ async fn main(spawner: Spawner) { let wrapper = WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); - wrapper.register().await.unwrap(); + info!("Spawning type-c service task"); + spawner.must_spawn(service_task(controller_context, controllers, [wrapper])); + spawner.must_spawn(pd_controller_task(wrapper)); spawner.must_spawn(fw_update_task()); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 38bbfdb89..d714101dc 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1787,7 +1787,6 @@ dependencies = [ "embedded-usb-pd", "heapless", "log", - "static_cell", "tps6699x", ] diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index bc0714fec..1f8441fe5 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,8 +1,8 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; -use embedded_services::power; use embedded_services::type_c::{Cached, ControllerId, controller}; +use embedded_services::{IntrusiveList, power}; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; @@ -124,13 +124,13 @@ mod test_controller { } #[embassy_executor::task] -async fn controller_task() { +async fn controller_task(controller_list: &'static IntrusiveList) { static CONTROLLER: OnceLock = OnceLock::new(); static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, POWER0_ID, &PORTS)); - controller::register_controller(controller).unwrap(); + controller::register_controller(controller_list, controller).unwrap(); loop { controller.process().await; @@ -141,24 +141,35 @@ async fn controller_task() { async fn task(spawner: Spawner) { embedded_services::init().await; - controller::init(); + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); info!("Starting controller task"); - spawner.must_spawn(controller_task()); + spawner.must_spawn(controller_task(controller_list)); // Wait for controller to be registered Timer::after_secs(1).await; - let context = controller::ContextToken::create().unwrap(); + let context = controller::Context::new(); - context.reset_controller(CONTROLLER0_ID).await.unwrap(); + context.reset_controller(controller_list, CONTROLLER0_ID).await.unwrap(); - let status = context.get_controller_status(CONTROLLER0_ID).await.unwrap(); + let status = context + .get_controller_status(controller_list, CONTROLLER0_ID) + .await + .unwrap(); info!("Controller 0 status: {status:#?}"); - let status = context.get_port_status(PORT0_ID, Cached(true)).await.unwrap(); + let status = context + .get_port_status(controller_list, PORT0_ID, Cached(true)) + .await + .unwrap(); info!("Port 0 status: {status:#?}"); - let status = context.get_port_status(PORT1_ID, Cached(true)).await.unwrap(); + let status = context + .get_port_status(controller_list, PORT1_ID, Cached(true)) + .await + .unwrap(); info!("Port 1 status: {status:#?}"); } diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 2ecc8050e..36a27b588 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -1,55 +1,26 @@ //! Low-level example of external messaging with a simple type-C service use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; +use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embedded_services::{ - GlobalRawMutex, power, - type_c::{Cached, ControllerId, external}, + GlobalRawMutex, IntrusiveList, power, + type_c::{Cached, ControllerId, controller::Context}, }; use embedded_usb_pd::GlobalPortId; use log::*; use static_cell::StaticCell; -use std_examples::type_c::mock_controller; +use std_examples::type_c::mock_controller::{self, Wrapper}; +use type_c_service::service::{Service, config::Config}; use type_c_service::wrapper::backing::Storage; +const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); #[embassy_executor::task] -async fn controller_task() { - static STATE: StaticCell = StaticCell::new(); - let state = STATE.init(mock_controller::ControllerState::new()); - - static STORAGE: StaticCell> = StaticCell::new(); - let backing_storage = STORAGE.init(Storage::new( - CONTROLLER0_ID, - 0, // CFU component ID (unused) - [(PORT0_ID, POWER0_ID)], - )); - static REFERENCED: StaticCell> = - StaticCell::new(); - let referenced = REFERENCED.init( - backing_storage - .create_referenced() - .expect("Failed to create referenced storage"), - ); - - static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - - static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = WRAPPER.init( - mock_controller::Wrapper::try_new( - controller, - Default::default(), - referenced, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ); - - wrapper.register().await.unwrap(); +async fn controller_task(wrapper: &'static Wrapper<'static>) { loop { if let Err(e) = wrapper.process_next_event().await { error!("Error processing wrapper: {e:#?}"); @@ -58,59 +29,150 @@ async fn controller_task() { } #[embassy_executor::task] -async fn task(_spawner: Spawner) { +async fn task(_spawner: Spawner, controller_context: &'static Context) { info!("Starting main task"); embedded_services::init().await; // Allow the controller to initialize and register itself Timer::after_secs(1).await; info!("Getting controller status"); - let controller_status = external::get_controller_status(ControllerId(0)).await.unwrap(); + let controller_status = controller_context + .get_controller_status_external(ControllerId(0)) + .await + .unwrap(); info!("Controller status: {controller_status:?}"); info!("Getting port status"); - let port_status = external::get_port_status(GlobalPortId(0), Cached(true)).await.unwrap(); + let port_status = controller_context + .get_port_status_external(GlobalPortId(0), Cached(true)) + .await + .unwrap(); info!("Port status: {port_status:?}"); info!("Getting retimer fw update status"); - let rt_fw_update_status = external::port_get_rt_fw_update_status(GlobalPortId(0)).await.unwrap(); + let rt_fw_update_status = controller_context + .port_get_rt_fw_update_status_external(GlobalPortId(0)) + .await + .unwrap(); info!("Get retimer fw update status: {rt_fw_update_status:?}"); info!("Setting retimer fw update state"); - external::port_set_rt_fw_update_state(GlobalPortId(0)).await.unwrap(); + controller_context + .port_set_rt_fw_update_state_external(GlobalPortId(0)) + .await + .unwrap(); info!("Clearing retimer fw update state"); - external::port_clear_rt_fw_update_state(GlobalPortId(0)).await.unwrap(); + controller_context + .port_clear_rt_fw_update_state_external(GlobalPortId(0)) + .await + .unwrap(); info!("Setting retimer compliance"); - external::port_set_rt_compliance(GlobalPortId(0)).await.unwrap(); + controller_context + .port_set_rt_compliance_external(GlobalPortId(0)) + .await + .unwrap(); info!("Setting max sink voltage"); - external::set_max_sink_voltage(GlobalPortId(0), Some(5000)) + controller_context + .set_max_sink_voltage_external(GlobalPortId(0), Some(5000)) .await .unwrap(); info!("Clearing dead battery flag"); - external::clear_dead_battery_flag(GlobalPortId(0)).await.unwrap(); + controller_context + .clear_dead_battery_flag_external(GlobalPortId(0)) + .await + .unwrap(); info!("Reconfiguring retimer"); - external::reconfigure_retimer(GlobalPortId(0)).await.unwrap(); + controller_context + .reconfigure_retimer_external(GlobalPortId(0)) + .await + .unwrap(); } #[embassy_executor::task] -async fn type_c_service_task() -> ! { - type_c_service::task(Default::default()).await; - unreachable!() +async fn service_task( + controller_context: &'static Context, + controllers: &'static IntrusiveList, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], +) { + info!("Starting type-c task"); + + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell> = + StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + let service = Service::create( + Config::default(), + controller_context, + controllers, + power_policy_publisher, + power_policy_subscriber, + ); + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(service); + + type_c_service::task::task(service, wrappers).await; +} + +fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper<'static> { + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(mock_controller::ControllerState::new()); + + static STORAGE: StaticCell> = StaticCell::new(); + let backing_storage = STORAGE.init(Storage::new( + controller_context, + CONTROLLER0_ID, + 0, // CFU component ID (unused) + [(PORT0_ID, POWER0_ID)], + )); + static REFERENCED: StaticCell> = + StaticCell::new(); + let referenced = REFERENCED.init( + backing_storage + .create_referenced() + .expect("Failed to create referenced storage"), + ); + + static CONTROLLER: StaticCell> = StaticCell::new(); + let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); + + static WRAPPER: StaticCell = StaticCell::new(); + WRAPPER.init( + mock_controller::Wrapper::try_new( + controller, + Default::default(), + referenced, + crate::mock_controller::Validator, + ) + .expect("Failed to create wrapper"), + ) } fn main() { env_logger::builder().filter_level(log::LevelFilter::Trace).init(); + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + let wrapper = create_wrapper(context); + static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(type_c_service_task()); - spawner.must_spawn(task(spawner)); - spawner.must_spawn(controller_task()); + spawner.must_spawn(service_task(context, controller_list, [wrapper])); + spawner.must_spawn(task(spawner, context)); + spawner.must_spawn(controller_task(wrapper)); }); } diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 5e9a7a354..5b7d3373a 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,19 +1,25 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; +use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embedded_services::power::{self}; -use embedded_services::type_c::{ControllerId, controller}; -use embedded_services::{GlobalRawMutex, comms}; +use embedded_services::type_c::ControllerId; +use embedded_services::type_c::controller::Context; +use embedded_services::{GlobalRawMutex, IntrusiveList, comms}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; use static_cell::StaticCell; use std_examples::type_c::mock_controller; +use std_examples::type_c::mock_controller::Wrapper; +use type_c_service::service::Service; +use type_c_service::service::config::Config; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; use type_c_service::wrapper::message::*; +const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); @@ -54,36 +60,10 @@ mod debug { } #[embassy_executor::task] -async fn controller_task(state: &'static mock_controller::ControllerState) { - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - CONTROLLER0_ID, - 0, // CFU component ID (unused) - [(PORT0_ID, POWER0_ID)], - )); - static REFERENCED: StaticCell> = StaticCell::new(); - let referenced = REFERENCED.init( - storage - .create_referenced() - .expect("Failed to create referenced storage"), - ); - - static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - - static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = WRAPPER.init( - mock_controller::Wrapper::try_new( - controller, - Default::default(), - referenced, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ); - - wrapper.register().await.unwrap(); - +async fn controller_task( + wrapper: &'static Wrapper<'static>, + controller: &'static Mutex>, +) { controller.lock().await.custom_function(); loop { @@ -109,21 +89,21 @@ async fn controller_task(state: &'static mock_controller::ControllerState) { } #[embassy_executor::task] -async fn task(spawner: Spawner) { +async fn task( + spawner: Spawner, + wrapper: &'static Wrapper<'static>, + controller: &'static Mutex>, + state: &'static mock_controller::ControllerState, +) { embedded_services::init().await; - controller::init(); - // Register debug accessory listener static LISTENER: OnceLock = OnceLock::new(); let listener = LISTENER.get_or_init(debug::Listener::new); comms::register_endpoint(listener, &listener.tp).await.unwrap(); - static STATE: OnceLock = OnceLock::new(); - let state = STATE.get_or_init(mock_controller::ControllerState::new); - info!("Starting controller task"); - spawner.must_spawn(controller_task(state)); + spawner.must_spawn(controller_task(wrapper, controller)); // Wait for controller to be registered Timer::after_secs(1).await; @@ -148,12 +128,6 @@ async fn task(spawner: Spawner) { Timer::after_millis(DELAY_MS).await; } -#[embassy_executor::task] -async fn type_c_service_task() -> ! { - type_c_service::task(Default::default()).await; - unreachable!() -} - #[embassy_executor::task] async fn power_policy_service_task() { power_policy_service::task::task(Default::default()) @@ -161,14 +135,96 @@ async fn power_policy_service_task() { .expect("Failed to start power policy service task"); } +#[embassy_executor::task] +async fn service_task( + controller_context: &'static Context, + controllers: &'static IntrusiveList, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], +) { + info!("Starting type-c task"); + + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell> = + StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + let service = Service::create( + Config::default(), + controller_context, + controllers, + power_policy_publisher, + power_policy_subscriber, + ); + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(service); + + type_c_service::task::task(service, wrappers).await; +} + +fn create_wrapper( + context: &'static Context, +) -> ( + &'static mut Wrapper<'static>, + &'static Mutex>, + &'static mock_controller::ControllerState, +) { + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(mock_controller::ControllerState::new()); + + static STORAGE: StaticCell> = StaticCell::new(); + let storage = STORAGE.init(Storage::new( + context, + CONTROLLER0_ID, + 0, // CFU component ID (unused) + [(PORT0_ID, POWER0_ID)], + )); + static REFERENCED: StaticCell> = StaticCell::new(); + let referenced = REFERENCED.init( + storage + .create_referenced() + .expect("Failed to create referenced storage"), + ); + + static CONTROLLER: StaticCell> = StaticCell::new(); + let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); + + static WRAPPER: StaticCell = StaticCell::new(); + ( + WRAPPER.init( + mock_controller::Wrapper::try_new( + controller, + Default::default(), + referenced, + crate::mock_controller::Validator, + ) + .expect("Failed to create wrapper"), + ), + controller, + state, + ) +} + fn main() { env_logger::builder().filter_level(log::LevelFilter::Trace).init(); static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); + + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + let (wrapper, controller, state) = create_wrapper(controller_context); + executor.run(|spawner| { spawner.must_spawn(power_policy_service_task()); - spawner.must_spawn(type_c_service_task()); - spawner.must_spawn(task(spawner)); + spawner.must_spawn(service_task(controller_context, controller_list, [wrapper])); + spawner.must_spawn(task(spawner, wrapper, controller, state)); }); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 77f43ee97..bd8a617b5 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,9 +1,13 @@ +use crate::mock_controller::Wrapper; use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; +use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; +use embedded_services::IntrusiveList; use embedded_services::power::policy::{self, PowerCapability}; use embedded_services::type_c::ControllerId; -use embedded_services::type_c::external::{UcsiResponseResult, execute_ucsi_command}; +use embedded_services::type_c::controller::Context; +use embedded_services::type_c::external::UcsiResponseResult; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -13,9 +17,11 @@ use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; use static_cell::StaticCell; use std_examples::type_c::mock_controller; +use type_c_service::service::Service; use type_c_service::service::config::Config; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +const NUM_PD_CONTROLLERS: usize = 2; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -26,54 +32,15 @@ const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; #[embassy_executor::task] -async fn opm_task(spawner: Spawner) { - static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); - static REFERENCED0: StaticCell> = StaticCell::new(); - let referenced0 = REFERENCED0.init( - storage0 - .create_referenced() - .expect("Failed to create referenced storage"), - ); - - static STATE0: StaticCell = StaticCell::new(); - let state0 = STATE0.init(mock_controller::ControllerState::new()); - static CONTROLLER0: StaticCell> = StaticCell::new(); - let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); - static WRAPPER0: StaticCell = StaticCell::new(); - let wrapper0 = WRAPPER0.init( - mock_controller::Wrapper::try_new(controller0, Default::default(), referenced0, mock_controller::Validator) - .expect("Failed to create wrapper"), - ); - spawner.must_spawn(wrapper_task(wrapper0)); - - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); - static REFERENCED1: StaticCell> = StaticCell::new(); - let referenced1 = REFERENCED1.init( - storage1 - .create_referenced() - .expect("Failed to create referenced storage"), - ); - - static STATE1: StaticCell = StaticCell::new(); - let state1 = STATE1.init(mock_controller::ControllerState::new()); - static CONTROLLER1: StaticCell> = StaticCell::new(); - let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); - static WRAPPER1: StaticCell = StaticCell::new(); - let wrapper1 = WRAPPER1.init( - mock_controller::Wrapper::try_new(controller1, Default::default(), referenced1, mock_controller::Validator) - .expect("Failed to create wrapper"), - ); - spawner.must_spawn(wrapper_task(wrapper1)); - +async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, current_ma: 5000, }; info!("Resetting PPM..."); - let response: UcsiResponseResult = execute_ucsi_command(Command::PpmCommand(ppm::Command::PpmReset)) + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::PpmReset)) .await .into(); let response = response.unwrap(); @@ -87,13 +54,14 @@ async fn opm_task(spawner: Spawner) { let mut notifications = NotificationEnable::default(); notifications.set_cmd_complete(true); notifications.set_connect_change(true); - let response: UcsiResponseResult = execute_ucsi_command(Command::PpmCommand(ppm::Command::SetNotificationEnable( - ppm::set_notification_enable::Args { - notification_enable: notifications, - }, - ))) - .await - .into(); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::SetNotificationEnable( + ppm::set_notification_enable::Args { + notification_enable: notifications, + }, + ))) + .await + .into(); let response = response.unwrap(); if !response.cci.cmd_complete() || response.cci.error() { error!("Set Notification enable failed: {:?}", response.cci); @@ -102,8 +70,8 @@ async fn opm_task(spawner: Spawner) { } info!("Sending command complete ack..."); - let response: UcsiResponseResult = - execute_ucsi_command(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { ack: *Ack::default().set_command_complete(true), }))) .await @@ -116,19 +84,20 @@ async fn opm_task(spawner: Spawner) { } info!("Connecting sinks on both ports"); - state0.connect_sink(CAPABILITY, false).await; - state1.connect_sink(CAPABILITY, false).await; + state[0].connect_sink(CAPABILITY, false).await; + state[1].connect_sink(CAPABILITY, false).await; // Ensure connect flow has time to complete embassy_time::Timer::after_millis(1000).await; info!("Port 0: Get connector status..."); - let response: UcsiResponseResult = execute_ucsi_command(Command::LpmCommand(lpm::GlobalCommand::new( - GlobalPortId(0), - lpm::CommandData::GetConnectorStatus, - ))) - .await - .into(); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::LpmCommand(lpm::GlobalCommand::new( + GlobalPortId(0), + lpm::CommandData::GetConnectorStatus, + ))) + .await + .into(); let response = response.unwrap(); if !response.cci.cmd_complete() || response.cci.error() { error!("Get connector status failed: {:?}", response.cci); @@ -140,8 +109,8 @@ async fn opm_task(spawner: Spawner) { } info!("Sending command complete ack..."); - let response: UcsiResponseResult = - execute_ucsi_command(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { ack: *Ack::default().set_command_complete(true).set_connector_change(true), }))) .await @@ -157,12 +126,13 @@ async fn opm_task(spawner: Spawner) { } info!("Port 1: Get connector status..."); - let response: UcsiResponseResult = execute_ucsi_command(Command::LpmCommand(lpm::GlobalCommand::new( - GlobalPortId(1), - lpm::CommandData::GetConnectorStatus, - ))) - .await - .into(); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::LpmCommand(lpm::GlobalCommand::new( + GlobalPortId(1), + lpm::CommandData::GetConnectorStatus, + ))) + .await + .into(); let response = response.unwrap(); if !response.cci.cmd_complete() || response.cci.error() { error!("Get connector status failed: {:?}", response.cci); @@ -174,8 +144,8 @@ async fn opm_task(spawner: Spawner) { } info!("Sending command complete ack..."); - let response: UcsiResponseResult = - execute_ucsi_command(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { ack: *Ack::default().set_command_complete(true).set_connector_change(true), }))) .await @@ -193,8 +163,6 @@ async fn opm_task(spawner: Spawner) { #[embassy_executor::task(pool_size = 2)] async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { - wrapper.register().await.unwrap(); - loop { if let Err(e) = wrapper.process_next_event().await { error!("Error processing wrapper: {e:#?}"); @@ -202,36 +170,6 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { } } -#[embassy_executor::task] -async fn type_c_service_task() -> ! { - type_c_service::task(Config { - ucsi_capabilities: UcsiCapabilities { - num_connectors: 2, - bcd_usb_pd_spec: 0x0300, - bcd_type_c_spec: 0x0200, - bcd_battery_charging_spec: 0x0120, - ..Default::default() - }, - ucsi_port_capabilities: Some( - *lpm::get_connector_capability::ResponseData::default() - .set_operation_mode( - *OperationModeFlags::default() - .set_drp(true) - .set_usb2(true) - .set_usb3(true), - ) - .set_consumer(true) - .set_provider(true) - .set_swap_to_dfp(true) - .set_swap_to_snk(true) - .set_swap_to_src(true), - ), - ..Default::default() - }) - .await; - unreachable!() -} - #[embassy_executor::task] async fn power_policy_service_task() { power_policy_service::task::task(Default::default()) @@ -240,14 +178,121 @@ async fn power_policy_service_task() { } #[embassy_executor::task] -async fn task(spawner: Spawner) { +async fn service_task( + config: Config, + controller_context: &'static Context, + controllers: &'static IntrusiveList, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], +) -> ! { + info!("Starting type-c task"); + + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + let service = Service::create( + config, + controller_context, + controllers, + power_policy_publisher, + power_policy_subscriber, + ); + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(service); + + type_c_service::task::task(service, wrappers).await; + unreachable!() +} + +#[embassy_executor::task] +async fn type_c_service_task(spawner: Spawner) { info!("Starting main task"); embedded_services::init().await; + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + static STORAGE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); + static REFERENCED0: StaticCell> = StaticCell::new(); + let referenced0 = REFERENCED0.init( + storage0 + .create_referenced() + .expect("Failed to create referenced storage"), + ); + + static STATE0: StaticCell = StaticCell::new(); + let state0 = STATE0.init(mock_controller::ControllerState::new()); + static CONTROLLER0: StaticCell> = StaticCell::new(); + let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); + static WRAPPER0: StaticCell = StaticCell::new(); + let wrapper0 = WRAPPER0.init( + mock_controller::Wrapper::try_new(controller0, Default::default(), referenced0, mock_controller::Validator) + .expect("Failed to create wrapper"), + ); + + static STORAGE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); + static REFERENCED1: StaticCell> = StaticCell::new(); + let referenced1 = REFERENCED1.init( + storage1 + .create_referenced() + .expect("Failed to create referenced storage"), + ); + + static STATE1: StaticCell = StaticCell::new(); + let state1 = STATE1.init(mock_controller::ControllerState::new()); + static CONTROLLER1: StaticCell> = StaticCell::new(); + let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); + static WRAPPER1: StaticCell = StaticCell::new(); + let wrapper1 = WRAPPER1.init( + mock_controller::Wrapper::try_new(controller1, Default::default(), referenced1, mock_controller::Validator) + .expect("Failed to create wrapper"), + ); + spawner.must_spawn(power_policy_service_task()); - spawner.must_spawn(type_c_service_task()); - spawner.must_spawn(opm_task(spawner)); + spawner.must_spawn(service_task( + Config { + ucsi_capabilities: UcsiCapabilities { + num_connectors: 2, + bcd_usb_pd_spec: 0x0300, + bcd_type_c_spec: 0x0200, + bcd_battery_charging_spec: 0x0120, + ..Default::default() + }, + ucsi_port_capabilities: Some( + *lpm::get_connector_capability::ResponseData::default() + .set_operation_mode( + *OperationModeFlags::default() + .set_drp(true) + .set_usb2(true) + .set_usb3(true), + ) + .set_consumer(true) + .set_provider(true) + .set_swap_to_dfp(true) + .set_swap_to_snk(true) + .set_swap_to_src(true), + ), + ..Default::default() + }, + context, + controller_list, + [wrapper0, wrapper1], + )); + spawner.must_spawn(wrapper_task(wrapper0)); + spawner.must_spawn(wrapper_task(wrapper1)); + spawner.must_spawn(opm_task(context, [state0, state1])); } fn main() { @@ -256,6 +301,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(task(spawner)); + spawner.must_spawn(type_c_service_task(spawner)); }); } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 8e127962d..c9f489b88 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,16 +1,23 @@ -use embassy_executor::{Executor, Spawner}; +use crate::mock_controller::Wrapper; +use embassy_executor::Executor; use embassy_sync::mutex::Mutex; +use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::GlobalRawMutex; use embedded_services::power::policy::PowerCapability; use embedded_services::power::{self}; -use embedded_services::type_c::{ControllerId, controller}; +use embedded_services::type_c::ControllerId; +use embedded_services::type_c::controller::Context; +use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; use static_cell::StaticCell; use std_examples::type_c::mock_controller; +use type_c_service::service::Service; +use type_c_service::service::config::Config; use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +const NUM_PD_CONTROLLERS: usize = 3; + const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); @@ -30,8 +37,6 @@ const DELAY_MS: u64 = 1000; #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { - wrapper.register().await.unwrap(); - loop { if let Err(e) = wrapper.process_next_event().await { error!("Error processing wrapper: {e:#?}"); @@ -40,13 +45,110 @@ async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { } #[embassy_executor::task] -async fn task(spawner: Spawner) { +async fn task(state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { embedded_services::init().await; - controller::init(); + const CAPABILITY: PowerCapability = PowerCapability { + voltage_mv: 20000, + current_ma: 5000, + }; + + // Wait for controller to be registered + Timer::after_secs(1).await; + + info!("Connecting port 0, unconstrained"); + state[0].connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 1, constrained"); + state[1].connect_sink(CAPABILITY, false).await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 0"); + state[0].disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 1"); + state[1].disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 0, unconstrained"); + state[0].connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 1, unconstrained"); + state[1].connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 2, unconstrained"); + state[2].connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 0"); + state[0].disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 1"); + state[1].disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 2"); + state[2].disconnect().await; + Timer::after_millis(DELAY_MS).await; +} + +#[embassy_executor::task] +async fn power_policy_service_task() { + power_policy_service::task::task(Default::default()) + .await + .expect("Failed to start power policy service task"); +} + +#[embassy_executor::task] +async fn service_task( + controller_context: &'static Context, + controllers: &'static IntrusiveList, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], +) -> ! { + info!("Starting type-c task"); + + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell> = + StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + let service = Service::create( + Config::default(), + controller_context, + controllers, + power_policy_publisher, + power_policy_subscriber, + ); + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(service); + + type_c_service::task::task(service, wrappers).await; + unreachable!() +} + +fn main() { + env_logger::builder().filter_level(log::LevelFilter::Trace).init(); + + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + + static CONTEXT: StaticCell = StaticCell::new(); + let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new(CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); + let storage = STORAGE.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); static REFERENCED: StaticCell> = StaticCell::new(); let referenced = REFERENCED.init( storage @@ -70,7 +172,7 @@ async fn task(spawner: Spawner) { ); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); + let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); static REFERENCED1: StaticCell> = StaticCell::new(); let referenced1 = REFERENCED1.init( storage1 @@ -94,7 +196,7 @@ async fn task(spawner: Spawner) { ); static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new(CONTROLLER2_ID, CFU2_ID, [(PORT2_ID, POWER2_ID)])); + let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [(PORT2_ID, POWER2_ID)])); static REFERENCED2: StaticCell> = StaticCell::new(); let referenced2 = REFERENCED2.init( storage2 @@ -117,81 +219,13 @@ async fn task(spawner: Spawner) { .expect("Failed to create wrapper"), ); - info!("Starting controller tasks"); - spawner.must_spawn(controller_task(wrapper0)); - spawner.must_spawn(controller_task(wrapper1)); - spawner.must_spawn(controller_task(wrapper2)); - - const CAPABILITY: PowerCapability = PowerCapability { - voltage_mv: 20000, - current_ma: 5000, - }; - - // Wait for controller to be registered - Timer::after_secs(1).await; - - info!("Connecting port 0, unconstrained"); - state0.connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 1, constrained"); - state1.connect_sink(CAPABILITY, false).await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 0"); - state0.disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 1"); - state1.disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 0, unconstrained"); - state0.connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 1, unconstrained"); - state1.connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 2, unconstrained"); - state2.connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 0"); - state0.disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 1"); - state1.disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 2"); - state2.disconnect().await; - Timer::after_millis(DELAY_MS).await; -} - -#[embassy_executor::task] -async fn type_c_service_task() -> ! { - type_c_service::task(Default::default()).await; - unreachable!() -} - -#[embassy_executor::task] -async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) - .await - .expect("Failed to start power policy service task"); -} - -fn main() { - env_logger::builder().filter_level(log::LevelFilter::Trace).init(); - - static EXECUTOR: StaticCell = StaticCell::new(); - let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { spawner.must_spawn(power_policy_service_task()); - spawner.must_spawn(type_c_service_task()); - spawner.must_spawn(task(spawner)); + spawner.must_spawn(service_task(context, controller_list, [wrapper0, wrapper1, wrapper2])); + spawner.must_spawn(task([state0, state1, state2])); + info!("Starting controller tasks"); + spawner.must_spawn(controller_task(wrapper0)); + spawner.must_spawn(controller_task(wrapper1)); + spawner.must_spawn(controller_task(wrapper2)); }); } diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index f8686f297..d52f82917 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -25,7 +25,6 @@ embedded-services.workspace = true embedded-usb-pd.workspace = true heapless.workspace = true log = { workspace = true, optional = true } -static_cell = { workspace = true } tps6699x = { workspace = true, features = ["embassy"] } [dev-dependencies] @@ -35,6 +34,7 @@ critical-section = { workspace = true, features = ["std"] } embassy-time-driver = { workspace = true } embassy-futures.workspace = true tokio = { workspace = true, features = ["rt", "macros", "time"] } +static_cell.workspace = true [features] default = [] diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index a53d22e3c..b1d0a5f11 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -9,7 +9,6 @@ use core::future::Future; use embedded_services::type_c::event::{ PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged, }; -pub use task::task; /// Enum to contain all port event variants #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/type-c-service/src/service/controller.rs b/type-c-service/src/service/controller.rs index de1c22732..f4034e7cd 100644 --- a/type-c-service/src/service/controller.rs +++ b/type-c-service/src/service/controller.rs @@ -14,7 +14,7 @@ impl<'a> Service<'a> { &self, controller: ControllerId, ) -> external::Response<'static> { - let status = self.context.get_controller_status(controller).await; + let status = self.context.get_controller_status(self.controllers, controller).await; if let Err(e) = status { error!("Error getting controller status: {:#?}", e); } @@ -26,7 +26,7 @@ impl<'a> Service<'a> { &self, controller: ControllerId, ) -> external::Response<'static> { - let status = self.context.sync_controller_state(controller).await; + let status = self.context.sync_controller_state(self.controllers, controller).await; if let Err(e) = status { error!("Error getting controller sync state: {:#?}", e); } @@ -38,7 +38,7 @@ impl<'a> Service<'a> { &self, controller: ControllerId, ) -> external::Response<'static> { - let status = self.context.reset_controller(controller).await; + let status = self.context.reset_controller(self.controllers, controller).await; if let Err(e) = status { error!("Error resetting controller: {:#?}", e); } diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 753fc5ba6..4637f39e1 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -30,10 +30,6 @@ pub mod vdm; const MAX_SUPPORTED_PORTS: usize = 4; -/// Maximum number of power policy events to buffer -/// Arbitrary number, but power policy events in general shouldn't be too frequent -pub const MAX_POWER_POLICY_EVENTS: usize = 4; - /// Type-C service state #[derive(Default)] struct State { @@ -46,9 +42,14 @@ struct 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 token - context: type_c::controller::ContextToken, + /// Type-C context + context: &'a type_c::controller::Context, + /// Controller intrusive list + controllers: &'a intrusive_list::IntrusiveList, /// Current state state: Mutex, /// Config @@ -94,16 +95,19 @@ impl<'a> Service<'a> { /// Create a new service the given configuration pub fn create( config: config::Config, + context: &'a embedded_services::type_c::controller::Context, + controller_list: &'a intrusive_list::IntrusiveList, power_policy_publisher: DynImmediatePublisher<'a, power_policy::CommsMessage>, power_policy_subscriber: DynSubscriber<'a, power_policy::CommsMessage>, - ) -> Option { - Some(Self { - context: type_c::controller::ContextToken::create()?, + ) -> Self { + Self { + context, state: Mutex::new(State::default()), config, power_policy_event_publisher: power_policy_publisher.into(), power_policy_event_subscriber: Mutex::new(power_policy_subscriber), - }) + controllers: controller_list, + } } /// Get the cached port status @@ -162,8 +166,10 @@ impl<'a> Service<'a> { async fn process_external_command(&self, command: &external::Command) -> external::Response<'static> { match command { external::Command::Controller(command) => self.process_external_controller_command(command).await, - external::Command::Port(command) => self.process_external_port_command(command).await, - external::Command::Ucsi(command) => external::Response::Ucsi(self.process_ucsi_command(command).await), + external::Command::Port(command) => self.process_external_port_command(command, self.controllers).await, + external::Command::Ucsi(command) => { + external::Response::Ucsi(self.process_ucsi_command(self.controllers, command).await) + } } } @@ -179,7 +185,10 @@ impl<'a> Service<'a> { { Either3::First(mut stream) => { if let Some((port_id, event)) = stream - .next(|port_id| self.context.get_port_event(GlobalPortId(port_id as u8))) + .next(|port_id| { + self.context + .get_port_event(self.controllers, GlobalPortId(port_id as u8)) + }) .await? { let port_id = GlobalPortId(port_id as u8); @@ -187,7 +196,10 @@ impl<'a> Service<'a> { match event { PortEventVariant::StatusChanged(status_event) => { // Return a port status changed event - let status = self.context.get_port_status(port_id, Cached(true)).await?; + let status = self + .context + .get_port_status(self.controllers, port_id, Cached(true)) + .await?; return Ok(Event::PortStatusChanged(port_id, status_event, status)); } PortEventVariant::Notification(notification) => { @@ -243,4 +255,8 @@ impl<'a> Service<'a> { pub fn register_comms(&'static self) -> Result<(), intrusive_list::Error> { power_policy::policy::register_message_receiver(&self.power_policy_event_publisher) } + + pub(crate) fn controllers(&self) -> &'a intrusive_list::IntrusiveList { + self.controllers + } } diff --git a/type-c-service/src/service/pd.rs b/type-c-service/src/service/pd.rs index 21934fa76..5076953f4 100644 --- a/type-c-service/src/service/pd.rs +++ b/type-c-service/src/service/pd.rs @@ -1,5 +1,6 @@ //! Power Delivery (PD) related functionality. +use embedded_services::intrusive_list; use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; use super::Service; @@ -8,7 +9,11 @@ impl Service<'_> { /// Get the oldest unhandled PD alert for the given port. /// /// Returns [`None`] if no alerts are pending. - pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { - self.context.get_pd_alert(port).await + pub async fn get_pd_alert( + &self, + controllers: &intrusive_list::IntrusiveList, + port: GlobalPortId, + ) -> Result, PdError> { + self.context.get_pd_alert(controllers, port).await } } diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs index c44f79651..80517dc8f 100644 --- a/type-c-service/src/service/port.rs +++ b/type-c-service/src/service/port.rs @@ -30,40 +30,57 @@ impl<'a> Service<'a> { pub(super) async fn process_external_port_command( &self, command: &external::PortCommand, + controllers: &intrusive_list::IntrusiveList, ) -> external::Response<'static> { debug!("Processing external port command: {:#?}", command); match command.data { external::PortCommandData::PortStatus(cached) => { - self.process_external_port_status(command.port, cached).await + self.process_external_port_status(command.port, cached, controllers) + .await } external::PortCommandData::RetimerFwUpdateGetState => { - self.process_get_rt_fw_update_status(command.port).await + self.process_get_rt_fw_update_status(command.port, controllers).await } external::PortCommandData::RetimerFwUpdateSetState => { - self.process_set_rt_fw_update_state(command.port).await + self.process_set_rt_fw_update_state(command.port, controllers).await } external::PortCommandData::RetimerFwUpdateClearState => { - self.process_clear_rt_fw_update_state(command.port).await + self.process_clear_rt_fw_update_state(command.port, controllers).await + } + external::PortCommandData::SetRetimerCompliance => { + self.process_set_rt_compliance(command.port, controllers).await + } + external::PortCommandData::ReconfigureRetimer => { + self.process_reconfigure_retimer(command.port, controllers).await } - external::PortCommandData::SetRetimerCompliance => self.process_set_rt_compliance(command.port).await, - external::PortCommandData::ReconfigureRetimer => self.process_reconfigure_retimer(command.port).await, external::PortCommandData::SetMaxSinkVoltage { max_voltage_mv } => { - self.process_set_max_sink_voltage(command.port, max_voltage_mv).await + self.process_set_max_sink_voltage(command.port, max_voltage_mv, controllers) + .await + } + external::PortCommandData::ClearDeadBatteryFlag => { + self.process_clear_dead_battery_flag(command.port, controllers).await + } + external::PortCommandData::SendVdm(tx_vdm) => { + self.process_send_vdm(command.port, tx_vdm, controllers).await } - external::PortCommandData::ClearDeadBatteryFlag => self.process_clear_dead_battery_flag(command.port).await, - external::PortCommandData::SendVdm(tx_vdm) => self.process_send_vdm(command.port, tx_vdm).await, external::PortCommandData::SetUsbControl(config) => { - self.process_set_usb_control(command.port, config).await + self.process_set_usb_control(command.port, config, controllers).await + } + external::PortCommandData::GetDpStatus => self.process_get_dp_status(command.port, controllers).await, + external::PortCommandData::SetDpConfig(config) => { + self.process_set_dp_config(command.port, config, controllers).await + } + external::PortCommandData::ExecuteDrst => self.process_execute_drst(command.port, controllers).await, + external::PortCommandData::SetTbtConfig(config) => { + self.process_set_tbt_config(command.port, config, controllers).await } - external::PortCommandData::GetDpStatus => self.process_get_dp_status(command.port).await, - external::PortCommandData::SetDpConfig(config) => self.process_set_dp_config(command.port, config).await, - external::PortCommandData::ExecuteDrst => self.process_execute_drst(command.port).await, - external::PortCommandData::SetTbtConfig(config) => self.process_set_tbt_config(command.port, config).await, external::PortCommandData::SetPdStateMachineConfig(config) => { - self.process_set_pd_state_machine_config(command.port, config).await + self.process_set_pd_state_machine_config(command.port, config, controllers) + .await } external::PortCommandData::SetTypeCStateMachineConfig(state) => { - self.process_set_type_c_state_machine_config(command.port, state).await + self.process_set_type_c_state_machine_config(command.port, state, controllers) + .await } } } @@ -73,8 +90,9 @@ impl<'a> Service<'a> { &self, port_id: GlobalPortId, cached: Cached, + controllers: &intrusive_list::IntrusiveList, ) -> external::Response<'static> { - let status = self.context.get_port_status(port_id, cached).await; + let status = self.context.get_port_status(controllers, port_id, cached).await; if let Err(e) = status { error!("Error getting port status: {:#?}", e); } @@ -82,8 +100,12 @@ impl<'a> Service<'a> { } /// Process get retimer fw update status commands - pub(super) async fn process_get_rt_fw_update_status(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.get_rt_fw_update_status(port_id).await; + pub(super) async fn process_get_rt_fw_update_status( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.get_rt_fw_update_status(controllers, port_id).await; if let Err(e) = status { error!("Error getting retimer fw update status: {:#?}", e); } @@ -92,8 +114,12 @@ impl<'a> Service<'a> { } /// Process set retimer fw update state commands - pub(super) async fn process_set_rt_fw_update_state(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.set_rt_fw_update_state(port_id).await; + pub(super) async fn process_set_rt_fw_update_state( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.set_rt_fw_update_state(controllers, port_id).await; if let Err(e) = status { error!("Error setting retimer fw update state: {:#?}", e); } @@ -102,8 +128,12 @@ impl<'a> Service<'a> { } /// Process clear retimer fw update state commands - pub(super) async fn process_clear_rt_fw_update_state(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.clear_rt_fw_update_state(port_id).await; + pub(super) async fn process_clear_rt_fw_update_state( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.clear_rt_fw_update_state(controllers, port_id).await; if let Err(e) = status { error!("Error clear retimer fw update state: {:#?}", e); } @@ -112,8 +142,12 @@ impl<'a> Service<'a> { } /// Process set retimer compliance - pub(super) async fn process_set_rt_compliance(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.set_rt_compliance(port_id).await; + pub(super) async fn process_set_rt_compliance( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.set_rt_compliance(controllers, port_id).await; if let Err(e) = status { error!("Error set retimer compliance: {:#?}", e); } @@ -121,8 +155,12 @@ impl<'a> Service<'a> { external::Response::Port(status.map(|_| external::PortResponseData::Complete)) } - async fn process_reconfigure_retimer(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.reconfigure_retimer(port_id).await; + async fn process_reconfigure_retimer( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.reconfigure_retimer(controllers, port_id).await; if let Err(e) = status { error!("Error reconfiguring retimer: {:#?}", e); } @@ -134,8 +172,12 @@ impl<'a> Service<'a> { &self, port_id: GlobalPortId, max_voltage_mv: Option, + controllers: &intrusive_list::IntrusiveList, ) -> external::Response<'static> { - let status = self.context.set_max_sink_voltage(port_id, max_voltage_mv).await; + let status = self + .context + .set_max_sink_voltage(controllers, port_id, max_voltage_mv) + .await; if let Err(e) = status { error!("Error setting max voltage: {:#?}", e); } @@ -143,8 +185,12 @@ impl<'a> Service<'a> { external::Response::Port(status.map(|_| external::PortResponseData::Complete)) } - async fn process_clear_dead_battery_flag(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.clear_dead_battery_flag(port_id).await; + async fn process_clear_dead_battery_flag( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.clear_dead_battery_flag(controllers, port_id).await; if let Err(e) = status { error!("Error clearing dead battery flag: {:#?}", e); } @@ -153,8 +199,14 @@ impl<'a> Service<'a> { } /// Process send vdm commands - async fn process_send_vdm(&self, port_id: GlobalPortId, tx_vdm: SendVdm) -> external::Response<'static> { - let status = self.context.send_vdm(port_id, tx_vdm).await; + /// Process send vdm commands + async fn process_send_vdm( + &self, + port_id: GlobalPortId, + tx_vdm: SendVdm, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.send_vdm(controllers, port_id, tx_vdm).await; if let Err(e) = status { error!("Error sending VDM data: {:#?}", e); } @@ -167,8 +219,9 @@ impl<'a> Service<'a> { &self, port_id: GlobalPortId, config: UsbControlConfig, + controllers: &intrusive_list::IntrusiveList, ) -> external::Response<'static> { - let status = self.context.set_usb_control(port_id, config).await; + let status = self.context.set_usb_control(controllers, port_id, config).await; if let Err(e) = status { error!("Error setting USB control: {:#?}", e); } @@ -177,8 +230,12 @@ impl<'a> Service<'a> { } /// Process get DisplayPort status commands - async fn process_get_dp_status(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.get_dp_status(port_id).await; + async fn process_get_dp_status( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.get_dp_status(controllers, port_id).await; if let Err(e) = status { error!("Error getting DP status: {:#?}", e); } @@ -187,8 +244,13 @@ impl<'a> Service<'a> { } /// Process set DisplayPort config commands - async fn process_set_dp_config(&self, port_id: GlobalPortId, config: DpConfig) -> external::Response<'static> { - let status = self.context.set_dp_config(port_id, config).await; + async fn process_set_dp_config( + &self, + port_id: GlobalPortId, + config: DpConfig, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.set_dp_config(controllers, port_id, config).await; if let Err(e) = status { error!("Error setting DP config: {:#?}", e); } @@ -197,8 +259,12 @@ impl<'a> Service<'a> { } /// Process execute DisplayPort reset commands - async fn process_execute_drst(&self, port_id: GlobalPortId) -> external::Response<'static> { - let status = self.context.execute_drst(port_id).await; + async fn process_execute_drst( + &self, + port_id: GlobalPortId, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.execute_drst(controllers, port_id).await; if let Err(e) = status { error!("Error executing DP reset: {:#?}", e); } @@ -207,8 +273,13 @@ impl<'a> Service<'a> { } /// Process set Thunderbolt configuration command - async fn process_set_tbt_config(&self, port_id: GlobalPortId, config: TbtConfig) -> external::Response<'static> { - let status = self.context.set_tbt_config(port_id, config).await; + async fn process_set_tbt_config( + &self, + port_id: GlobalPortId, + config: TbtConfig, + controllers: &intrusive_list::IntrusiveList, + ) -> external::Response<'static> { + let status = self.context.set_tbt_config(controllers, port_id, config).await; if let Err(e) = status { error!("Error setting TBT config: {:#?}", e); } @@ -221,8 +292,12 @@ impl<'a> Service<'a> { &self, port_id: GlobalPortId, config: PdStateMachineConfig, + controllers: &intrusive_list::IntrusiveList, ) -> external::Response<'static> { - let status = self.context.set_pd_state_machine_config(port_id, config).await; + let status = self + .context + .set_pd_state_machine_config(controllers, port_id, config) + .await; if let Err(e) = status { error!("Error setting PD state-machine config: {:#?}", e); } @@ -235,8 +310,12 @@ impl<'a> Service<'a> { &self, port_id: GlobalPortId, state: TypeCStateMachineState, + controllers: &intrusive_list::IntrusiveList, ) -> external::Response<'static> { - let status = self.context.set_type_c_state_machine_config(port_id, state).await; + let status = self + .context + .set_type_c_state_machine_config(controllers, port_id, state) + .await; if let Err(e) = status { error!("Error setting Type-C state-machine config: {:#?}", e); } diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index c6bb628d2..64c77a8c8 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -32,9 +32,9 @@ impl<'a> Service<'a> { /// Set the unconstrained state for all ports pub(super) async fn set_unconstrained_all(&self, unconstrained: bool) -> Result<(), Error> { - for port_index in 0..self.context.get_num_ports() { + for port_index in 0..self.context.get_num_ports(self.controllers) { self.context - .set_unconstrained_power(GlobalPortId(port_index as u8), unconstrained) + .set_unconstrained_power(self.controllers, GlobalPortId(port_index as u8), unconstrained) .await?; } Ok(()) @@ -57,7 +57,7 @@ impl<'a> Service<'a> { 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 num_ports = self.context.get_num_ports(self.controllers); let unconstrained_port = state .port_status .iter() @@ -74,7 +74,11 @@ impl<'a> Service<'a> { ); for port_index in 0..num_ports { self.context - .set_unconstrained_power(GlobalPortId(port_index as u8), port_index != unconstrained_index) + .set_unconstrained_power( + self.controllers, + GlobalPortId(port_index as u8), + port_index != unconstrained_index, + ) .await?; } } else { diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index e11a16023..53bf886e4 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -44,10 +44,10 @@ impl<'a> Service<'a> { } /// PPM get capabilities implementation - fn process_get_capabilities(&self) -> ppm::ResponseData { + fn process_get_capabilities(&self, controllers: &intrusive_list::IntrusiveList) -> ppm::ResponseData { debug!("Get PPM capabilities: {:?}", self.config.ucsi_capabilities); let mut capabilities = self.config.ucsi_capabilities; - capabilities.num_connectors = external::get_num_ports() as u8; + capabilities.num_connectors = external::get_num_ports(controllers) as u8; ppm::ResponseData::GetCapability(capabilities) } @@ -55,13 +55,14 @@ impl<'a> Service<'a> { &self, state: &mut State, command: &ucsi::ppm::Command, + controllers: &intrusive_list::IntrusiveList, ) -> Result, PdError> { match command { ppm::Command::SetNotificationEnable(enable) => { self.process_set_notification_enable(state, enable.notification_enable); Ok(None) } - ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities())), + ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities(controllers))), _ => Ok(None), // Other commands are currently no-ops } } @@ -97,6 +98,7 @@ impl<'a> Service<'a> { &self, state: &mut super::State, command: &ucsi::lpm::GlobalCommand, + controllers: &intrusive_list::IntrusiveList, ) -> Result, PdError> { debug!("Processing LPM command: {:?}", command); match command.operation() { @@ -105,11 +107,11 @@ impl<'a> Service<'a> { if let Some(capabilities) = &self.config.ucsi_port_capabilities { Ok(Some(lpm::ResponseData::GetConnectorCapability(*capabilities))) } else { - self.context.execute_ucsi_command(*command).await + self.context.execute_ucsi_command(controllers, *command).await } } lpm::CommandData::GetConnectorStatus => { - let mut response = self.context.execute_ucsi_command(*command).await; + let mut response = self.context.execute_ucsi_command(controllers, *command).await; if let Ok(Some(lpm::ResponseData::GetConnectorStatus(lpm::get_connector_status::ResponseData { status_change: ref mut states_change, status: @@ -129,7 +131,7 @@ impl<'a> Service<'a> { response } - _ => self.context.execute_ucsi_command(*command).await, + _ => self.context.execute_ucsi_command(controllers, *command).await, } } @@ -168,7 +170,11 @@ impl<'a> Service<'a> { } /// Process an external UCSI command - pub(super) async fn process_ucsi_command(&self, command: &GlobalCommand) -> external::UcsiResponse { + pub(super) async fn process_ucsi_command( + &self, + controllers: &intrusive_list::IntrusiveList, + command: &GlobalCommand, + ) -> external::UcsiResponse { let state = &mut self.state.lock().await; let mut next_input = Some(PpmInput::Command(command)); let mut response: external::UcsiResponse = external::UcsiResponse { @@ -212,12 +218,12 @@ impl<'a> Service<'a> { match command { ucsi::GlobalCommand::PpmCommand(ppm_command) => { response.data = self - .process_ppm_command(&mut state.ucsi, ppm_command) + .process_ppm_command(&mut state.ucsi, ppm_command, controllers) .map(|inner| inner.map(ResponseData::Ppm)); } ucsi::GlobalCommand::LpmCommand(lpm_command) => { response.data = self - .process_lpm_command(state, lpm_command) + .process_lpm_command(state, lpm_command, controllers) .await .map(|inner| inner.map(ResponseData::Lpm)); } diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index eda35679f..43e97e5e2 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,5 +1,6 @@ //! VDM (Vendor Defined Messages) related functionality. +use embedded_services::intrusive_list; use embedded_services::type_c::controller::{AttnVdm, OtherVdm}; use embedded_usb_pd::{GlobalPortId, PdError}; @@ -7,12 +8,20 @@ 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 + pub async fn get_other_vdm( + &self, + controllers: &intrusive_list::IntrusiveList, + port_id: GlobalPortId, + ) -> Result { + self.context.get_other_vdm(controllers, 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 + pub async fn get_attn_vdm( + &self, + controllers: &intrusive_list::IntrusiveList, + port_id: GlobalPortId, + ) -> Result { + self.context.get_attn_vdm(controllers, port_id).await } } diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index 0d965e8e9..11664a1dc 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,48 +1,49 @@ use core::future::Future; -use embassy_sync::pubsub::PubSubChannel; -use embedded_services::{GlobalRawMutex, error, info, power}; -use static_cell::StaticCell; +use embedded_services::{error, info}; -use crate::service::config::Config; -use crate::service::{MAX_POWER_POLICY_EVENTS, Service}; +use crate::{service::Service, wrapper::ControllerWrapper}; /// Task to run the Type-C service, takes a closure to customize the event loop -pub async fn task_closure<'a, Fut: Future, F: Fn(&'a Service) -> Fut>(config: Config, f: F) { +pub async fn task_closure<'a, M, C, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( + service: &'static Service<'a>, + wrappers: [&'a ControllerWrapper<'a, M, C, V>; N], + f: F, +) where + M: embassy_sync::blocking_mutex::raw::RawMutex, + C: embedded_services::sync::Lockable, + V: crate::wrapper::FwOfferValidator, + ::Inner: embedded_services::type_c::controller::Controller, +{ info!("Starting type-c task"); - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - let Ok(power_policy_subscriber) = power_policy_channel.dyn_subscriber() else { - error!("Failed to create power policy subscriber"); - return; - }; - - let service = Service::create(config, power_policy_publisher, power_policy_subscriber); - let Some(service) = service else { - error!("Type-C service already initialized"); - return; - }; - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); - if service.register_comms().is_err() { error!("Failed to register type-c service endpoint"); return; } + for controller_wrapper in wrappers { + if controller_wrapper.register(service.controllers()).await.is_err() { + error!("Failed to register a controller"); + return; + } + } + loop { f(service).await; } } -pub async fn task(config: Config) { - task_closure(config, |service: &Service| async { +/// Task to run the Type-C service, running the default event loop +pub async fn task<'a, M, C, V, const N: usize>( + service: &'static Service<'a>, + wrappers: [&'a ControllerWrapper<'a, M, C, V>; N], +) where + M: embassy_sync::blocking_mutex::raw::RawMutex, + C: embedded_services::sync::Lockable, + V: crate::wrapper::FwOfferValidator, + ::Inner: embedded_services::type_c::controller::Controller, +{ + task_closure(service, wrappers, |service: &Service| async { if let Err(e) = service.process_next_event().await { error!("Type-C service processing error: {:#?}", e); } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index baf9cf5a2..711b5a02e 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -26,9 +26,10 @@ //! //! const NUM_PORTS: usize = 2; //! -//! fn init() { +//! fn init(context: &'static embedded_services::type_c::controller::Context) { //! static STORAGE: StaticCell> = StaticCell::new(); //! let storage = STORAGE.init(Storage::new( +//! context, //! ControllerId(0), //! 0x0, //! [(GlobalPortId(0), power::policy::DeviceId(0)), (GlobalPortId(1), power::policy::DeviceId(1))], @@ -158,6 +159,7 @@ pub trait DynPortState<'a> { /// Service registration objects pub struct Registration<'a> { + pub context: &'a embedded_services::type_c::controller::Context, pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, pub cfu_device: &'a embedded_services::cfu::component::CfuDevice, pub power_devices: &'a [embedded_services::power::policy::device::Device], @@ -173,8 +175,9 @@ impl<'a> Registration<'a> { const MAX_BUFFERED_PD_ALERTS: usize = 4; /// Base storage -pub struct Storage { +pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related + context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: embedded_services::cfu::component::CfuDevice, @@ -184,13 +187,15 @@ pub struct Storage { pd_alerts: [PubSubChannel; N], } -impl Storage { +impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( + context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, cfu_id: ComponentId, ports: [(GlobalPortId, power::policy::DeviceId); N], ) -> Self { Self { + context, controller_id, pd_ports: ports.map(|(port, _)| port), cfu_device: embedded_services::cfu::component::CfuDevice::new(cfu_id), @@ -210,7 +215,7 @@ impl Storage { /// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state /// completely separately. pub struct ReferencedStorage<'a, const N: usize, M: RawMutex> { - storage: &'a Storage, + storage: &'a Storage<'a, N, M>, state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, } @@ -235,6 +240,7 @@ impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { { self.state.try_borrow_mut().ok().map(|state| Backing { registration: Registration { + context: self.storage.context, pd_controller: &self.pd_controller, cfu_device: &self.storage.cfu_device, power_devices: &self.storage.power_devices, diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 38dcb170b..fb2853c0c 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -27,12 +27,12 @@ use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::GlobalRawMutex; use embedded_services::power::policy::device::StateKind; use embedded_services::power::policy::{self, action}; use embedded_services::sync::Lockable; use embedded_services::type_c::controller::{self, Controller, PortStatus}; use embedded_services::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; +use embedded_services::{GlobalRawMutex, intrusive_list}; use embedded_services::{debug, error, info, trace, warn}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; @@ -301,8 +301,10 @@ where let mut pending = PortPending::none(); pending .pend_port(global_port_id.0 as usize) - .map_err(|_| Error::Pd(PdError::InvalidPort))?; - self.registration.pd_controller.notify_ports(pending); + .map_err(|_| PdError::InvalidPort)?; + self.registration + .pd_controller + .notify_ports(self.registration.context, pending); trace!("P{}: Notified service for events: {:#?}", global_port_id.0, events); } @@ -337,8 +339,10 @@ where let mut pending = PortPending::none(); pending .pend_port(global_port_id.0 as usize) - .map_err(|_| Error::Pd(PdError::InvalidPort))?; - self.registration.pd_controller.notify_ports(pending); + .map_err(|_| PdError::InvalidPort)?; + self.registration + .pd_controller + .notify_ports(self.registration.context, pending); Ok(()) } @@ -603,7 +607,11 @@ where } /// Register all devices with their respective services - pub async fn register(&'static self) -> Result<(), Error<::BusError>> { + pub async fn register( + &'static self, + controllers: &intrusive_list::IntrusiveList, + ) -> Result<(), Error<::BusError>> { + // TODO: Unify these devices? for device in self.registration.power_devices { policy::register_device(device).map_err(|_| { error!( @@ -615,7 +623,7 @@ where })?; } - controller::register_controller(self.registration.pd_controller).map_err(|_| { + controller::register_controller(controllers, self.registration.pd_controller).map_err(|_| { error!( "Controller{}: Failed to register PD controller", self.registration.pd_controller.id().0 diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 0eb01fa5b..029b7f7d1 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -58,7 +58,9 @@ where pending .pend_port(global_port_id.0 as usize) .map_err(|_| PdError::InvalidPort)?; - self.registration.pd_controller.notify_ports(pending); + self.registration + .pd_controller + .notify_ports(self.registration.context, pending); Ok(()) } } From 829f5dab1affd681ff28f0214cad0cd20bd121bf Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Thu, 22 Jan 2026 10:21:36 -0800 Subject: [PATCH 08/79] Uprev cargo-vet CI job to use cargo-vet@0.10.2 (#681) The cargo-vet CI job is currently failing with 36 unvetted dependencies with a note `These audits may have been made with a more recent version of cargo-vet`. Updating to the latest version of cargo-vet gets rid of these unvetted dependencies as it seems the vetting was done with cargo-vet@0.10.2. Why this is not a breaking minor change is beyond me. ``` Vetting Failed! 42 unvetted dependencies: aho-corasick:1.1.3 missing ["safe-to-deploy"] anyhow:1.0.99 missing ["safe-to-deploy"] cc:1.2.33 missing ["safe-to-deploy"] encoding_rs:0.8.35 missing ["safe-to-deploy"] libc:0.2.175 missing ["safe-to-deploy"] loom:0.7.2 missing ["safe-to-deploy"] memchr:2.7.5 missing ["safe-to-deploy"] paste:1.0.15 missing ["safe-to-deploy"] proc-macro2:1.0.101 missing ["safe-to-deploy"] regex-automata:0.4.13 missing ["safe-to-deploy"] ryu:1.0.20 missing ["safe-to-deploy"] scoped-tls:1.0.1 missing ["safe-to-deploy"] serde_json:1.0.143 missing ["safe-to-deploy"] syn:1.0.109 missing ["safe-to-deploy"] syn:2.0.106 missing ["safe-to-deploy"] thiserror:1.0.69 missing ["safe-to-deploy"] thiserror:2.0.16 missing ["safe-to-deploy"] thiserror-impl:1.0.69 missing ["safe-to-deploy"] thiserror-impl:2.0.16 missing ["safe-to-deploy"] unicode-segmentation:1.12.0 missing ["safe-to-deploy"] unicode-width:0.1.14 missing ["safe-to-deploy"] windows:0.61.3 missing ["safe-to-deploy"] windows-collections:0.2.0 missing ["safe-to-deploy"] windows-core:0.61.2 missing ["safe-to-deploy"] windows-future:0.2.1 missing ["safe-to-deploy"] windows-implement:0.60.0 missing ["safe-to-deploy"] windows-interface:0.59.1 missing ["safe-to-deploy"] windows-numerics:0.2.0 missing ["safe-to-deploy"] windows-result:0.3.4 missing ["safe-to-deploy"] windows-strings:0.4.2 missing ["safe-to-deploy"] windows-sys:0.52.0 missing ["safe-to-deploy"] windows-sys:0.59.0 missing ["safe-to-run"] windows-targets:0.52.6 missing ["safe-to-deploy"] windows-threading:0.1.0 missing ["safe-to-deploy"] windows_aarch64_gnullvm:0.52.6 missing ["safe-to-deploy"] windows_aarch64_msvc:0.52.6 missing ["safe-to-deploy"] windows_i686_gnu:0.52.6 missing ["safe-to-deploy"] windows_i686_gnullvm:0.52.6 missing ["safe-to-deploy"] windows_i686_msvc:0.52.6 missing ["safe-to-deploy"] windows_x86_64_gnu:0.52.6 missing ["safe-to-deploy"] windows_x86_64_gnullvm:0.52.6 missing ["safe-to-deploy"] windows_x86_64_msvc:0.52.6 missing ["safe-to-deploy"] ``` --- .github/workflows/cargo-vet.yml | 4 ++-- supply-chain/imports.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cargo-vet.yml b/.github/workflows/cargo-vet.yml index 3253851ee..d96e4da1d 100644 --- a/.github/workflows/cargo-vet.yml +++ b/.github/workflows/cargo-vet.yml @@ -22,7 +22,7 @@ jobs: name: vet-dependencies runs-on: ubuntu-latest env: - CARGO_VET_VERSION: 0.10.1 + CARGO_VET_VERSION: 0.10.2 steps: - uses: actions/checkout@v4 with: @@ -34,7 +34,7 @@ jobs: - name: Add the tool cache directory to the search path run: echo "${{ runner.tool_cache }}/cargo-vet/bin" >> $GITHUB_PATH - name: Ensure that the tool cache is populated with the cargo-vet binary - run: cargo install --root ${{ runner.tool_cache }}/cargo-vet --version ${{ env.CARGO_VET_VERSION }} cargo-vet + run: cargo +stable install --root ${{ runner.tool_cache }}/cargo-vet --version ${{ env.CARGO_VET_VERSION }} cargo-vet - name: Download Cargo.lock files if: ${{ inputs.download-lockfiles }} uses: actions/download-artifact@v4 diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 7a5f8e7d4..fb6528a0c 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -597,7 +597,7 @@ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_p who = "Lukasz Anforowicz " criteria = "safe-to-deploy" delta = "1.14.0 -> 1.15.0" -notes = "The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = \"std\")]`." +notes = 'The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = "std")]`.' aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.equivalent]] @@ -759,8 +759,8 @@ who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.35" notes = """ -Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits -(except for benign \"net\" hit in tests and \"fs\" hit in README.md) +Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits +(except for benign "net" hit in tests and "fs" hit in README.md) """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" @@ -1026,7 +1026,7 @@ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_p who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.197" -notes = "Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits" +notes = 'Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits' aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.serde_derive]] @@ -1045,7 +1045,7 @@ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_p who = "Lukasz Anforowicz " criteria = "safe-to-deploy" delta = "1.0.202 -> 1.0.203" -notes = "Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits" +notes = 'Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits' aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.serde_derive]] From c6f2ee0355a456ee990da04dc7ebbfef0e83f939 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Tue, 27 Jan 2026 15:59:51 -0800 Subject: [PATCH 09/79] Power policy refactor (#688) Part of the larger embedded-services refactor work. Power policy v0.2.0 refactor - Make power policy context explicit, generic, and injectable - Remove all global/singleton usage, making the context generic over channel size and requiring explicit passing of context references - Updates all examples and Type-C service code to use explicit, generic policy context parameters --- .../src/power/policy/action/device.rs | 78 +++--- .../src/power/policy/action/policy.rs | 52 ++-- embedded-service/src/power/policy/device.rs | 35 ++- embedded-service/src/power/policy/mod.rs | 2 +- embedded-service/src/power/policy/policy.rs | 246 ++++++++---------- examples/rt685s-evk/src/bin/type_c.rs | 38 ++- examples/rt685s-evk/src/bin/type_c_cfu.rs | 40 ++- examples/rt685s-evk/src/lib.rs | 18 ++ examples/std/src/bin/power_policy.rs | 58 +++-- examples/std/src/bin/type_c/basic.rs | 29 ++- examples/std/src/bin/type_c/external.rs | 24 +- examples/std/src/bin/type_c/service.rs | 40 ++- examples/std/src/bin/type_c/ucsi.rs | 49 +++- examples/std/src/bin/type_c/unconstrained.rs | 67 +++-- .../std/src/lib/type_c/mock_controller.rs | 11 +- examples/std/src/lib/type_c/mod.rs | 18 ++ power-policy-service/src/consumer.rs | 12 +- power-policy-service/src/lib.rs | 20 +- power-policy-service/src/provider.rs | 8 +- power-policy-service/src/task.rs | 75 +++++- type-c-service/src/service/mod.rs | 7 +- type-c-service/src/task.rs | 29 ++- type-c-service/src/wrapper/backing.rs | 49 ++-- type-c-service/src/wrapper/cfu.rs | 3 +- type-c-service/src/wrapper/dp.rs | 3 +- type-c-service/src/wrapper/mod.rs | 19 +- type-c-service/src/wrapper/pd.rs | 3 +- type-c-service/src/wrapper/power.rs | 11 +- type-c-service/src/wrapper/vdm.rs | 3 +- 29 files changed, 667 insertions(+), 380 deletions(-) diff --git a/embedded-service/src/power/policy/action/device.rs b/embedded-service/src/power/policy/action/device.rs index 01d0d6c36..b08a7681f 100644 --- a/embedded-service/src/power/policy/action/device.rs +++ b/embedded-service/src/power/policy/action/device.rs @@ -4,24 +4,24 @@ use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapabili use crate::{info, trace}; /// Device state machine control -pub struct Device<'a, S: Kind> { - device: &'a device::Device, +pub struct Device<'a, S: Kind, const N: usize> { + device: &'a device::Device, _state: core::marker::PhantomData, } /// Enum to contain any state -pub enum AnyState<'a> { +pub enum AnyState<'a, const N: usize> { /// Detached - Detached(Device<'a, Detached>), + Detached(Device<'a, Detached, N>), /// Idle - Idle(Device<'a, Idle>), + Idle(Device<'a, Idle, N>), /// Connected Consumer - ConnectedConsumer(Device<'a, ConnectedConsumer>), + ConnectedConsumer(Device<'a, ConnectedConsumer, N>), /// Connected Provider - ConnectedProvider(Device<'a, ConnectedProvider>), + ConnectedProvider(Device<'a, ConnectedProvider, N>), } -impl AnyState<'_> { +impl AnyState<'_, N> { /// Return the kind of the contained state pub fn kind(&self) -> StateKind { match self { @@ -33,9 +33,9 @@ impl AnyState<'_> { } } -impl<'a, S: Kind> Device<'a, S> { +impl<'a, S: Kind, const N: usize> Device<'a, S, N> { /// Create a new state machine - pub(crate) fn new(device: &'a device::Device) -> Self { + pub(crate) fn new(device: &'a device::Device) -> Self { Self { device, _state: core::marker::PhantomData, @@ -43,12 +43,14 @@ impl<'a, S: Kind> Device<'a, S> { } /// Detach the device - pub async fn detach(self) -> Result, Error> { + pub async fn detach(self) -> Result, Error> { info!("Received detach from device {}", self.device.id().0); self.device.set_state(device::State::Detached).await; self.device.update_consumer_capability(None).await; self.device.update_requested_provider_capability(None).await; - policy::send_request(self.device.id(), policy::RequestData::NotifyDetached) + self.device + .context_ref + .send_request(self.device.id(), policy::RequestData::NotifyDetached) .await? .complete_or_err()?; Ok(Device::new(self.device)) @@ -60,7 +62,9 @@ impl<'a, S: Kind> Device<'a, S> { self.device.update_consumer_capability(None).await; self.device.update_requested_provider_capability(None).await; self.device.set_state(device::State::Idle).await; - policy::send_request(self.device.id(), policy::RequestData::NotifyDisconnect) + self.device + .context_ref + .send_request(self.device.id(), policy::RequestData::NotifyDisconnect) .await? .complete_or_err() } @@ -76,12 +80,14 @@ impl<'a, S: Kind> Device<'a, S> { capability ); self.device.update_consumer_capability(capability).await; - policy::send_request( - self.device.id(), - policy::RequestData::NotifyConsumerCapability(capability), - ) - .await? - .complete_or_err() + self.device + .context_ref + .send_request( + self.device.id(), + policy::RequestData::NotifyConsumerCapability(capability), + ) + .await? + .complete_or_err() } /// Request the given power from the power policy service @@ -97,29 +103,33 @@ impl<'a, S: Kind> Device<'a, S> { info!("Request provide from device {}, {:#?}", self.device.id().0, capability); self.device.update_requested_provider_capability(Some(capability)).await; - policy::send_request( - self.device.id(), - policy::RequestData::RequestProviderCapability(capability), - ) - .await? - .complete_or_err()?; + self.device + .context_ref + .send_request( + self.device.id(), + policy::RequestData::RequestProviderCapability(capability), + ) + .await? + .complete_or_err()?; Ok(()) } } -impl<'a> Device<'a, Detached> { +impl<'a, const N: usize> Device<'a, Detached, N> { /// Attach the device - pub async fn attach(self) -> Result, Error> { + pub async fn attach(self) -> Result, Error> { info!("Received attach from device {}", self.device.id().0); self.device.set_state(device::State::Idle).await; - policy::send_request(self.device.id(), policy::RequestData::NotifyAttached) + self.device + .context_ref + .send_request(self.device.id(), policy::RequestData::NotifyAttached) .await? .complete_or_err()?; Ok(Device::new(self.device)) } } -impl Device<'_, Idle> { +impl Device<'_, Idle, N> { /// Notify the power policy service of an updated consumer power capability pub async fn notify_consumer_power_capability( &self, @@ -134,9 +144,9 @@ impl Device<'_, Idle> { } } -impl<'a> Device<'a, ConnectedConsumer> { +impl<'a, const N: usize> Device<'a, ConnectedConsumer, N> { /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await?; Ok(Device::new(self.device)) } @@ -144,15 +154,16 @@ impl<'a> Device<'a, ConnectedConsumer> { /// Notify the power policy service of an updated consumer power capability pub async fn notify_consumer_power_capability( &self, + capability: Option, ) -> Result<(), Error> { self.notify_consumer_power_capability_internal(capability).await } } -impl<'a> Device<'a, ConnectedProvider> { +impl<'a, const N: usize> Device<'a, ConnectedProvider, N> { /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await?; Ok(Device::new(self.device)) } @@ -165,6 +176,7 @@ impl<'a> Device<'a, ConnectedProvider> { /// Notify the power policy service of an updated consumer power capability pub async fn notify_consumer_power_capability( &self, + capability: Option, ) -> Result<(), Error> { self.notify_consumer_power_capability_internal(capability).await diff --git a/embedded-service/src/power/policy/action/policy.rs b/embedded-service/src/power/policy/action/policy.rs index 0559421fc..540ff762d 100644 --- a/embedded-service/src/power/policy/action/policy.rs +++ b/embedded-service/src/power/policy/action/policy.rs @@ -9,24 +9,24 @@ use crate::{error, info}; const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); /// Policy state machine control -pub struct Policy<'a, S: Kind> { - device: &'a device::Device, +pub struct Policy<'a, S: Kind, const N: usize> { + device: &'a device::Device, _state: core::marker::PhantomData, } /// Enum to contain any state -pub enum AnyState<'a> { +pub enum AnyState<'a, const N: usize> { /// Detached - Detached(Policy<'a, Detached>), + Detached(Policy<'a, Detached, N>), /// Idle - Idle(Policy<'a, Idle>), + Idle(Policy<'a, Idle, N>), /// Connected Consumer - ConnectedConsumer(Policy<'a, ConnectedConsumer>), + ConnectedConsumer(Policy<'a, ConnectedConsumer, N>), /// Connected Provider - ConnectedProvider(Policy<'a, ConnectedProvider>), + ConnectedProvider(Policy<'a, ConnectedProvider, N>), } -impl AnyState<'_> { +impl AnyState<'_, N> { /// Return the kind of the contained state pub fn kind(&self) -> StateKind { match self { @@ -38,9 +38,9 @@ impl AnyState<'_> { } } -impl<'a, S: Kind> Policy<'a, S> { +impl<'a, S: Kind, const N: usize> Policy<'a, S, N> { /// Create a new state machine - pub(crate) fn new(device: &'a device::Device) -> Self { + pub(crate) fn new(device: &'a device::Device) -> Self { Self { device, _state: core::marker::PhantomData, @@ -97,14 +97,14 @@ impl<'a, S: Kind> Policy<'a, S> { } // The policy can do nothing when no device is attached -impl Policy<'_, Detached> {} +impl Policy<'_, Detached, N> {} -impl<'a> Policy<'a, Idle> { +impl<'a, const N: usize> Policy<'a, Idle, N> { /// Connect this device as a consumer pub async fn connect_as_consumer_no_timeout( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { info!("Device {} connecting as consumer", self.device.id().0); self.device @@ -122,7 +122,7 @@ impl<'a> Policy<'a, Idle> { pub async fn connect_consumer( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -133,7 +133,7 @@ impl<'a> Policy<'a, Idle> { pub async fn connect_provider_no_timeout( self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_as_provider_internal_no_timeout(capability) .await .map(|_| Policy::new(self.device)) @@ -143,30 +143,30 @@ impl<'a> Policy<'a, Idle> { pub async fn connect_provider( self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_provider_internal(capability) .await .map(|_| Policy::new(self.device)) } } -impl<'a> Policy<'a, ConnectedConsumer> { +impl<'a, const N: usize> Policy<'a, ConnectedConsumer, N> { /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { + pub async fn disconnect_no_timeout(self) -> Result, Error> { self.disconnect_internal_no_timeout() .await .map(|_| Policy::new(self.device)) } /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { self.disconnect_internal().await.map(|_| Policy::new(self.device)) } } -impl<'a> Policy<'a, ConnectedProvider> { +impl<'a, const N: usize> Policy<'a, ConnectedProvider, N> { /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { + pub async fn disconnect_no_timeout(self) -> Result, Error> { if let Err(e) = self.disconnect_internal_no_timeout().await { error!("Error disconnecting device {}: {:?}", self.device.id().0, e); return Err(e); @@ -175,7 +175,7 @@ impl<'a> Policy<'a, ConnectedProvider> { } /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { + pub async fn disconnect(self) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.disconnect_no_timeout()).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -186,7 +186,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_as_consumer_no_timeout( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { info!("Device {} connecting as consumer", self.device.id().0); self.device @@ -204,7 +204,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_consumer( self, capability: ConsumerPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { Ok(r) => r, Err(TimeoutError) => Err(Error::Timeout), @@ -215,7 +215,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_provider_no_timeout( &self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_as_provider_internal_no_timeout(capability) .await .map(|_| Policy::new(self.device)) @@ -225,7 +225,7 @@ impl<'a> Policy<'a, ConnectedProvider> { pub async fn connect_provider( &self, capability: ProviderPowerCapability, - ) -> Result, Error> { + ) -> Result, Error> { self.connect_provider_internal(capability) .await .map(|_| Policy::new(self.device)) diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 23a6cc052..45fe2fcdd 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -5,6 +5,7 @@ use embassy_sync::mutex::Mutex; use super::{DeviceId, Error, action}; use crate::ipc::deferred; +use crate::power::policy::policy::Context; use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; use crate::{GlobalRawMutex, intrusive_list}; @@ -111,7 +112,7 @@ pub struct Response { } /// Device struct -pub struct Device { +pub struct Device { /// Intrusive list node node: intrusive_list::Node, /// Device ID @@ -120,11 +121,13 @@ pub struct Device { state: Mutex, /// Command channel command: deferred::Channel, + /// Reference back to the power policy context to send messages + pub(crate) context_ref: &'static Context, } -impl Device { +impl Device { /// Create a new device - pub fn new(id: DeviceId) -> Self { + pub fn new(id: DeviceId, context_ref: &'static Context) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -134,6 +137,7 @@ impl Device { requested_provider_capability: None, }), command: deferred::Channel::new(), + context_ref, } } @@ -209,7 +213,9 @@ impl Device { } /// Try to provide access to the device actions for the given state - pub async fn try_device_action(&self) -> Result, Error> { + pub async fn try_device_action( + &self, + ) -> Result, Error> { let state = self.state().await.kind(); if S::kind() != state { return Err(Error::InvalidState(S::kind(), state)); @@ -218,7 +224,7 @@ impl Device { } /// Provide access to the current device state - pub async fn device_action(&self) -> action::device::AnyState<'_> { + pub async fn device_action(&self) -> action::device::AnyState<'_, CHANNEL_SIZE> { match self.state().await.kind() { StateKind::Detached => action::device::AnyState::Detached(action::device::Device::new(self)), StateKind::Idle => action::device::AnyState::Idle(action::device::Device::new(self)), @@ -233,7 +239,9 @@ impl Device { /// Try to provide access to the policy actions for the given state /// Implemented here for lifetime reasons - pub(super) async fn try_policy_action(&self) -> Result, Error> { + pub(super) async fn try_policy_action( + &self, + ) -> Result, Error> { let state = self.state().await.kind(); if S::kind() != state { return Err(Error::InvalidState(S::kind(), state)); @@ -243,7 +251,7 @@ impl Device { /// Provide access to the current policy actions /// Implemented here for lifetime reasons - pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_> { + pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_, CHANNEL_SIZE> { match self.state().await.kind() { StateKind::Detached => action::policy::AnyState::Detached(action::policy::Policy::new(self)), StateKind::Idle => action::policy::AnyState::Idle(action::policy::Policy::new(self)), @@ -257,7 +265,7 @@ impl Device { } /// Detach the device, this action is available in all states - pub async fn detach(&self) -> Result, Error> { + pub async fn detach(&self) -> Result, Error> { match self.device_action().await { action::device::AnyState::Detached(state) => Ok(state), action::device::AnyState::Idle(state) => state.detach().await, @@ -267,20 +275,21 @@ impl Device { } } -impl intrusive_list::NodeContainer for Device { +// This must be static due to a bound on IntrusiveNode needing a static reference to Any +impl intrusive_list::NodeContainer for Device { fn get_node(&self) -> &crate::Node { &self.node } } /// Trait for any container that holds a device -pub trait DeviceContainer { +pub trait DeviceContainer { /// Get the underlying device struct - fn get_power_policy_device(&self) -> &Device; + fn get_power_policy_device(&self) -> &Device; } -impl DeviceContainer for Device { - fn get_power_policy_device(&self) -> &Device { +impl DeviceContainer for Device { + fn get_power_policy_device(&self) -> &Device { self } } diff --git a/embedded-service/src/power/policy/mod.rs b/embedded-service/src/power/policy/mod.rs index b925f860b..3b1d4eb76 100644 --- a/embedded-service/src/power/policy/mod.rs +++ b/embedded-service/src/power/policy/mod.rs @@ -5,7 +5,7 @@ pub mod device; pub mod flags; pub mod policy; -pub use policy::{init, register_device}; +pub use policy::init; use crate::power::policy::charger::ChargerError; diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 3e3d694bb..25cf25976 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -1,5 +1,4 @@ //! Context for any power policy implementations -use core::sync::atomic::{AtomicBool, Ordering}; use crate::GlobalRawMutex; use crate::broadcaster::immediate as broadcaster; @@ -12,9 +11,6 @@ use super::{DeviceId, Error, action, charger}; use crate::power::policy::charger::ChargerResponseData::Ack; use crate::{error, intrusive_list}; -/// Number of slots for policy requests -const POLICY_CHANNEL_SIZE: usize = 1; - /// Data for a power policy request #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -72,212 +68,200 @@ pub struct Response { type InternalResponseData = Result; /// Power policy context -struct Context { +pub struct Context { /// Registered devices - devices: intrusive_list::IntrusiveList, + power_devices: intrusive_list::IntrusiveList, /// Policy request policy_request: Channel, /// Policy response policy_response: Channel, /// Registered chargers - chargers: intrusive_list::IntrusiveList, + charger_devices: intrusive_list::IntrusiveList, /// Message broadcaster broadcaster: broadcaster::Immediate, } -impl Context { - const fn new() -> Self { +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +impl Context { + /// Construct a new power policy Context + pub const fn new() -> Self { Self { - devices: intrusive_list::IntrusiveList::new(), - chargers: intrusive_list::IntrusiveList::new(), + power_devices: intrusive_list::IntrusiveList::new(), + charger_devices: intrusive_list::IntrusiveList::new(), policy_request: Channel::new(), policy_response: Channel::new(), broadcaster: broadcaster::Immediate::new(), } } -} - -static CONTEXT: Context = Context::new(); -/// Init power policy service -pub fn init() {} + /// Register a device with the power policy service + pub fn register_device( + &self, + device: &'static impl device::DeviceContainer, + ) -> Result<(), intrusive_list::Error> { + let device = device.get_power_policy_device(); + if self.get_device(device.id()).is_ok() { + return Err(intrusive_list::Error::NodeAlreadyInList); + } -/// Register a device with the power policy service -pub fn register_device(device: &'static impl device::DeviceContainer) -> Result<(), intrusive_list::Error> { - let device = device.get_power_policy_device(); - if get_device(device.id()).is_some() { - return Err(intrusive_list::Error::NodeAlreadyInList); + self.power_devices.push(device) } - CONTEXT.devices.push(device) -} + /// Register a charger with the power policy service + pub fn register_charger( + &self, + device: &'static impl charger::ChargerContainer, + ) -> Result<(), intrusive_list::Error> { + let device = device.get_charger(); + if self.get_charger(device.id()).is_ok() { + return Err(intrusive_list::Error::NodeAlreadyInList); + } -/// Register a charger with the power policy service -pub fn register_charger(device: &'static impl charger::ChargerContainer) -> Result<(), intrusive_list::Error> { - let device = device.get_charger(); - if get_charger(device.id()).is_some() { - return Err(intrusive_list::Error::NodeAlreadyInList); + self.charger_devices.push(device) } - CONTEXT.chargers.push(device) -} - -/// Find a device by its ID -fn get_device(id: DeviceId) -> Option<&'static device::Device> { - for device in &CONTEXT.devices { - if let Some(data) = device.data::() { - if data.id() == id { - return Some(data); + /// Get a device by its ID + pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device, Error> { + for device in &self.power_devices { + if let Some(data) = device.data::>() { + if data.id() == id { + return Ok(data); + } + } else { + error!("Non-device located in devices list"); } - } else { - error!("Non-device located in devices list"); } - } - None -} + Err(Error::InvalidDevice) + } -/// Returns the total amount of power that is being supplied to external devices -pub async fn compute_total_provider_power_mw() -> u32 { - let mut total = 0; - for device in CONTEXT.devices.iter_only::() { - if let Some(capability) = device.provider_capability().await { - if device.is_provider().await { - total += capability.capability.max_power_mw(); + /// Returns the total amount of power that is being supplied to external devices + pub async fn compute_total_provider_power_mw(&self) -> u32 { + let mut total = 0; + for device in self.power_devices.iter_only::>() { + if let Some(capability) = device.provider_capability().await { + if device.is_provider().await { + total += capability.capability.max_power_mw(); + } } } + total } - total -} -/// Find a device by its ID -fn get_charger(id: charger::ChargerId) -> Option<&'static charger::Device> { - for charger in &CONTEXT.chargers { - if let Some(data) = charger.data::() { - if data.id() == id { - return Some(data); + /// Get a charger by its ID + pub fn get_charger(&self, id: charger::ChargerId) -> Result<&'static charger::Device, Error> { + for charger in &self.charger_devices { + if let Some(data) = charger.data::() { + if data.id() == id { + return Ok(data); + } + } else { + error!("Non-device located in charger list"); } - } else { - error!("Non-device located in charger list"); } - } - - None -} -/// Convenience function to send a request to the power policy service -pub(super) async fn send_request(from: DeviceId, request: RequestData) -> Result { - CONTEXT - .policy_request - .send(Request { - id: from, - data: request, - }) - .await; - CONTEXT.policy_response.receive().await -} + Err(Error::InvalidDevice) + } -/// Initialize chargers in hardware -pub async fn init_chargers() -> ChargerResponse { - for charger in &CONTEXT.chargers { - if let Some(data) = charger.data::() { - data.execute_command(charger::PolicyEvent::InitRequest) - .await - .inspect_err(|e| error!("Charger {:?} failed InitRequest: {:?}", data.id(), e))?; - } + /// Convenience function to send a request to the power policy service + pub(super) async fn send_request(&self, from: DeviceId, request: RequestData) -> Result { + self.policy_request + .send(Request { + id: from, + data: request, + }) + .await; + self.policy_response.receive().await } - Ok(Ack) -} -/// Check if charger hardware is ready for communications. -pub async fn check_chargers_ready() -> ChargerResponse { - for charger in &CONTEXT.chargers { - if let Some(data) = charger.data::() { - data.execute_command(charger::PolicyEvent::CheckReady) - .await - .inspect_err(|e| error!("Charger {:?} failed CheckReady: {:?}", data.id(), e))?; + /// Initialize chargers in hardware + pub async fn init_chargers(&self) -> ChargerResponse { + for charger in &self.charger_devices { + if let Some(data) = charger.data::() { + data.execute_command(charger::PolicyEvent::InitRequest) + .await + .inspect_err(|e| error!("Charger {:?} failed InitRequest: {:?}", data.id(), e))?; + } } + Ok(Ack) } - Ok(Ack) -} -/// Register a message receiver for power policy messages -pub fn register_message_receiver( - receiver: &'static broadcaster::Receiver<'_, CommsMessage>, -) -> intrusive_list::Result<()> { - CONTEXT.broadcaster.register_receiver(receiver) -} - -/// Singleton struct to give access to the power policy context -pub struct ContextToken(()); - -impl ContextToken { - /// Create a new context token, returning None if this function has been called before - pub fn create() -> Option { - static INIT: AtomicBool = AtomicBool::new(false); - if INIT.load(Ordering::SeqCst) { - return None; + /// Check if charger hardware is ready for communications. + pub async fn check_chargers_ready(&self) -> ChargerResponse { + for charger in &self.charger_devices { + if let Some(data) = charger.data::() { + data.execute_command(charger::PolicyEvent::CheckReady) + .await + .inspect_err(|e| error!("Charger {:?} failed CheckReady: {:?}", data.id(), e))?; + } } + Ok(Ack) + } - INIT.store(true, Ordering::SeqCst); - Some(ContextToken(())) + /// Register a message receiver for power policy messages + pub fn register_message_receiver( + &self, + receiver: &'static broadcaster::Receiver<'_, CommsMessage>, + ) -> intrusive_list::Result<()> { + self.broadcaster.register_receiver(receiver) } /// Initialize Policy charger devices - pub async fn init() -> Result<(), Error> { + pub async fn init(&self) -> Result<(), Error> { // Check if the chargers are powered and able to communicate - check_chargers_ready().await?; + self.check_chargers_ready().await?; // Initialize chargers - init_chargers().await?; + self.init_chargers().await?; Ok(()) } /// Wait for a power policy request pub async fn wait_request(&self) -> Request { - CONTEXT.policy_request.receive().await + self.policy_request.receive().await } /// Send a response to a power policy request pub async fn send_response(&self, response: Result) { - CONTEXT.policy_response.send(response).await - } - - /// Get a device by its ID - pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device, Error> { - get_device(id).ok_or(Error::InvalidDevice) + self.policy_response.send(response).await } /// Provides access to the device list pub fn devices(&self) -> &intrusive_list::IntrusiveList { - &CONTEXT.devices - } - - /// Get a charger by its ID - pub fn get_charger(&self, id: charger::ChargerId) -> Result<&'static charger::Device, Error> { - get_charger(id).ok_or(Error::InvalidDevice) + &self.power_devices } /// Provides access to the charger list pub fn chargers(&self) -> &intrusive_list::IntrusiveList { - &CONTEXT.chargers + &self.charger_devices } /// Try to provide access to the actions available to the policy for the given state and device pub async fn try_policy_action( &self, id: DeviceId, - ) -> Result, Error> { + ) -> Result, Error> { self.get_device(id)?.try_policy_action().await } /// Provide access to current policy actions - pub async fn policy_action(&self, id: DeviceId) -> Result, Error> { + pub async fn policy_action( + &self, + id: DeviceId, + ) -> Result, Error> { Ok(self.get_device(id)?.policy_action().await) } /// Broadcast a power policy message to all subscribers pub async fn broadcast_message(&self, message: CommsMessage) { - CONTEXT.broadcaster.broadcast(message).await; + self.broadcaster.broadcast(message).await; } } + +/// Init power policy service +pub fn init() {} diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 3d46d274d..8264a9954 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -32,6 +32,7 @@ const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); const PORT0_PWR_ID: PowerId = PowerId(0); const PORT1_PWR_ID: PowerId = PowerId(1); +const POLICY_CHANNEL_SIZE: usize = 1; bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; @@ -49,7 +50,7 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator>; +type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator, POLICY_CHANNEL_SIZE>; type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; @@ -68,10 +69,14 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st } #[embassy_executor::task] -async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { + power_policy_service::task::task( + policy, + None::<[&rt685s_evk_example::DummyPowerDevice; 0]>, + None::<[&rt685s_evk_example::DummyCharger; 0]>, + ) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] @@ -79,6 +84,7 @@ async fn service_task( controller_context: &'static embedded_services::type_c::controller::Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_policy_context: &'static embedded_services::power::policy::policy::Context, ) { info!("Starting type-c task"); @@ -101,7 +107,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers).await; + type_c_service::task::task(service, wrappers, power_policy_context).await; } #[embassy_executor::main] @@ -111,8 +117,13 @@ async fn main(spawner: Spawner) { info!("Embedded service init"); embedded_services::init().await; + static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); + let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( + power_policy_service::Config::default(), + )); + info!("Spawining power policy task"); - spawner.must_spawn(power_policy_service_task()); + spawner.must_spawn(power_policy_service_task(power_service)); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); @@ -152,15 +163,17 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static STORAGE: StaticCell> = StaticCell::new(); + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, 0, // CFU component ID [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], + &power_service.context, )); - static REFERENCED: StaticCell> = StaticCell::new(); + static REFERENCED: StaticCell> = + StaticCell::new(); let referenced = REFERENCED.init( storage .create_referenced() @@ -176,7 +189,12 @@ async fn main(spawner: Spawner) { WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); info!("Spawining type-c service task"); - spawner.must_spawn(service_task(controller_context, controllers, [wrapper])); + spawner.must_spawn(service_task( + controller_context, + controllers, + [wrapper], + &power_service.context, + )); spawner.must_spawn(pd_controller_task(wrapper)); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 87cfed3a4..66612e85b 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -47,7 +47,7 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator>; +type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator, POLICY_CHANNEL_SIZE>; type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; @@ -59,6 +59,8 @@ const PORT1_ID: GlobalPortId = GlobalPortId(1); const PORT0_PWR_ID: PowerId = PowerId(0); const PORT1_PWR_ID: PowerId = PowerId(1); +const POLICY_CHANNEL_SIZE: usize = 1; + #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { loop { @@ -154,10 +156,14 @@ async fn fw_update_task() { } #[embassy_executor::task] -async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { + power_policy_service::task::task( + policy, + None::<[&rt685s_evk_example::DummyPowerDevice; 0]>, + None::<[&rt685s_evk_example::DummyCharger; 0]>, + ) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] @@ -165,6 +171,7 @@ async fn service_task( controller_context: &'static Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_policy_context: &'static embedded_services::power::policy::policy::Context, ) -> ! { info!("Starting type-c task"); @@ -187,7 +194,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers).await; + type_c_service::task::task(service, wrappers, power_policy_context).await; unreachable!() } @@ -199,7 +206,13 @@ async fn main(spawner: Spawner) { embedded_services::init().await; info!("Spawining power policy task"); - spawner.must_spawn(power_policy_service_task()); + + static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); + let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( + power_policy_service::Config::default(), + )); + + spawner.must_spawn(power_policy_service_task(power_service)); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); @@ -238,15 +251,17 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static STORAGE: StaticCell> = StaticCell::new(); + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, CONTROLLER0_CFU_ID, [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], + &power_service.context, )); - static REFERENCED: StaticCell> = StaticCell::new(); + static REFERENCED: StaticCell> = + StaticCell::new(); let referenced = REFERENCED.init( storage .create_referenced() @@ -262,7 +277,12 @@ async fn main(spawner: Spawner) { WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); info!("Spawning type-c service task"); - spawner.must_spawn(service_task(controller_context, controllers, [wrapper])); + spawner.must_spawn(service_task( + controller_context, + controllers, + [wrapper], + &power_service.context, + )); spawner.must_spawn(pd_controller_task(wrapper)); diff --git a/examples/rt685s-evk/src/lib.rs b/examples/rt685s-evk/src/lib.rs index b40be19ff..82fbd0ff3 100644 --- a/examples/rt685s-evk/src/lib.rs +++ b/examples/rt685s-evk/src/lib.rs @@ -18,3 +18,21 @@ static BOOT_IMAGE_VERSION: u32 = 0x01000000; #[unsafe(link_section = ".keystore")] #[used] static KEYSTORE: [u8; 2048] = [0; 2048]; + +pub struct DummyCharger(embedded_services::power::policy::charger::Device); +impl embedded_services::power::policy::charger::ChargerContainer for DummyCharger { + fn get_charger(&self) -> &embedded_services::power::policy::charger::Device { + &self.0 + } +} + +pub struct DummyPowerDevice( + embedded_services::power::policy::device::Device, +); +impl embedded_services::power::policy::device::DeviceContainer + for DummyPowerDevice +{ + fn get_power_policy_device(&self) -> &embedded_services::power::policy::device::Device { + &self.0 + } +} diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index af2ee9608..99d1c2053 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -3,7 +3,7 @@ use embassy_sync::{blocking_mutex::raw::NoopRawMutex, once_lock::OnceLock, pubsu use embassy_time::{self as _, Timer}; use embedded_services::{ broadcaster::immediate as broadcaster, - power::policy::{self, ConsumerPowerCapability, PowerCapability, device, flags}, + power::policy::{self, ConsumerPowerCapability, PowerCapability, device, flags, policy::Context}, }; use log::*; use static_cell::StaticCell; @@ -18,14 +18,17 @@ const HIGH_POWER: PowerCapability = PowerCapability { current_ma: 3000, }; +const POWER_POLICY_CHANNEL_SIZE: usize = 1; +const NUM_POWER_DEVICES: usize = 2; + struct ExampleDevice { - device: policy::device::Device, + device: policy::device::Device, } impl ExampleDevice { - fn new(id: policy::DeviceId) -> Self { + fn new(id: policy::DeviceId, context_ref: &'static Context) -> Self { Self { - device: policy::device::Device::new(id), + device: policy::device::Device::new(id, context_ref), } } @@ -56,8 +59,8 @@ impl ExampleDevice { } } -impl policy::device::DeviceContainer for ExampleDevice { - fn get_power_policy_device(&self) -> &policy::device::Device { +impl policy::device::DeviceContainer for ExampleDevice { + fn get_power_policy_device(&self) -> &policy::device::Device { &self.device } } @@ -81,23 +84,25 @@ async fn device_task1(device: &'static ExampleDevice) { } #[embassy_executor::task] -async fn run(spawner: Spawner) { +async fn run(spawner: Spawner, service: &'static power_policy_service::PowerPolicy) { embedded_services::init().await; + spawner.must_spawn(receiver_task(service)); + info!("Creating device 0"); static DEVICE0: OnceLock = OnceLock::new(); - let device0_mock = DEVICE0.get_or_init(|| ExampleDevice::new(policy::DeviceId(0))); - policy::register_device(device0_mock).unwrap(); + let device0_mock = DEVICE0.get_or_init(|| ExampleDevice::new(policy::DeviceId(0), &service.context)); spawner.must_spawn(device_task0(device0_mock)); let device0 = device0_mock.device.try_device_action().await.unwrap(); info!("Creating device 1"); static DEVICE1: OnceLock = OnceLock::new(); - let device1_mock = DEVICE1.get_or_init(|| ExampleDevice::new(policy::DeviceId(1))); - policy::register_device(device1_mock).unwrap(); + let device1_mock = DEVICE1.get_or_init(|| ExampleDevice::new(policy::DeviceId(1), &service.context)); spawner.must_spawn(device_task1(device1_mock)); let device1 = device1_mock.device.try_device_action().await.unwrap(); + spawner.must_spawn(power_policy_service_task(service, [device0_mock, device1_mock])); + // Plug in device 0, should become current consumer info!("Connecting device 0"); let device0 = device0.attach().await.unwrap(); @@ -156,7 +161,7 @@ async fn run(spawner: Spawner) { Timer::after_millis(250).await; info!( "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await + service.context.compute_total_provider_power_mw().await ); info!("Device 1 attach and requesting provider"); @@ -169,7 +174,7 @@ async fn run(spawner: Spawner) { Timer::after_millis(250).await; info!( "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await + service.context.compute_total_provider_power_mw().await ); // Provider upgrade should fail because device 0 is already connected @@ -182,7 +187,7 @@ async fn run(spawner: Spawner) { Timer::after_millis(250).await; info!( "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await + service.context.compute_total_provider_power_mw().await ); // Disconnect device 0 @@ -192,7 +197,7 @@ async fn run(spawner: Spawner) { Timer::after_millis(250).await; info!( "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await + service.context.compute_total_provider_power_mw().await ); // Provider upgrade should succeed now @@ -205,12 +210,12 @@ async fn run(spawner: Spawner) { Timer::after_millis(250).await; info!( "Total provider power: {} mW", - policy::policy::compute_total_provider_power_mw().await + service.context.compute_total_provider_power_mw().await ); } #[embassy_executor::task] -async fn receiver_task() { +async fn receiver_task(service: &'static power_policy_service::PowerPolicy) { static CHANNEL: StaticCell> = StaticCell::new(); let channel = CHANNEL.init(PubSubChannel::new()); @@ -220,7 +225,7 @@ async fn receiver_task() { static RECEIVER: StaticCell> = StaticCell::new(); let receiver = RECEIVER.init(broadcaster::Receiver::new(publisher)); - policy::policy::register_message_receiver(receiver).unwrap(); + service.context.register_message_receiver(receiver).unwrap(); loop { match subscriber.next_message().await { @@ -235,8 +240,11 @@ async fn receiver_task() { } #[embassy_executor::task] -async fn power_policy_task(config: power_policy_service::config::Config) { - power_policy_service::task::task(config) +async fn power_policy_service_task( + service: &'static power_policy_service::PowerPolicy, + devices: [&'static ExampleDevice; NUM_POWER_DEVICES], +) { + power_policy_service::task::task(service, Some(devices), None::<[&std_examples::type_c::DummyCharger; 0]>) .await .expect("Failed to start power policy service task"); } @@ -246,9 +254,13 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); + + static SERVICE: StaticCell> = StaticCell::new(); + let service = SERVICE.init(power_policy_service::PowerPolicy::new( + power_policy_service::config::Config::default(), + )); + executor.run(|spawner| { - spawner.must_spawn(power_policy_task(power_policy_service::config::Config::default())); - spawner.must_spawn(run(spawner)); - spawner.must_spawn(receiver_task()); + spawner.must_spawn(run(spawner, service)); }); } diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 1f8441fe5..85ec9adc2 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,6 +1,7 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; +use embedded_services::power::policy::*; use embedded_services::type_c::{Cached, ControllerId, controller}; use embedded_services::{IntrusiveList, power}; use embedded_usb_pd::ucsi::lpm; @@ -12,6 +13,7 @@ const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); +const POWER_POLICY_CHANNEL_SIZE: usize = 1; mod test_controller { use embedded_services::type_c::controller::{ControllerStatus, PortStatus}; @@ -21,7 +23,7 @@ mod test_controller { pub struct Controller<'a> { pub controller: controller::Device<'a>, - pub power_policy: power::policy::device::Device, + pub power_policy: power::policy::device::Device, } impl controller::DeviceContainer for Controller<'_> { @@ -30,17 +32,22 @@ mod test_controller { } } - impl power::policy::device::DeviceContainer for Controller<'_> { - fn get_power_policy_device(&self) -> &power::policy::device::Device { + impl power::policy::device::DeviceContainer for Controller<'_> { + fn get_power_policy_device(&self) -> &power::policy::device::Device { &self.power_policy } } impl<'a> Controller<'a> { - pub fn new(id: ControllerId, power_id: power::policy::DeviceId, ports: &'a [GlobalPortId]) -> Self { + pub fn new( + id: ControllerId, + power_id: power::policy::DeviceId, + ports: &'a [GlobalPortId], + power_context: &'static crate::power::policy::policy::Context, + ) -> Self { Self { controller: controller::Device::new(id, ports), - power_policy: power::policy::device::Device::new(power_id), + power_policy: power::policy::device::Device::new(power_id, power_context), } } @@ -124,12 +131,16 @@ mod test_controller { } #[embassy_executor::task] -async fn controller_task(controller_list: &'static IntrusiveList) { +async fn controller_task( + controller_list: &'static IntrusiveList, + power_context: &'static policy::Context, +) { static CONTROLLER: OnceLock = OnceLock::new(); static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; - let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, POWER0_ID, &PORTS)); + let controller = + CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, POWER0_ID, &PORTS, power_context)); controller::register_controller(controller_list, controller).unwrap(); loop { @@ -141,12 +152,14 @@ async fn controller_task(controller_list: &'static IntrusiveList) { async fn task(spawner: Spawner) { embedded_services::init().await; + static POWER_CONTEXT: StaticCell> = StaticCell::new(); static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let power_context = POWER_CONTEXT.init(policy::Context::new()); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); info!("Starting controller task"); - spawner.must_spawn(controller_task(controller_list)); + spawner.must_spawn(controller_task(controller_list, power_context)); // Wait for controller to be registered Timer::after_secs(1).await; diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 36a27b588..1b4cbcc83 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -3,6 +3,7 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::power::policy::*; use embedded_services::{ GlobalRawMutex, IntrusiveList, power, type_c::{Cached, ControllerId, controller::Context}, @@ -18,6 +19,7 @@ const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); +const POLICY_CHANNEL_SIZE: usize = 1; #[embassy_executor::task] async fn controller_task(wrapper: &'static Wrapper<'static>) { @@ -98,6 +100,7 @@ async fn service_task( controller_context: &'static Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_context: &'static policy::Context, ) { info!("Starting type-c task"); @@ -121,22 +124,27 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers).await; + type_c_service::task::task(service, wrappers, power_context).await; } -fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper<'static> { +fn create_wrapper( + controller_context: &'static Context, + power_context: &'static policy::Context, +) -> &'static mut Wrapper<'static> { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); - static STORAGE: StaticCell> = StaticCell::new(); + static STORAGE: StaticCell> = StaticCell::new(); let backing_storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, 0, // CFU component ID (unused) [(PORT0_ID, POWER0_ID)], + power_context, )); - static REFERENCED: StaticCell> = - StaticCell::new(); + static REFERENCED: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage<1, GlobalRawMutex, POLICY_CHANNEL_SIZE>, + > = StaticCell::new(); let referenced = REFERENCED.init( backing_storage .create_referenced() @@ -165,13 +173,15 @@ fn main() { let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); static CONTEXT: StaticCell = StaticCell::new(); let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static POWER_CONTEXT: StaticCell> = StaticCell::new(); + let power_context = POWER_CONTEXT.init(policy::Context::new()); - let wrapper = create_wrapper(context); + let wrapper = create_wrapper(context, power_context); static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(service_task(context, controller_list, [wrapper])); + spawner.must_spawn(service_task(context, controller_list, [wrapper], power_context)); spawner.must_spawn(task(spawner, context)); spawner.must_spawn(controller_task(wrapper)); }); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 5b7d3373a..d47b6e2d8 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -3,6 +3,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::power::policy::policy; use embedded_services::power::{self}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; @@ -25,6 +26,8 @@ const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); const DELAY_MS: u64 = 1000; +const POLICY_CHANNEL_SIZE: usize = 1; + mod debug { use embedded_services::{ comms::{self, Endpoint, EndpointID, Internal}, @@ -129,10 +132,14 @@ async fn task( } #[embassy_executor::task] -async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { + power_policy_service::task::task( + policy, + None::<[&std_examples::type_c::DummyPowerDevice; 0]>, + None::<[&std_examples::type_c::DummyCharger; 0]>, + ) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] @@ -140,6 +147,7 @@ async fn service_task( controller_context: &'static Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_policy_context: &'static policy::Context, ) { info!("Starting type-c task"); @@ -163,11 +171,12 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers).await; + type_c_service::task::task(service, wrappers, power_policy_context).await; } fn create_wrapper( context: &'static Context, + power_policy_context: &'static policy::Context, ) -> ( &'static mut Wrapper<'static>, &'static Mutex>, @@ -176,14 +185,15 @@ fn create_wrapper( static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); - static STORAGE: StaticCell> = StaticCell::new(); + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( context, CONTROLLER0_ID, 0, // CFU component ID (unused) [(PORT0_ID, POWER0_ID)], + power_policy_context, )); - static REFERENCED: StaticCell> = StaticCell::new(); + static REFERENCED: StaticCell> = StaticCell::new(); let referenced = REFERENCED.init( storage .create_referenced() @@ -220,11 +230,21 @@ fn main() { static CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - let (wrapper, controller, state) = create_wrapper(controller_context); + static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); + let power_policy_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( + power_policy_service::Config::default(), + )); + + let (wrapper, controller, state) = create_wrapper(controller_context, &power_policy_service.context); executor.run(|spawner| { - spawner.must_spawn(power_policy_service_task()); - spawner.must_spawn(service_task(controller_context, controller_list, [wrapper])); + spawner.must_spawn(power_policy_service_task(power_policy_service)); + spawner.must_spawn(service_task( + controller_context, + controller_list, + [wrapper], + &power_policy_service.context, + )); spawner.must_spawn(task(spawner, wrapper, controller, state)); }); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index bd8a617b5..1600ae2b6 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -31,6 +31,8 @@ const POWER1_ID: policy::DeviceId = policy::DeviceId(1); const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; +const POLICY_CHANNEL_SIZE: usize = 1; + #[embassy_executor::task] async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { @@ -171,10 +173,14 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { } #[embassy_executor::task] -async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { + power_policy_service::task::task( + policy, + None::<[&std_examples::type_c::DummyPowerDevice; 0]>, + None::<[&std_examples::type_c::DummyCharger; 0]>, + ) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] @@ -183,6 +189,7 @@ async fn service_task( controller_context: &'static Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_policy_context: &'static embedded_services::power::policy::policy::Context, ) -> ! { info!("Starting type-c task"); @@ -207,7 +214,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers).await; + type_c_service::task::task(service, wrappers, power_policy_context).await; unreachable!() } @@ -222,9 +229,20 @@ async fn type_c_service_task(spawner: Spawner) { static CONTEXT: StaticCell = StaticCell::new(); let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); - static REFERENCED0: StaticCell> = StaticCell::new(); + static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); + let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( + power_policy_service::Config::default(), + )); + + static STORAGE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new( + context, + CONTROLLER0_ID, + CFU0_ID, + [(PORT0_ID, POWER0_ID)], + &power_service.context, + )); + static REFERENCED0: StaticCell> = StaticCell::new(); let referenced0 = REFERENCED0.init( storage0 .create_referenced() @@ -241,9 +259,15 @@ async fn type_c_service_task(spawner: Spawner) { .expect("Failed to create wrapper"), ); - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); - static REFERENCED1: StaticCell> = StaticCell::new(); + static STORAGE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new( + context, + CONTROLLER1_ID, + CFU1_ID, + [(PORT1_ID, POWER1_ID)], + &power_service.context, + )); + static REFERENCED1: StaticCell> = StaticCell::new(); let referenced1 = REFERENCED1.init( storage1 .create_referenced() @@ -260,7 +284,7 @@ async fn type_c_service_task(spawner: Spawner) { .expect("Failed to create wrapper"), ); - spawner.must_spawn(power_policy_service_task()); + spawner.must_spawn(power_policy_service_task(power_service)); spawner.must_spawn(service_task( Config { ucsi_capabilities: UcsiCapabilities { @@ -289,6 +313,7 @@ async fn type_c_service_task(spawner: Spawner) { context, controller_list, [wrapper0, wrapper1], + &power_service.context, )); spawner.must_spawn(wrapper_task(wrapper0)); spawner.must_spawn(wrapper_task(wrapper1)); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index c9f489b88..8b896bbb9 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -35,6 +35,8 @@ const CFU2_ID: u8 = 0x02; const DELAY_MS: u64 = 1000; +const POLICY_CHANNEL_SIZE: usize = 1; + #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -98,10 +100,14 @@ async fn task(state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLL } #[embassy_executor::task] -async fn power_policy_service_task() { - power_policy_service::task::task(Default::default()) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { + power_policy_service::task::task( + policy, + None::<[&std_examples::type_c::DummyPowerDevice; 0]>, + None::<[&std_examples::type_c::DummyCharger; 0]>, + ) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] @@ -109,6 +115,7 @@ async fn service_task( controller_context: &'static Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_policy_context: &'static embedded_services::power::policy::policy::Context, ) -> ! { info!("Starting type-c task"); @@ -132,7 +139,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers).await; + type_c_service::task::task(service, wrappers, power_policy_context).await; unreachable!() } @@ -147,9 +154,20 @@ fn main() { static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [(PORT0_ID, POWER0_ID)])); - static REFERENCED: StaticCell> = StaticCell::new(); + static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); + let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( + power_policy_service::Config::default(), + )); + + static STORAGE: StaticCell> = StaticCell::new(); + let storage = STORAGE.init(Storage::new( + context, + CONTROLLER0_ID, + CFU0_ID, + [(PORT0_ID, POWER0_ID)], + &power_service.context, + )); + static REFERENCED: StaticCell> = StaticCell::new(); let referenced = REFERENCED.init( storage .create_referenced() @@ -171,9 +189,15 @@ fn main() { .expect("Failed to create wrapper"), ); - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [(PORT1_ID, POWER1_ID)])); - static REFERENCED1: StaticCell> = StaticCell::new(); + static STORAGE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new( + context, + CONTROLLER1_ID, + CFU1_ID, + [(PORT1_ID, POWER1_ID)], + &power_service.context, + )); + static REFERENCED1: StaticCell> = StaticCell::new(); let referenced1 = REFERENCED1.init( storage1 .create_referenced() @@ -195,9 +219,15 @@ fn main() { .expect("Failed to create wrapper"), ); - static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [(PORT2_ID, POWER2_ID)])); - static REFERENCED2: StaticCell> = StaticCell::new(); + static STORAGE2: StaticCell> = StaticCell::new(); + let storage2 = STORAGE2.init(Storage::new( + context, + CONTROLLER2_ID, + CFU2_ID, + [(PORT2_ID, POWER2_ID)], + &power_service.context, + )); + static REFERENCED2: StaticCell> = StaticCell::new(); let referenced2 = REFERENCED2.init( storage2 .create_referenced() @@ -220,8 +250,13 @@ fn main() { ); executor.run(|spawner| { - spawner.must_spawn(power_policy_service_task()); - spawner.must_spawn(service_task(context, controller_list, [wrapper0, wrapper1, wrapper2])); + spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(service_task( + context, + controller_list, + [wrapper0, wrapper1, wrapper2], + &power_service.context, + )); spawner.must_spawn(task([state0, state1, state2])); info!("Starting controller tasks"); spawner.must_spawn(controller_task(wrapper0)); diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 766373508..810362c5b 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -18,6 +18,8 @@ use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; use log::{debug, info, trace}; use std::cell::Cell; +const POWER_POLICY_CHANNEL_SIZE: usize = 1; + pub struct ControllerState { events: Signal, status: Mutex, @@ -336,5 +338,10 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { } } -pub type Wrapper<'a> = - type_c_service::wrapper::ControllerWrapper<'a, GlobalRawMutex, Mutex>, Validator>; +pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< + 'a, + GlobalRawMutex, + Mutex>, + Validator, + POWER_POLICY_CHANNEL_SIZE, +>; diff --git a/examples/std/src/lib/type_c/mod.rs b/examples/std/src/lib/type_c/mod.rs index 4fc4ccf55..bf900859f 100644 --- a/examples/std/src/lib/type_c/mod.rs +++ b/examples/std/src/lib/type_c/mod.rs @@ -1 +1,19 @@ pub mod mock_controller; + +pub struct DummyCharger(embedded_services::power::policy::charger::Device); +impl embedded_services::power::policy::charger::ChargerContainer for DummyCharger { + fn get_charger(&self) -> &embedded_services::power::policy::charger::Device { + &self.0 + } +} + +pub struct DummyPowerDevice( + embedded_services::power::policy::device::Device, +); +impl embedded_services::power::policy::device::DeviceContainer + for DummyPowerDevice +{ + fn get_power_policy_device(&self) -> &embedded_services::power::policy::device::Device { + &self.0 + } +} diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index 0600193fc..d1162858d 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -2,8 +2,6 @@ use core::cmp::Ordering; use embedded_services::debug; use embedded_services::power::policy::charger::Device as ChargerDevice; use embedded_services::power::policy::charger::PolicyEvent; -use embedded_services::power::policy::policy::check_chargers_ready; -use embedded_services::power::policy::policy::init_chargers; use super::*; @@ -31,14 +29,14 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl PowerPolicy { +impl PowerPolicy { /// Iterate over all devices to determine what is best power port provides the highest power async fn find_best_consumer(&self, state: &InternalState) -> Result, Error> { let mut best_consumer = None; let current_consumer_id = state.current_consumer_state.map(|f| f.device_id); for node in self.context.devices() { - let device = node.data::().ok_or(Error::InvalidDevice)?; + let device = node.data::>().ok_or(Error::InvalidDevice)?; let consumer_capability = device.consumer_capability().await; // Don't consider consumers below minimum threshold @@ -92,7 +90,7 @@ impl PowerPolicy { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); for node in self.context.devices() { - let device = node.data::().ok_or(Error::InvalidDevice)?; + let device = node.data::>().ok_or(Error::InvalidDevice)?; if let Some(capability) = device.consumer_capability().await { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; @@ -139,8 +137,8 @@ impl PowerPolicy { // Force charger CheckReady and InitRequest to get it into an initialized state. // This condition can get hit if we did not have a previous consumer and the charger is unpowered. info!("Charger is unpowered, forcing charger CheckReady and Init sequence"); - check_chargers_ready().await?; - init_chargers().await?; + self.context.check_chargers_ready().await?; + self.context.init_chargers().await?; device .execute_command(PolicyEvent::PolicyConfiguration( connected_consumer.consumer_power_capability, diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index c43719f81..a267ea43f 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -29,9 +29,9 @@ struct InternalState { } /// Power policy state -pub struct PowerPolicy { +pub struct PowerPolicy { /// Power policy context - context: policy::ContextToken, + pub context: policy::Context, /// State state: Mutex, /// Comms endpoint @@ -40,15 +40,15 @@ pub struct PowerPolicy { config: config::Config, } -impl PowerPolicy { +impl PowerPolicy { /// Create a new power policy - pub fn create(config: config::Config) -> Option { - Some(Self { - context: policy::ContextToken::create()?, + pub fn new(config: config::Config) -> Self { + Self { + context: policy::Context::new(), state: Mutex::new(InternalState::default()), tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Power)), config, - }) + } } async fn process_notify_attach(&self) -> Result<(), Error> { @@ -56,7 +56,7 @@ impl PowerPolicy { Ok(()) } - async fn process_notify_detach(&self, device: &device::Device) -> Result<(), Error> { + async fn process_notify_detach(&self, device: &device::Device) -> Result<(), Error> { self.context.send_response(Ok(policy::ResponseData::Complete)).await; self.remove_connected_provider(device.id()).await; self.update_current_consumer().await?; @@ -75,7 +75,7 @@ impl PowerPolicy { Ok(()) } - async fn process_notify_disconnect(&self, device: &device::Device) -> Result<(), Error> { + async fn process_notify_disconnect(&self, device: &device::Device) -> Result<(), Error> { self.context.send_response(Ok(policy::ResponseData::Complete)).await; if let Some(consumer) = self.state.lock().await.current_consumer_state.take() { info!("Device{}: Connected consumer disconnected", consumer.device_id.0); @@ -162,4 +162,4 @@ impl PowerPolicy { } } -impl comms::MailboxDelegate for PowerPolicy {} +impl comms::MailboxDelegate for PowerPolicy {} diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index 830509576..0f134bcb1 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -25,7 +25,7 @@ pub(super) struct State { state: PowerState, } -impl PowerPolicy { +impl PowerPolicy { /// Attempt to connect the requester as a provider pub(super) async fn connect_provider(&self, requester_id: DeviceId) { trace!("Device{}: Attempting to connect as provider", requester_id.0); @@ -48,7 +48,11 @@ impl PowerPolicy { let mut total_power_mw = 0; // Determine total requested power draw - for device in self.context.devices().iter_only::() { + for device in self + .context + .devices() + .iter_only::>() + { let target_provider_cap = if device.id() == requester_id { // Use the requester's requested power capability // this handles both new connections and upgrade requests diff --git a/power-policy-service/src/task.rs b/power-policy-service/src/task.rs index 916b2909f..f33327e2d 100644 --- a/power-policy-service/src/task.rs +++ b/power-policy-service/src/task.rs @@ -1,32 +1,79 @@ -use embassy_sync::once_lock::OnceLock; -use embedded_services::{comms, error, info}; +use embedded_services::{ + comms, error, info, + power::policy::{charger, device}, +}; -use crate::{PowerPolicy, config}; +use crate::PowerPolicy; #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InitError { - /// Power policy singleton has already been initialized - AlreadyInitialized, /// Comms registration failed RegistrationFailed, + /// Power device registration failed + PowerDeviceRegistrationFailed, + /// Charger device registration failed + ChargerDeviceRegistrationFailed, } -pub async fn task(config: config::Config) -> Result { +/// Initializes and runs the power policy task. +/// +/// This task function initializes the power policy service by registering its endpoint +/// with the comms layer, registering any provided **non Type-C** power devices and charger devices, +/// and then continuously processes incoming power policy requests. It should be run as its own task +/// that never returns. +/// +/// # Generic Parameters +/// +/// * `POLICY_CHANNEL_SIZE` - The capacity of the channel used for power policy messages. +/// * `NUM_POWER_DEVICES` - The number of **non Type-C** power devices to be managed by power policy. +/// * `NUM_CHARGERS` - The number of charger devices to be managed by power policy. +/// +/// # Arguments +/// +/// * `policy` - A static reference to the [`PowerPolicy`] instance that manages power policies. +/// * `power_devices` - An optional array of static references to **non Type-C** power device containers. +/// If provided, each device will be registered with the policy context. +/// * `charger_devices` - An optional array of static references to charger device containers. +/// If provided, each charger will be registered with the policy context. +/// +/// # Returns +/// +/// Returns `Result`. The `Never` type indicates that +/// this function runs indefinitely once initialized. The function returns an error if +/// initialization fails at any stage: +/// - [`InitError::RegistrationFailed`] - if comms endpoint registration fails +/// - [`InitError::PowerDeviceRegistrationFailed`] - if power device registration fails +/// - [`InitError::ChargerDeviceRegistrationFailed`] - if charger device registration fails +pub async fn task( + policy: &'static PowerPolicy, + power_devices: Option<[&'static impl device::DeviceContainer; NUM_POWER_DEVICES]>, + charger_devices: Option<[&'static impl charger::ChargerContainer; NUM_CHARGERS]>, +) -> Result { info!("Starting power policy task"); - static POLICY: OnceLock = OnceLock::new(); - let policy = if let Some(policy) = PowerPolicy::create(config) { - POLICY.get_or_init(|| policy) - } else { - error!("Power policy service already initialized"); - return Err(InitError::AlreadyInitialized); - }; - if comms::register_endpoint(policy, &policy.tp).await.is_err() { error!("Failed to register power policy endpoint"); return Err(InitError::RegistrationFailed); } + if let Some(power_devices) = power_devices { + for device in power_devices { + policy + .context + .register_device(device) + .map_err(|_| InitError::PowerDeviceRegistrationFailed)?; + } + } + + if let Some(charger_devices) = charger_devices { + for device in charger_devices { + policy + .context + .register_charger(device) + .map_err(|_| InitError::ChargerDeviceRegistrationFailed)?; + } + } + loop { if let Err(e) = policy.process().await { error!("Error processing request: {:?}", e); diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 4637f39e1..95c48a636 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -252,8 +252,11 @@ impl<'a> Service<'a> { } /// Register the Type-C service with the power policy service - pub fn register_comms(&'static self) -> Result<(), intrusive_list::Error> { - power_policy::policy::register_message_receiver(&self.power_policy_event_publisher) + pub fn register_comms( + &'static self, + power_policy_context: &power_policy::policy::Context, + ) -> Result<(), intrusive_list::Error> { + power_policy_context.register_message_receiver(&self.power_policy_event_publisher) } pub(crate) fn controllers(&self) -> &'a intrusive_list::IntrusiveList { diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index 11664a1dc..b328003fb 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -4,9 +4,19 @@ use embedded_services::{error, info}; use crate::{service::Service, wrapper::ControllerWrapper}; /// Task to run the Type-C service, takes a closure to customize the event loop -pub async fn task_closure<'a, M, C, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( +pub async fn task_closure< + 'a, + M, + C, + V, + Fut: Future, + F: Fn(&'a Service) -> Fut, + const N: usize, + const POLICY_CHANNEL_SIZE: usize, +>( service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, C, V>; N], + wrappers: [&'a ControllerWrapper<'a, M, C, V, POLICY_CHANNEL_SIZE>; N], + power_policy_context: &'a embedded_services::power::policy::policy::Context, f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, @@ -16,13 +26,17 @@ pub async fn task_closure<'a, M, C, V, Fut: Future, F: Fn(&'a Servi { info!("Starting type-c task"); - if service.register_comms().is_err() { + if service.register_comms(power_policy_context).is_err() { error!("Failed to register type-c service endpoint"); return; } for controller_wrapper in wrappers { - if controller_wrapper.register(service.controllers()).await.is_err() { + if controller_wrapper + .register(service.controllers(), power_policy_context) + .await + .is_err() + { error!("Failed to register a controller"); return; } @@ -34,16 +48,17 @@ pub async fn task_closure<'a, M, C, V, Fut: Future, F: Fn(&'a Servi } /// Task to run the Type-C service, running the default event loop -pub async fn task<'a, M, C, V, const N: usize>( +pub async fn task<'a, M, C, V, const N: usize, const POLICY_CHANNEL_SIZE: usize>( service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, C, V>; N], + wrappers: [&'a ControllerWrapper<'a, M, C, V, POLICY_CHANNEL_SIZE>; N], + power_policy_context: &'a embedded_services::power::policy::policy::Context, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, C: embedded_services::sync::Lockable, V: crate::wrapper::FwOfferValidator, ::Inner: embedded_services::type_c::controller::Controller, { - task_closure(service, wrappers, |service: &Service| async { + task_closure(service, wrappers, power_policy_context, |service: &Service| async { if let Err(e) = service.process_next_event().await { error!("Type-C service processing error: {:#?}", e); } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 711b5a02e..6c5d2db60 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -25,16 +25,21 @@ //! //! //! const NUM_PORTS: usize = 2; +//! const POLICY_CHANNEL_SIZE: usize = 1; //! -//! fn init(context: &'static embedded_services::type_c::controller::Context) { -//! static STORAGE: StaticCell> = StaticCell::new(); +//! fn init( +//! context: &'static embedded_services::type_c::controller::Context, +//! power_policy_context: &'static embedded_services::power::policy::policy::Context +//! ) { +//! static STORAGE: StaticCell> = StaticCell::new(); //! let storage = STORAGE.init(Storage::new( //! context, //! ControllerId(0), //! 0x0, //! [(GlobalPortId(0), power::policy::DeviceId(0)), (GlobalPortId(1), power::policy::DeviceId(1))], +//! power_policy_context //! )); -//! static REFERENCED: StaticCell> = StaticCell::new(); +//! static REFERENCED: StaticCell> = StaticCell::new(); //! let referenced = REFERENCED.init(storage.create_referenced().unwrap()); //! let _backing = referenced.create_backing().unwrap(); //! } @@ -100,7 +105,9 @@ struct InternalState<'a, const N: usize> { } impl<'a, const N: usize> InternalState<'a, N> { - fn try_new(storage: &'a Storage) -> Option { + fn try_new( + storage: &'a Storage, + ) -> Option { let port_states = storage.pd_alerts.each_ref().map(|pd_alert| { Some(PortState { status: PortStatus::new(), @@ -158,14 +165,14 @@ pub trait DynPortState<'a> { } /// Service registration objects -pub struct Registration<'a> { +pub struct Registration<'a, const POLICY_CHANNEL_SIZE: usize> { pub context: &'a embedded_services::type_c::controller::Context, pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, pub cfu_device: &'a embedded_services::cfu::component::CfuDevice, - pub power_devices: &'a [embedded_services::power::policy::device::Device], + pub power_devices: &'a [embedded_services::power::policy::device::Device], } -impl<'a> Registration<'a> { +impl<'a, const POLICY_CHANNEL_SIZE: usize> Registration<'a, POLICY_CHANNEL_SIZE> { pub fn num_ports(&self) -> usize { self.power_devices.len() } @@ -175,37 +182,39 @@ impl<'a> Registration<'a> { const MAX_BUFFERED_PD_ALERTS: usize = 4; /// Base storage -pub struct Storage<'a, const N: usize, M: RawMutex> { +pub struct Storage<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> { // Registration-related context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: embedded_services::cfu::component::CfuDevice, - power_devices: [embedded_services::power::policy::device::Device; N], + power_devices: [embedded_services::power::policy::device::Device; N], // State-related pd_alerts: [PubSubChannel; N], } -impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { +impl<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> Storage<'a, N, M, POLICY_CHANNEL_SIZE> { pub fn new( context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, cfu_id: ComponentId, ports: [(GlobalPortId, power::policy::DeviceId); N], + power_policy_context: &'static embedded_services::power::policy::policy::Context, ) -> Self { Self { context, controller_id, pd_ports: ports.map(|(port, _)| port), cfu_device: embedded_services::cfu::component::CfuDevice::new(cfu_id), - power_devices: ports.map(|(_, device)| embedded_services::power::policy::device::Device::new(device)), + power_devices: ports + .map(|(_, device)| embedded_services::power::policy::device::Device::new(device, power_policy_context)), pd_alerts: [const { PubSubChannel::new() }; N], } } /// Create referenced storage from this storage - pub fn create_referenced(&self) -> Option> { + pub fn create_referenced(&self) -> Option> { ReferencedStorage::try_from_storage(self) } } @@ -214,15 +223,17 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { /// /// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state /// completely separately. -pub struct ReferencedStorage<'a, const N: usize, M: RawMutex> { - storage: &'a Storage<'a, N, M>, +pub struct ReferencedStorage<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> { + storage: &'a Storage<'a, N, M, POLICY_CHANNEL_SIZE>, state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, } -impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { +impl<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> + ReferencedStorage<'a, N, M, POLICY_CHANNEL_SIZE> +{ /// Create a new referenced storage from the given storage and controller ID - fn try_from_storage(storage: &'a Storage) -> Option { + fn try_from_storage(storage: &'a Storage) -> Option { Some(Self { storage, state: RefCell::new(InternalState::try_new(storage)?), @@ -234,7 +245,7 @@ impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { } /// Creates the backing, returns `None` if a backing has already been created - pub fn create_backing<'b>(&'b self) -> Option> + pub fn create_backing<'b>(&'b self) -> Option> where 'b: 'a, { @@ -251,7 +262,7 @@ impl<'a, const N: usize, M: RawMutex> ReferencedStorage<'a, N, M> { } /// Wrapper around registration and type-erased state -pub struct Backing<'a> { - pub(crate) registration: Registration<'a>, +pub struct Backing<'a, const POLICY_CHANNEL_SIZE: usize> { + pub(crate) registration: Registration<'a, POLICY_CHANNEL_SIZE>, pub(crate) state: RefMut<'a, dyn DynPortState<'a>>, } diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index d1b7f7246..209ffbe25 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -29,7 +29,8 @@ impl FwUpdateState { } } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> + ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 8bae8560f..1b9882c8e 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -4,7 +4,8 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{sync::Lockable, trace, type_c::controller::Controller}; use embedded_usb_pd::{Error, LocalPortId}; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> + ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index fb2853c0c..9cb42a8d6 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -68,7 +68,7 @@ pub trait FwOfferValidator { pub const MAX_SUPPORTED_PORTS: usize = 2; /// Common functionality implemented on top of [`embedded_services::type_c::controller::Controller`] -pub struct ControllerWrapper<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> +pub struct ControllerWrapper<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> where ::Inner: Controller, { @@ -78,7 +78,7 @@ where /// FW update ticker used to check for timeouts and recovery attempts fw_update_ticker: Mutex, /// Registration information for services - registration: backing::Registration<'device>, + registration: backing::Registration<'device, POLICY_CHANNEL_SIZE>, /// State state: Mutex>>, /// SW port status event signal @@ -87,7 +87,8 @@ where config: config::Config, } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> + ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { @@ -95,7 +96,7 @@ where pub fn try_new( controller: &'device C, config: config::Config, - storage: &'device backing::ReferencedStorage<'device, N, M>, + storage: &'device backing::ReferencedStorage<'device, N, M, POLICY_CHANNEL_SIZE>, fw_version_validator: V, ) -> Option { const { @@ -117,7 +118,7 @@ where } /// Get the power policy devices for this controller. - pub fn power_policy_devices(&self) -> &[policy::device::Device] { + pub fn power_policy_devices(&self) -> &[policy::device::Device] { self.registration.power_devices } @@ -180,7 +181,7 @@ where async fn process_plug_event( &self, _controller: &mut C::Inner, - power: &policy::device::Device, + power: &policy::device::Device, port: LocalPortId, status: &PortStatus, ) -> Result<(), Error<::BusError>> { @@ -610,10 +611,11 @@ where pub async fn register( &'static self, controllers: &intrusive_list::IntrusiveList, + power_policy_context: &embedded_services::power::policy::policy::Context, ) -> Result<(), Error<::BusError>> { // TODO: Unify these devices? for device in self.registration.power_devices { - policy::register_device(device).map_err(|_| { + power_policy_context.register_device(device).map_err(|_| { error!( "Controller{}: Failed to register power device {}", self.registration.pd_controller.id().0, @@ -645,7 +647,8 @@ where } } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> Lockable for ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> Lockable + for ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index f55bd8bbf..4d1660618 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -9,7 +9,8 @@ use embedded_usb_pd::ucsi::{self, lpm}; use super::*; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> + ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 2d3682a1d..f2e6a5b29 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -15,19 +15,20 @@ use crate::wrapper::config::UnconstrainedSink; use super::*; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> + ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { /// Return the power device for the given port - pub fn get_power_device(&self, port: LocalPortId) -> Option<&policy::device::Device> { + pub fn get_power_device(&self, port: LocalPortId) -> Option<&policy::device::Device> { self.registration.power_devices.get(port.0 as usize) } /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, - power: &policy::device::Device, + power: &policy::device::Device, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); @@ -84,7 +85,7 @@ where /// Handle a new contract as provider pub(super) async fn process_new_provider_contract( &self, - power: &policy::device::Device, + power: &policy::device::Device, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); @@ -149,7 +150,7 @@ where &self, port: LocalPortId, controller: &mut C::Inner, - power: &policy::device::Device, + power: &policy::device::Device, ) -> Result<(), Error<::BusError>> { let state = power.state().await.kind(); if state == StateKind::ConnectedConsumer { diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 029b7f7d1..c4d0ad947 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -13,7 +13,8 @@ use crate::wrapper::{DynPortState, message::vdm::OutputKind}; use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator> ControllerWrapper<'device, M, C, V> +impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> + ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> where ::Inner: Controller, { From a95d1aea48d5659f5437dab4fe1ca9c6a44b1bcc Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:53:42 -0800 Subject: [PATCH 10/79] Fix `cargo test` on Windows (#696) `cargo test` currently doesn't compile on Windows. It looks like this is because a couple crates have hard dependencies on libraries that don't work on desktop - in particular: - debug-service depends on defmt - espi-service depends on embassy-imxrt - partition-manager has an enabled-by-default dependency on defmt These don't build on desktop, so when you try to run `cargo test` against mocks, the build fails. We get away with it on Linux because it looks like the linker over there is more aggressive at pruning unused symbols than the MSVC linker, but the MSVC linker errors immediately when it can't find a referenced symbol, even if that symbol is not reachable from any entry points. This change mitigates this problem by making partition-manager not default-depend on defmt and disabling build of debug-service and espi-service in test contexts. This does unfortunately mean that we can't write tests in those modules, but those modules already didn't have tests, so we're not conceding any existing test collateral by doing this. In future, we can look into breaking the dependency espi-service has on embassy-imxrt by introducing traits. It's less clear to me how we would do this with the debug-service - perhaps a stub implementation of some of the defmt macros. Fixes #691 --- debug-service/src/lib.rs | 21 +++++++++++++++++++ espi-service/src/espi_service.rs | 11 +++------- espi-service/src/lib.rs | 21 +++++++++++++++++++ .../partition-manager/Cargo.toml | 2 +- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/debug-service/src/lib.rs b/debug-service/src/lib.rs index 827847212..48db64975 100644 --- a/debug-service/src/lib.rs +++ b/debug-service/src/lib.rs @@ -3,8 +3,29 @@ #![allow(clippy::indexing_slicing)] #![allow(clippy::unwrap_used)] +// This module has a hard dependency on defmt, which doesn't work on desktop. +// This means that the entire workspace's tests won't compile if this module is enabled. +// +// On Linux, we sort-of get away with it - as far as I can tell, the linker on Linux is more aggressive +// with pruning unused code, so as long as there's no test that calls into anything that eventually calls +// into defmt, we at least compile on Linux. +// +// However, on Windows, it looks like the linker is erroring out because it can't find defmt-related symbols before +// it does the analysis to determine that those symbols aren't reachable anyway, so we have to disable this module +// entirely to be able to compile the workspace's tests at all on Windows. +// +// If we ever want to run tests for this module on Windows, we'll need some way to either break the dependency +// on defmt or provide dummy implementations of the defmt symbols for test builds on Windows. Until then, +// we need to gate everything on #[cfg(not(test))]. + +#[cfg(not(test))] mod debug_service; + +#[cfg(not(test))] mod defmt_ring_logger; + +#[cfg(not(test))] pub mod task; +#[cfg(not(test))] pub use debug_service::*; diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 55dcd55e7..74fd55826 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -114,7 +114,6 @@ impl Service<'_> { })?; // Last byte is PEC, ignore for now let packet = &packet[..packet.len() - 1]; - #[cfg(feature = "defmt")] trace!("Sending MCTP response: {:?}", packet); self.write_to_hw(espi, packet).map_err(|e| { @@ -228,7 +227,7 @@ pub(crate) async fn process_controller_event( with_pec[src_slice.len()] = 0; let with_pec = &with_pec[..=src_slice.len()]; - #[cfg(feature = "defmt")] + #[cfg(feature = "defmt")] // Required because without defmt, there is no implementation of UpperHex for [u8] debug!("OOB message: {:02X}", &src_slice[0..]); let mut assembly_access = espi_service @@ -240,13 +239,11 @@ pub(crate) async fn process_controller_event( match mctp_ctx.deserialize_packet(with_pec) { Ok(Some(message)) => { - #[cfg(feature = "defmt")] trace!("MCTP packet successfully deserialized"); match message.parse_as::() { Ok((header, body)) => { let target_endpoint = header.service.get_endpoint_id(); - #[cfg(feature = "defmt")] trace!( "Host Request: Service {:?}, Command {:?}", target_endpoint, header.message_id, @@ -261,9 +258,8 @@ pub(crate) async fn process_controller_event( .expect("result error type is infallible"); info!("MCTP packet forwarded to service: {:?}", target_endpoint); } - Err(_e) => { - #[cfg(feature = "defmt")] - error!("MCTP ODP type malformed: {}", _e); + Err(e) => { + error!("MCTP ODP type malformed: {:?}", e); espi.complete_port(port_event.port); @@ -282,7 +278,6 @@ pub(crate) async fn process_controller_event( // Handle protocol or medium error error!("MCTP packet malformed"); - #[cfg(feature = "defmt")] error!("error code: {:?}", _e); espi.complete_port(OOB_PORT_ID); diff --git a/espi-service/src/lib.rs b/espi-service/src/lib.rs index 88a69717e..e8233bc93 100644 --- a/espi-service/src/lib.rs +++ b/espi-service/src/lib.rs @@ -4,8 +4,29 @@ #![allow(clippy::panic)] #![allow(clippy::unwrap_used)] +// This module has a hard dependency on embassy-imxrt, which doesn't work on desktop. +// This means that the entire workspace's tests won't compile if this module is enabled. +// +// On Linux, we sort-of get away with it - as far as I can tell, the linker on Linux is more aggressive +// with pruning unused code, so as long as there's no test that calls into anything that eventually calls +// into embassy-imxrt, we at least compile on Linux. +// +// However, on Windows, it looks like the linker is erroring out because it can't find embassy-imxrt-related +// symbols before it does the analysis to determine that those symbols aren't reachable anyway, so we have to +// disable this module entirely to be able to compile the workspace's tests at all on Windows. +// +// If we ever want to run tests for this module on Windows, we'll need some way to break the dependency +// on embassy-imxrt - probably by switching to some sort of trait-based interface with eSPI. Until then, +// we need to gate everything on #[cfg(not(test))]. + +#[cfg(not(test))] mod espi_service; + +#[cfg(not(test))] mod mctp; + +#[cfg(not(test))] pub mod task; +#[cfg(not(test))] pub use espi_service::*; diff --git a/partition-manager/partition-manager/Cargo.toml b/partition-manager/partition-manager/Cargo.toml index fae91142d..5958dcb42 100644 --- a/partition-manager/partition-manager/Cargo.toml +++ b/partition-manager/partition-manager/Cargo.toml @@ -26,7 +26,7 @@ partition-manager-macros = { path = "../macros", features = [ defmt = { workspace = true, optional = true } [features] -default = ["esa", "bdd", "macros", "defmt"] +default = ["esa", "bdd", "macros"] macros = ["dep:partition-manager-macros"] From f4f36486e4b4f0e9a242c1c50a806b6c57b5b87d Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:20:57 -0800 Subject: [PATCH 11/79] Implement ACPI time-alarm device (#683) This change implements an ACPI time-and-alarm device as defined in section 9.18 of the ACPI 6.4 spec. Such a device needs to interface with the not-yet-existent power service to trigger wakes and subscribe to power source notifications, so those interface points are stubbed out for now. Resolves #138, #139, #140, #141, #142, #143, #144 --- Cargo.lock | 129 +++++- Cargo.toml | 6 +- .../src/ec_type/generator/ec_memory_map.yaml | 38 -- embedded-service/src/ec_type/mod.rs | 321 -------------- embedded-service/src/ec_type/structure.rs | 24 -- embedded-service/src/relay/mod.rs | 5 +- espi-service/Cargo.toml | 2 + espi-service/src/mctp.rs | 7 +- examples/rt633/Cargo.lock | 50 ++- examples/rt633/src/bin/espi_battery.rs | 1 + examples/rt685s-evk/Cargo.lock | 73 +++- examples/rt685s-evk/Cargo.toml | 4 + examples/rt685s-evk/src/bin/time_alarm.rs | 113 +++++ examples/std/Cargo.lock | 8 +- supply-chain/audits.toml | 17 + time-alarm-service-messages/Cargo.toml | 23 + .../src/acpi_timestamp.rs | 177 ++++++++ time-alarm-service-messages/src/lib.rs | 382 +++++++++++++++++ time-alarm-service/Cargo.toml | 35 ++ time-alarm-service/src/lib.rs | 354 ++++++++++++++++ time-alarm-service/src/task.rs | 20 + time-alarm-service/src/timer.rs | 400 ++++++++++++++++++ uart-service/Cargo.toml | 2 + uart-service/src/mctp.rs | 7 +- 24 files changed, 1762 insertions(+), 436 deletions(-) create mode 100644 examples/rt685s-evk/src/bin/time_alarm.rs create mode 100644 time-alarm-service-messages/Cargo.toml create mode 100644 time-alarm-service-messages/src/acpi_timestamp.rs create mode 100644 time-alarm-service-messages/src/lib.rs create mode 100644 time-alarm-service/Cargo.toml create mode 100644 time-alarm-service/src/lib.rs create mode 100644 time-alarm-service/src/task.rs create mode 100644 time-alarm-service/src/timer.rs diff --git a/Cargo.lock b/Cargo.lock index ee0606d4b..6efaee808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,6 +438,41 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.106", +] + [[package]] name = "dd-manifest-tree" version = "1.0.0" @@ -586,6 +621,31 @@ dependencies = [ "nb 1.1.0", ] +[[package]] +name = "embassy-executor" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +dependencies = [ + "critical-section", + "defmt 1.0.1", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "embassy-executor-timer-queue" version = "0.1.0" @@ -613,7 +673,7 @@ dependencies = [ [[package]] name = "embassy-imxrt" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#4699b3c023cd9276a7589c5cbdec8f6523a11739" +source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#fbf7df43734625332214634188b21883af114115" dependencies = [ "cfg-if", "cortex-m", @@ -817,9 +877,10 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#458f99699831ef5fbb557175334195cca7440f49" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" dependencies = [ "defmt 1.0.1", + "num_enum", ] [[package]] @@ -952,6 +1013,7 @@ dependencies = [ "mctp-rs", "num_enum", "thermal-service-messages", + "time-alarm-service-messages", ] [[package]] @@ -966,6 +1028,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "funty" version = "2.0.0" @@ -1082,6 +1150,12 @@ dependencies = [ "log", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "include_dir" version = "0.7.4" @@ -1476,9 +1550,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1486,9 +1560,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", @@ -1700,9 +1774,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" [[package]] name = "regex" @@ -1946,7 +2020,13 @@ dependencies = [ [[package]] name = "storage_bus" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#458f99699831ef5fbb557175334195cca7440f49" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subenum" @@ -2067,6 +2147,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time-alarm-service" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-mcu-hal", + "embedded-services", + "log", + "time-alarm-service-messages", + "zerocopy", +] + +[[package]] +name = "time-alarm-service-messages" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embedded-mcu-hal", + "embedded-services", + "log", + "num_enum", + "zerocopy", +] + [[package]] name = "tokio" version = "1.47.1" @@ -2257,6 +2367,7 @@ dependencies = [ "mctp-rs", "num_enum", "thermal-service-messages", + "time-alarm-service-messages", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c71070098..27aa09cd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ members = [ "platform-service", "power-button-service", "power-policy-service", + "time-alarm-service", + "time-alarm-service-messages", "type-c-service", "debug-service", "debug-service-messages", @@ -62,6 +64,7 @@ critical-section = "1.1" defmt = "0.3" document-features = "0.2.7" debug-service-messages = { path = "./debug-service-messages" } +embassy-executor = "0.9.1" embassy-futures = "0.1.2" embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" } embassy-sync = "0.7.2" @@ -80,7 +83,7 @@ embedded-storage = "0.3" embedded-storage-async = "0.4.1" embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb-pd", default-features = false } mctp-rs = { git = "https://github.com/dymk/mctp-rs" } -num_enum = { version = "0.7.4", default-features = false } +num_enum = { version = "0.7.5", default-features = false } portable-atomic = { version = "1.11", default-features = false } fixed = "1.23.1" heapless = "0.8.*" @@ -94,6 +97,7 @@ serde = { version = "1.0.*", default-features = false } static_cell = "2.1.0" toml = { version = "0.8", default-features = false } thermal-service-messages = { path = "./thermal-service-messages" } +time-alarm-service-messages = { path = "./time-alarm-service-messages" } syn = "2.0" tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" } tokio = { version = "1.42.0" } diff --git a/embedded-service/src/ec_type/generator/ec_memory_map.yaml b/embedded-service/src/ec_type/generator/ec_memory_map.yaml index 05677cc64..59e20535a 100644 --- a/embedded-service/src/ec_type/generator/ec_memory_map.yaml +++ b/embedded-service/src/ec_type/generator/ec_memory_map.yaml @@ -39,44 +39,6 @@ Capabilities: res0: type: u16 -# Size 0x28 -TimeAlarm: - events: - type: u32 - capability: - type: u32 - year: - type: u16 - month: - type: u8 - day: - type: u8 - hour: - type: u8 - minute: - type: u8 - second: - type: u8 - valid: - type: u8 - daylight: - type: u8 - res1: - type: u8 - milli: - type: u16 - time_zone: - type: u16 - res2: - type: u16 - alarm_status: - type: u32 - ac_time_val: - type: u32 - dc_time_val: - type: u32 - - # Size 0x64 Battery: events: diff --git a/embedded-service/src/ec_type/mod.rs b/embedded-service/src/ec_type/mod.rs index c74b13a2a..0a73a17c1 100644 --- a/embedded-service/src/ec_type/mod.rs +++ b/embedded-service/src/ec_type/mod.rs @@ -81,29 +81,6 @@ pub fn update_thermal_section(msg: &message::ThermalMessage, memory_map: &mut st } } -/// Update time alarm section of memory map based on battery message -pub fn update_time_alarm_section(msg: &message::TimeAlarmMessage, memory_map: &mut structure::ECMemory) { - match msg { - message::TimeAlarmMessage::Events(events) => memory_map.alarm.events = *events, - message::TimeAlarmMessage::Capability(capability) => memory_map.alarm.capability = *capability, - message::TimeAlarmMessage::Year(year) => memory_map.alarm.year = *year, - message::TimeAlarmMessage::Month(month) => memory_map.alarm.month = *month, - message::TimeAlarmMessage::Day(day) => memory_map.alarm.day = *day, - message::TimeAlarmMessage::Hour(hour) => memory_map.alarm.hour = *hour, - message::TimeAlarmMessage::Minute(minute) => memory_map.alarm.minute = *minute, - message::TimeAlarmMessage::Second(second) => memory_map.alarm.second = *second, - message::TimeAlarmMessage::Valid(valid) => memory_map.alarm.valid = *valid, - message::TimeAlarmMessage::Daylight(daylight) => memory_map.alarm.daylight = *daylight, - message::TimeAlarmMessage::Res1(res1) => memory_map.alarm.res1 = *res1, - message::TimeAlarmMessage::Milli(milli) => memory_map.alarm.milli = *milli, - message::TimeAlarmMessage::TimeZone(time_zone) => memory_map.alarm.time_zone = *time_zone, - message::TimeAlarmMessage::Res2(res2) => memory_map.alarm.res2 = *res2, - message::TimeAlarmMessage::AlarmStatus(alarm_status) => memory_map.alarm.alarm_status = *alarm_status, - message::TimeAlarmMessage::AcTimeVal(ac_time_val) => memory_map.alarm.ac_time_val = *ac_time_val, - message::TimeAlarmMessage::DcTimeVal(dc_time_val) => memory_map.alarm.dc_time_val = *dc_time_val, - } -} - /// Helper macro to simplify the conversion of memory map to message macro_rules! into_message { ($offset:ident, $length:ident, $member:expr, $msg:expr) => { @@ -409,99 +386,6 @@ pub fn mem_map_to_thermal_msg( } } -/// Convert from memory map offset and length to time alarm message -/// Modifies offset and length -pub fn mem_map_to_time_alarm_msg( - memory_map: &structure::ECMemory, - offset: &mut usize, - length: &mut usize, -) -> Result { - let local_offset = *offset - offset_of!(structure::ECMemory, alarm); - - if local_offset == offset_of!(structure::TimeAlarm, events) { - into_message!( - offset, - length, - memory_map.alarm.events, - message::TimeAlarmMessage::Events - ); - } else if local_offset == offset_of!(structure::TimeAlarm, capability) { - into_message!( - offset, - length, - memory_map.alarm.capability, - message::TimeAlarmMessage::Capability - ); - } else if local_offset == offset_of!(structure::TimeAlarm, year) { - into_message!(offset, length, memory_map.alarm.year, message::TimeAlarmMessage::Year); - } else if local_offset == offset_of!(structure::TimeAlarm, month) { - into_message!(offset, length, memory_map.alarm.month, message::TimeAlarmMessage::Month); - } else if local_offset == offset_of!(structure::TimeAlarm, day) { - into_message!(offset, length, memory_map.alarm.day, message::TimeAlarmMessage::Day); - } else if local_offset == offset_of!(structure::TimeAlarm, hour) { - into_message!(offset, length, memory_map.alarm.hour, message::TimeAlarmMessage::Hour); - } else if local_offset == offset_of!(structure::TimeAlarm, minute) { - into_message!( - offset, - length, - memory_map.alarm.minute, - message::TimeAlarmMessage::Minute - ); - } else if local_offset == offset_of!(structure::TimeAlarm, second) { - into_message!( - offset, - length, - memory_map.alarm.second, - message::TimeAlarmMessage::Second - ); - } else if local_offset == offset_of!(structure::TimeAlarm, valid) { - into_message!(offset, length, memory_map.alarm.valid, message::TimeAlarmMessage::Valid); - } else if local_offset == offset_of!(structure::TimeAlarm, daylight) { - into_message!( - offset, - length, - memory_map.alarm.daylight, - message::TimeAlarmMessage::Daylight - ); - } else if local_offset == offset_of!(structure::TimeAlarm, res1) { - into_message!(offset, length, memory_map.alarm.res1, message::TimeAlarmMessage::Res1); - } else if local_offset == offset_of!(structure::TimeAlarm, milli) { - into_message!(offset, length, memory_map.alarm.milli, message::TimeAlarmMessage::Milli); - } else if local_offset == offset_of!(structure::TimeAlarm, time_zone) { - into_message!( - offset, - length, - memory_map.alarm.time_zone, - message::TimeAlarmMessage::TimeZone - ); - } else if local_offset == offset_of!(structure::TimeAlarm, res2) { - into_message!(offset, length, memory_map.alarm.res2, message::TimeAlarmMessage::Res2); - } else if local_offset == offset_of!(structure::TimeAlarm, alarm_status) { - into_message!( - offset, - length, - memory_map.alarm.alarm_status, - message::TimeAlarmMessage::AlarmStatus - ); - } else if local_offset == offset_of!(structure::TimeAlarm, ac_time_val) { - into_message!( - offset, - length, - memory_map.alarm.ac_time_val, - message::TimeAlarmMessage::AcTimeVal - ); - } else if local_offset == offset_of!(structure::TimeAlarm, dc_time_val) { - into_message!( - offset, - length, - memory_map.alarm.dc_time_val, - message::TimeAlarmMessage::DcTimeVal - ); - } else { - Err(Error::InvalidLocation) - } -} - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { @@ -998,209 +882,4 @@ mod tests { let res = mem_map_to_thermal_msg(&memory_map, &mut offset, &mut length); assert!(res.is_err() && res.unwrap_err() == Error::InvalidLocation); } - - #[test] - fn test_mem_map_to_time_alarm_msg() { - use crate::ec_type::message::TimeAlarmMessage; - use crate::ec_type::structure::{ECMemory, TimeAlarm}; - - let memory_map = ECMemory { - alarm: TimeAlarm { - events: 1, - capability: 2, - year: 2025, - month: 3, - day: 12, - hour: 10, - minute: 30, - second: 45, - valid: 1, - daylight: 0, - res1: 0, - milli: 500, - time_zone: 1, - res2: 0, - alarm_status: 1, - ac_time_val: 100, - dc_time_val: 200, - }, - ..Default::default() - }; - - let mut offset = offset_of!(ECMemory, alarm); - let mut length = size_of::(); - - test_field!( - memory_map, - offset, - length, - memory_map.alarm.events, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Events - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.capability, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Capability - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.year, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Year - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.month, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Month - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.day, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Day - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.hour, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Hour - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.minute, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Minute - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.second, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Second - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.valid, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Valid - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.daylight, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Daylight - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.res1, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Res1 - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.milli, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Milli - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.time_zone, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::TimeZone - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.res2, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::Res2 - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.alarm_status, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::AlarmStatus - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.ac_time_val, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::AcTimeVal - ); - test_field!( - memory_map, - offset, - length, - memory_map.alarm.dc_time_val, - mem_map_to_time_alarm_msg, - TimeAlarmMessage::DcTimeVal - ); - - assert_eq!(length, 0); - } - - #[test] - fn test_mem_map_to_time_alarm_msg_error() { - use crate::ec_type::structure::{ECMemory, TimeAlarm}; - - let memory_map = ECMemory { - alarm: TimeAlarm { - events: 1, - capability: 2, - year: 2025, - month: 3, - day: 12, - hour: 10, - minute: 30, - second: 45, - valid: 1, - daylight: 0, - res1: 0, - milli: 500, - time_zone: 1, - res2: 0, - alarm_status: 1, - ac_time_val: 100, - dc_time_val: 200, - }, - ..Default::default() - }; - - let mut offset = offset_of!(ECMemory, alarm) + 1; - let mut length = size_of::(); - - let res = mem_map_to_time_alarm_msg(&memory_map, &mut offset, &mut length); - assert!(res.is_err() && res.unwrap_err() == Error::InvalidLocation); - } } diff --git a/embedded-service/src/ec_type/structure.rs b/embedded-service/src/ec_type/structure.rs index 05b3c2e6f..558b9d47f 100644 --- a/embedded-service/src/ec_type/structure.rs +++ b/embedded-service/src/ec_type/structure.rs @@ -34,29 +34,6 @@ pub struct Capabilities { pub res0: u16, } -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct TimeAlarm { - pub events: u32, - pub capability: u32, - pub year: u16, - pub month: u8, - pub day: u8, - pub hour: u8, - pub minute: u8, - pub second: u8, - pub valid: u8, - pub daylight: u8, - pub res1: u8, - pub milli: u16, - pub time_zone: u16, - pub res2: u16, - pub alarm_status: u32, - pub ac_time_val: u32, - pub dc_time_val: u32, -} - #[allow(missing_docs)] #[repr(C, packed)] #[derive(Clone, Copy, Debug, Default)] @@ -125,7 +102,6 @@ pub struct ECMemory { pub ver: Version, pub caps: Capabilities, pub notif: Notifications, - pub alarm: TimeAlarm, pub batt: Battery, pub therm: Thermal, } diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index 00d9e5d02..fbf6e988f 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -204,8 +204,7 @@ pub mod mctp { fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { match self { $( - HostRequest::$service_name(request) => request - .serialize(buffer) + HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), )+ } @@ -215,7 +214,7 @@ pub mod mctp { Ok(match header.service { $( OdpService::$service_name => Self::$service_name( - <$request_type>::deserialize(header.message_id, buffer) + <$request_type as SerializableMessage>::deserialize(header.message_id, buffer) .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, ), )+ diff --git a/espi-service/Cargo.toml b/espi-service/Cargo.toml index 7f0975d2c..1c3214e78 100644 --- a/espi-service/Cargo.toml +++ b/espi-service/Cargo.toml @@ -27,6 +27,7 @@ num_enum.workspace = true battery-service-messages.workspace = true debug-service-messages.workspace = true thermal-service-messages.workspace = true +time-alarm-service-messages.workspace = true [target.'cfg(target_os = "none")'.dependencies] cortex-m-rt.workspace = true @@ -53,6 +54,7 @@ defmt = [ "embassy-imxrt/defmt", "mctp-rs/defmt", "thermal-service-messages/defmt", + "time-alarm-service-messages/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs index 7ef3968e0..33778deb5 100644 --- a/espi-service/src/mctp.rs +++ b/espi-service/src/mctp.rs @@ -10,7 +10,8 @@ use embedded_services::{ // types. impl_odp_mctp_relay_types!( - Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + TimeAlarm, 0x0B, (comms::EndpointID::Internal(comms::Internal::TimeAlarm)), time_alarm_service_messages::AcpiTimeAlarmRequest, time_alarm_service_messages::AcpiTimeAlarmResult; ); diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index c1215bae5..2f02deefa 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -540,7 +540,7 @@ dependencies = [ [[package]] name = "embassy-imxrt" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#581f66e68ceaefb77e35f230dfb114322912dd6b" +source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d13994d875c29342b86bef374b44b9f401b44917" dependencies = [ "cfg-if", "cortex-m", @@ -570,7 +570,7 @@ dependencies = [ "mimxrt685s-pac", "nb 1.1.0", "paste", - "rand_core", + "rand_core 0.9.5", "storage_bus", ] @@ -718,9 +718,10 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#146fda33807af6aeabcade513914da95c926fbc9" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" dependencies = [ "defmt 1.0.1", + "num_enum", ] [[package]] @@ -819,6 +820,7 @@ dependencies = [ "mctp-rs", "num_enum", "thermal-service-messages", + "time-alarm-service-messages", ] [[package]] @@ -1100,27 +1102,27 @@ dependencies = [ [[package]] name = "mimxrt633s-pac" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843c1c63c367293e4fa270cc161b5bdfef55b4c6a0a18768f737241fb24be70c" +checksum = "a580cd0048e0e5b1eee17208b7f95ba05ec7ca0175789333b2fa7241798d3cdc" dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "vcell", ] [[package]] name = "mimxrt685s-pac" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0b80e5add9dc74500acbb1ca70248e237d242b77988631e41db40a225f3a40" +checksum = "8c8860258951178c4f91e42581ae8f33241a0e9a3e89bf2721d932ef366db51b" dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "vcell", ] @@ -1214,9 +1216,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1224,9 +1226,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", @@ -1331,7 +1333,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1340,6 +1342,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + [[package]] name = "rt633-examples" version = "0.1.0" @@ -1475,7 +1483,7 @@ dependencies = [ [[package]] name = "storage_bus" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#146fda33807af6aeabcade513914da95c926fbc9" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" [[package]] name = "strsim" @@ -1573,6 +1581,18 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "time-alarm-service-messages" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embedded-mcu-hal", + "embedded-services", + "num_enum", + "zerocopy", +] + [[package]] name = "typenum" version = "1.18.0" diff --git a/examples/rt633/src/bin/espi_battery.rs b/examples/rt633/src/bin/espi_battery.rs index 165b52441..081ef0a02 100644 --- a/examples/rt633/src/bin/espi_battery.rs +++ b/examples/rt633/src/bin/espi_battery.rs @@ -306,6 +306,7 @@ async fn main(spawner: Spawner) { let config = embassy_imxrt::i2c::master::Config { speed: embassy_imxrt::i2c::master::Speed::Standard, duty_cycle: embassy_imxrt::i2c::master::DutyCycle::new(50).unwrap(), + strict_mode: false, }; let i2c_fg = embassy_imxrt::i2c::master::I2cMaster::new_async( diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 52cc130ee..8a3945ed4 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -508,7 +508,7 @@ dependencies = [ [[package]] name = "embassy-imxrt" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#581f66e68ceaefb77e35f230dfb114322912dd6b" +source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d13994d875c29342b86bef374b44b9f401b44917" dependencies = [ "cfg-if", "cortex-m", @@ -535,7 +535,7 @@ dependencies = [ "itertools 0.11.0", "mimxrt600-fcb 0.2.2", "mimxrt633s-pac", - "mimxrt685s-pac", + "mimxrt685s-pac 0.5.0", "nb 1.1.0", "paste", "rand_core", @@ -688,9 +688,10 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#146fda33807af6aeabcade513914da95c926fbc9" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" dependencies = [ "defmt 1.0.1", + "num_enum", ] [[package]] @@ -1059,14 +1060,14 @@ dependencies = [ [[package]] name = "mimxrt633s-pac" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843c1c63c367293e4fa270cc161b5bdfef55b4c6a0a18768f737241fb24be70c" +checksum = "a580cd0048e0e5b1eee17208b7f95ba05ec7ca0175789333b2fa7241798d3cdc" dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "vcell", ] @@ -1079,7 +1080,19 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 0.3.100", + "vcell", +] + +[[package]] +name = "mimxrt685s-pac" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8860258951178c4f91e42581ae8f33241a0e9a3e89bf2721d932ef366db51b" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 1.0.1", "vcell", ] @@ -1173,9 +1186,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1183,9 +1196,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", @@ -1323,9 +1336,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" [[package]] name = "rt685s-evk-example" @@ -1350,12 +1363,14 @@ dependencies = [ "embedded-usb-pd", "futures", "mimxrt600-fcb 0.1.0", - "mimxrt685s-pac", + "mimxrt685s-pac 0.4.0", "panic-probe", "platform-service", "power-button-service", "power-policy-service", "static_cell", + "time-alarm-service", + "time-alarm-service-messages", "tps6699x", "type-c-service", ] @@ -1467,7 +1482,7 @@ dependencies = [ [[package]] name = "storage_bus" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#146fda33807af6aeabcade513914da95c926fbc9" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" [[package]] name = "strsim" @@ -1555,6 +1570,34 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "time-alarm-service" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-mcu-hal", + "embedded-services", + "time-alarm-service-messages", + "zerocopy", +] + +[[package]] +name = "time-alarm-service-messages" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embedded-mcu-hal", + "embedded-services", + "num_enum", + "zerocopy", +] + [[package]] name = "tps6699x" version = "0.1.0" diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index c27b34f6c..ca667b922 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -65,6 +65,10 @@ embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb "defmt", ] } type-c-service = { path = "../../type-c-service", features = ["defmt"] } +time-alarm-service = { path = "../../time-alarm-service", features = ["defmt"] } +time-alarm-service-messages = { path = "../../time-alarm-service-messages", features = [ + "defmt", +] } static_cell = "2.1.0" embedded-hal = "1.0.0" diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs new file mode 100644 index 000000000..5f4d3d305 --- /dev/null +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -0,0 +1,113 @@ +#![no_std] +#![no_main] + +use embassy_sync::once_lock::OnceLock; +use embedded_mcu_hal::Nvram; +use embedded_services::{error, info}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +mod mock_espi_service { + + use crate::OnceLock; + use crate::{error, info}; + use embassy_time::{Duration, Ticker}; + use embedded_services::comms::{self, EndpointID, External, Internal}; + use time_alarm_service_messages::{AcpiTimeAlarmRequest, AcpiTimeAlarmResult}; + + pub struct Service { + endpoint: comms::Endpoint, + } + + impl Service { + pub async fn init(spawner: embassy_executor::Spawner, service_storage: &'static OnceLock) { + let instance = service_storage.get_or_init(|| Service { + endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), + }); + + comms::register_endpoint(instance, &instance.endpoint).await.unwrap(); + + spawner.must_spawn(run_mock_service(instance)); + } + } + + impl comms::MailboxDelegate for Service { + fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { + let msg = message.data.get::().ok_or_else(|| { + error!("Mock eSPI service received unknown message type"); + comms::MailboxDelegateError::MessageNotFound + })?; + + info!("Mock eSPI service received ACPI Time Alarm Response: {:?}", msg); + + Ok(()) + } + } + + #[embassy_executor::task] + async fn run_mock_service(espi_service: &'static Service) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + + loop { + ticker.next().await; + espi_service + .endpoint + .send( + EndpointID::Internal(Internal::TimeAlarm), + &AcpiTimeAlarmRequest::GetRealTime, + ) + .await + .unwrap(); + } + } +} + +#[embassy_executor::main] +async fn main(spawner: embassy_executor::Spawner) { + let p = embassy_imxrt::init(Default::default()); + + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(embassy_imxrt::rtc::Rtc::new(p.RTC)); + let (dt_clock, rtc_nvram) = rtc.split(); + + let [tz, ac_expiration, ac_policy, dc_expiration, dc_policy, ..] = rtc_nvram.storage(); + + embedded_services::init().await; + info!("services initialized"); + + static MOCK_ESPI_SERVICE: OnceLock = OnceLock::new(); + mock_espi_service::Service::init(spawner, &MOCK_ESPI_SERVICE).await; + + static TIME_SERVICE: embassy_sync::once_lock::OnceLock = + embassy_sync::once_lock::OnceLock::new(); + let time_service = time_alarm_service::Service::init( + &TIME_SERVICE, + dt_clock, + tz, + ac_expiration, + ac_policy, + dc_expiration, + dc_policy, + ) + .await + .expect("Failed to initialize time-alarm service"); + + #[embassy_executor::task] + async fn command_handler_task(service: &'static time_alarm_service::Service) { + time_alarm_service::task::command_handler_task(service).await + } + + #[embassy_executor::task] + async fn ac_timer_task(service: &'static time_alarm_service::Service) { + time_alarm_service::task::ac_timer_task(service).await + } + + #[embassy_executor::task] + async fn dc_timer_task(service: &'static time_alarm_service::Service) { + time_alarm_service::task::dc_timer_task(service).await + } + + spawner.must_spawn(command_handler_task(time_service)); + spawner.must_spawn(ac_timer_task(time_service)); + spawner.must_spawn(dc_timer_task(time_service)); +} diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index d714101dc..b2ee67381 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1227,9 +1227,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1237,9 +1237,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index ce1d52347..b268a971d 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -217,11 +217,23 @@ who = "Matteo Tullo " criteria = "safe-to-deploy" version = "0.7.4" +[[audits.num_enum]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.7.4 -> 0.7.5" +notes = "Looks like this is just uptaking a new version of num_enum_derive" + [[audits.num_enum_derive]] who = "Matteo Tullo " criteria = "safe-to-deploy" version = "0.7.4" +[[audits.num_enum_derive]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.7.4 -> 0.7.5" +notes = "Looks like mostly improvements to error messaging" + [[audits.object]] who = "Robert Zieba " criteria = "safe-to-run" @@ -242,6 +254,11 @@ who = "Jerry Xie " criteria = "safe-to-deploy" version = "1.0.4" +[[audits.rand_core]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.6.4 -> 0.9.5" + [[audits.regex]] who = "Billy Price " criteria = "safe-to-run" diff --git a/time-alarm-service-messages/Cargo.toml b/time-alarm-service-messages/Cargo.toml new file mode 100644 index 000000000..04e172f7d --- /dev/null +++ b/time-alarm-service-messages/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "time-alarm-service-messages" +description = "Time and Alarm service message definitions" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +bitfield.workspace = true +defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } +embedded-mcu-hal.workspace = true +embedded-services.workspace = true +num_enum.workspace = true +zerocopy.workspace = true + +[features] +defmt = ["dep:defmt", "embedded-mcu-hal/defmt"] +log = ["dep:log", "embedded-services/log"] + +[lints] +workspace = true diff --git a/time-alarm-service-messages/src/acpi_timestamp.rs b/time-alarm-service-messages/src/acpi_timestamp.rs new file mode 100644 index 000000000..c89f2ff32 --- /dev/null +++ b/time-alarm-service-messages/src/acpi_timestamp.rs @@ -0,0 +1,177 @@ +use embedded_mcu_hal::time::{Datetime, Month, UncheckedDatetime}; + +use crate::AcpiTimeAlarmError; +use zerocopy::{FromBytes, I16, Immutable, IntoBytes, KnownLayout, LE, U16, Unaligned}; + +// Timestamp structure as specified in the ACPI spec. Must be exactly this layout. +#[repr(C, packed)] +#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Unaligned, Copy, Clone, Debug)] +struct RawAcpiTimestamp { + // Year: 1900 - 9999 + year: U16, + + // Month: 1 - 12 + month: u8, + + // Day: 1 - 31 + day: u8, + + // Hour: 0 - 23 + hour: u8, + + // Minute: 0 - 59 + minute: u8, + + // Second: 0 - 59. Leap seconds are not supported. + second: u8, + + // For _GRT, 0 = time is not valid (request failed), 1 = time is valid. For _SRT, this is padding and should be 0. + valid_or_padding: u8, + + // Milliseconds: 0-999. Leap seconds are not supported. + milliseconds: U16, + + // Time zone: -1440 to 1440 in minutes from UTC, or 2047 if unspecified + time_zone: I16, + + // 1 = daylight savings time in effect, 0 = standard time + daylight: u8, + + // Reserved, must be 0 + _padding: [u8; 3], +} + +impl From<&AcpiTimestamp> for RawAcpiTimestamp { + fn from(ts: &AcpiTimestamp) -> Self { + Self { + year: ts.datetime.year().into(), + month: ts.datetime.month().into(), + day: ts.datetime.day(), + hour: ts.datetime.hour(), + minute: ts.datetime.minute(), + second: ts.datetime.second(), + valid_or_padding: 1, // valid + milliseconds: ((ts.datetime.nanoseconds() / 1_000_000) as u16).into(), + time_zone: i16::from(ts.time_zone).into(), + daylight: ts.dst_status.into(), + _padding: [0; 3], + } + } +} + +// ------------------------------------------------- + +#[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum AcpiDaylightSavingsTimeStatus { + /// Daylight savings time is not observed in this timezone. + NotObserved = 0, + + /// Daylight savings time is observed in this timezone, but the current time has not been adjusted for it. + NotAdjusted = 1, + + // Note: in the spec, this is a pair of flags where bit 0 = observed, bit 1 = adjusted. 2 (adjusted but not observed) is nonsensical, so we omit it. + // + /// Daylight savings time is observed in this timezone, and the current time has been adjusted for it. + Adjusted = 3, +} + +// ------------------------------------------------- + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AcpiTimeZoneOffset { + minutes_from_utc: i16, // minutes from UTC +} + +impl AcpiTimeZoneOffset { + pub fn new(minutes_from_utc: i16) -> Result { + if !(-1440..=1440).contains(&minutes_from_utc) { + Err(AcpiTimeAlarmError::UnspecifiedFailure) + } else { + Ok(Self { minutes_from_utc }) + } + } + + pub fn minutes_from_utc(&self) -> i16 { + self.minutes_from_utc + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiTimeZone { + /// The time zone is not specified and no relation to UTC can be inferred. + Unknown, + + /// The time zone is this many minutes from UTC. + MinutesFromUtc(AcpiTimeZoneOffset), +} + +impl TryFrom for AcpiTimeZone { + type Error = AcpiTimeAlarmError; + + fn try_from(value: i16) -> Result { + if value == 2047 { + Ok(Self::Unknown) + } else { + Ok(Self::MinutesFromUtc(AcpiTimeZoneOffset::new(value)?)) + } + } +} + +impl From for i16 { + fn from(val: AcpiTimeZone) -> Self { + match val { + AcpiTimeZone::Unknown => 2047, + AcpiTimeZone::MinutesFromUtc(offset) => offset.minutes_from_utc(), + } + } +} + +// ------------------------------------------------- + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AcpiTimestamp { + pub datetime: Datetime, + pub time_zone: AcpiTimeZone, + pub dst_status: AcpiDaylightSavingsTimeStatus, +} + +impl AcpiTimestamp { + pub fn as_bytes(&self) -> [u8; core::mem::size_of::()] /* 16 */ { + // Size is guaranteed to be correct by zerocopy, but zerocopy returns as a slice rather than an array, + // and we need to return an owned array, so we need to convert. + // This operation is infallible due to the size guarantee. + #[allow(clippy::expect_used)] + RawAcpiTimestamp::from(self) + .as_bytes() + .try_into() + .expect("Size is guaranteed to be the size of RawAcpiTimestamp") + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + let raw = RawAcpiTimestamp::ref_from_bytes( + bytes + .get(..core::mem::size_of::()) + .ok_or(AcpiTimeAlarmError::UnspecifiedFailure)?, + ) + .map_err(|_| AcpiTimeAlarmError::UnspecifiedFailure)?; + + Ok(Self { + datetime: Datetime::new(UncheckedDatetime { + year: raw.year.get(), + month: Month::try_from(raw.month).map_err(|_| AcpiTimeAlarmError::UnspecifiedFailure)?, + day: raw.day, + hour: raw.hour, + minute: raw.minute, + second: raw.second, + nanosecond: (raw.milliseconds.get() as u32) * 1_000_000, + })?, + time_zone: raw.time_zone.get().try_into()?, + dst_status: raw.daylight.try_into()?, + }) + } +} diff --git a/time-alarm-service-messages/src/lib.rs b/time-alarm-service-messages/src/lib.rs new file mode 100644 index 000000000..0b6329eb0 --- /dev/null +++ b/time-alarm-service-messages/src/lib.rs @@ -0,0 +1,382 @@ +#![no_std] + +mod acpi_timestamp; +pub use acpi_timestamp::{AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimeZoneOffset, AcpiTimestamp}; + +use bitfield::bitfield; +use core::array::TryFromSliceError; +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +/// Message types for the ACPI Time and Alarm device service. +/// These are directly analogous to the ACPI Time and Alarm device methods. +/// See ACPI Specification 6.4, Section 9.18 "Time and Alarm Device" for additional details on semantics. +#[rustfmt::skip] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AcpiTimeAlarmRequest { + GetCapabilities, // _GCP + GetRealTime, // _GRT + SetRealTime(AcpiTimestamp), // _SRT + GetWakeStatus(AcpiTimerId), // _GWS + ClearWakeStatus(AcpiTimerId), // _CWS + SetTimerValue(AcpiTimerId, AlarmTimerSeconds), // _STV + GetTimerValue(AcpiTimerId), // _TIV + SetExpiredTimerPolicy(AcpiTimerId, AlarmExpiredWakePolicy), // _STP + GetExpiredTimerPolicy(AcpiTimerId), // _TIP +} + +#[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum AcpiTimeAlarmRequestDiscriminant { + GetCapabilities = 1, + GetRealTime = 2, + SetRealTime = 3, + GetWakeStatus = 4, + ClearWakeStatus = 5, + SetTimerValue = 6, + GetTimerValue = 7, + SetExpiredTimerPolicy = 8, + GetExpiredTimerPolicy = 9, +} + +impl SerializableMessage for AcpiTimeAlarmRequest { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::GetCapabilities => Ok(0), + Self::GetRealTime => Ok(0), + Self::SetRealTime(timestamp) => { + let serialized = timestamp.as_bytes(); + buffer + .split_at_mut_checked(serialized.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .0 + .copy_from_slice(&serialized); + Ok(serialized.len()) + } + Self::GetWakeStatus(timer_id) + | Self::ClearWakeStatus(timer_id) + | Self::GetTimerValue(timer_id) + | Self::GetExpiredTimerPolicy(timer_id) => safe_put_u32(buffer, 0, timer_id.into()), + + Self::SetTimerValue(timer_id, alarm_timer_seconds) => { + safe_put_u32(buffer, 0, timer_id.into())?; + safe_put_u32(buffer, 4, alarm_timer_seconds.0)?; + Ok(8) + } + Self::SetExpiredTimerPolicy(timer_id, alarm_expired_wake_policy) => { + safe_put_u32(buffer, 0, timer_id.into())?; + safe_put_u32(buffer, 4, alarm_expired_wake_policy.0)?; + Ok(8) + } + } + } + + fn discriminant(&self) -> u16 { + match self { + AcpiTimeAlarmRequest::GetCapabilities => AcpiTimeAlarmRequestDiscriminant::GetCapabilities.into(), + AcpiTimeAlarmRequest::GetRealTime => AcpiTimeAlarmRequestDiscriminant::GetRealTime.into(), + AcpiTimeAlarmRequest::SetRealTime(_) => AcpiTimeAlarmRequestDiscriminant::SetRealTime.into(), + AcpiTimeAlarmRequest::GetWakeStatus(_) => AcpiTimeAlarmRequestDiscriminant::GetWakeStatus.into(), + AcpiTimeAlarmRequest::ClearWakeStatus(_) => AcpiTimeAlarmRequestDiscriminant::ClearWakeStatus.into(), + AcpiTimeAlarmRequest::SetTimerValue(_, _) => AcpiTimeAlarmRequestDiscriminant::SetTimerValue.into(), + AcpiTimeAlarmRequest::GetTimerValue(_) => AcpiTimeAlarmRequestDiscriminant::GetTimerValue.into(), + AcpiTimeAlarmRequest::SetExpiredTimerPolicy(_, _) => { + AcpiTimeAlarmRequestDiscriminant::SetExpiredTimerPolicy.into() + } + AcpiTimeAlarmRequest::GetExpiredTimerPolicy(_) => { + AcpiTimeAlarmRequestDiscriminant::GetExpiredTimerPolicy.into() + } + } + } + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + let discriminant = AcpiTimeAlarmRequestDiscriminant::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))?; + match discriminant { + AcpiTimeAlarmRequestDiscriminant::GetCapabilities => Ok(AcpiTimeAlarmRequest::GetCapabilities), + AcpiTimeAlarmRequestDiscriminant::GetRealTime => Ok(AcpiTimeAlarmRequest::GetRealTime), + AcpiTimeAlarmRequestDiscriminant::SetRealTime => Ok(AcpiTimeAlarmRequest::SetRealTime( + AcpiTimestamp::try_from_bytes(buffer) + .map_err(|_| MessageSerializationError::InvalidPayload("Could not deserialize timestamp"))?, + )), + _ => { + let (timer_id, buffer) = buffer + .split_at_checked(4) + .ok_or(MessageSerializationError::BufferTooSmall)?; + let timer_id = AcpiTimerId::try_from(u32::from_le_bytes( + timer_id + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?, + )) + .map_err(|_| MessageSerializationError::InvalidPayload("Could not deserialize timer ID"))?; + + match discriminant { + AcpiTimeAlarmRequestDiscriminant::GetWakeStatus => { + Ok(AcpiTimeAlarmRequest::GetWakeStatus(timer_id)) + } + AcpiTimeAlarmRequestDiscriminant::ClearWakeStatus => { + Ok(AcpiTimeAlarmRequest::ClearWakeStatus(timer_id)) + } + AcpiTimeAlarmRequestDiscriminant::SetTimerValue => Ok(AcpiTimeAlarmRequest::SetTimerValue( + timer_id, + AlarmTimerSeconds(u32::from_le_bytes( + buffer + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?, + )), + )), + AcpiTimeAlarmRequestDiscriminant::GetTimerValue => { + Ok(AcpiTimeAlarmRequest::GetTimerValue(timer_id)) + } + AcpiTimeAlarmRequestDiscriminant::SetExpiredTimerPolicy => { + Ok(AcpiTimeAlarmRequest::SetExpiredTimerPolicy( + timer_id, + AlarmExpiredWakePolicy(u32::from_le_bytes( + buffer + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?, + )), + )) + } + AcpiTimeAlarmRequestDiscriminant::GetExpiredTimerPolicy => { + Ok(AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id)) + } + _ => Err(MessageSerializationError::UnknownMessageDiscriminant( + discriminant.into(), + )), + } + } + } + } +} + +// ------------------------------------------------- + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AlarmTimerSeconds(pub u32); +impl AlarmTimerSeconds { + pub const DISABLED: Self = Self(u32::MAX); +} + +impl Default for AlarmTimerSeconds { + fn default() -> Self { + Self::DISABLED + } +} + +// ------------------------------------------------- + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AlarmExpiredWakePolicy(pub u32); +impl AlarmExpiredWakePolicy { + #[allow(dead_code)] + pub const INSTANTLY: Self = Self(0); + pub const NEVER: Self = Self(u32::MAX); +} + +impl Default for AlarmExpiredWakePolicy { + fn default() -> Self { + Self::NEVER + } +} + +// ------------------------------------------------- + +// Timer ID as defined in the ACPI spec. +#[derive(Clone, Copy, Debug, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub enum AcpiTimerId { + AcPower = 0, + DcPower = 1, +} + +impl AcpiTimerId { + pub fn get_other_timer_id(&self) -> Self { + match self { + AcpiTimerId::AcPower => AcpiTimerId::DcPower, + AcpiTimerId::DcPower => AcpiTimerId::AcPower, + } + } +} + +bitfield!( + #[derive(Copy, Clone, Default, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct TimerStatus(u32); + impl Debug; + bool; + pub timer_expired, set_timer_expired: 0; + pub timer_triggered_wake, set_timer_triggered_wake: 1; +); + +// ------------------------------------------------- + +bitfield!( + #[derive(Copy, Clone, Default, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct TimeAlarmDeviceCapabilities(u32); + impl Debug; + bool; + pub ac_wake_implemented, set_ac_wake_implemented: 0; + pub dc_wake_implemented, set_dc_wake_implemented: 1; + pub realtime_implemented, set_realtime_implemented: 2; + pub realtime_accuracy_in_milliseconds, set_realtime_accuracy_in_milliseconds: 3; + pub get_wake_status_supported, set_get_wake_status_supported: 4; + pub ac_s4_wake_supported, set_ac_s4_wake_supported: 5; + pub ac_s5_wake_supported, set_ac_s5_wake_supported: 6; + pub dc_s4_wake_supported, set_dc_s4_wake_supported: 7; + pub dc_s5_wake_supported, set_dc_s5_wake_supported: 8; +); + +// ------------------------------------------------- + +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiTimeAlarmResponse { + Capabilities(TimeAlarmDeviceCapabilities), + RealTime(AcpiTimestamp), + TimerStatus(TimerStatus), + WakePolicy(AlarmExpiredWakePolicy), + TimerSeconds(AlarmTimerSeconds), + + /// Operation succeeded, but there's no data to return. + OkNoData, +} + +#[derive(Copy, Clone, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u16)] +enum AcpiTimeAlarmResponseDiscriminant { + Capabilities = 1, + RealTime = 2, + TimerStatus = 3, + WakePolicy = 4, + TimerSeconds = 5, + OkNoData = 6, +} + +impl SerializableMessage for AcpiTimeAlarmResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::Capabilities(capabilities) => safe_put_u32(buffer, 0, capabilities.0), + Self::RealTime(timestamp) => { + let result = timestamp.as_bytes(); + buffer + .split_at_mut_checked(result.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .0 + .copy_from_slice(&result); + Ok(result.len()) + } + Self::TimerStatus(timer_status) => safe_put_u32(buffer, 0, timer_status.0), + Self::WakePolicy(wake_policy) => safe_put_u32(buffer, 0, wake_policy.0), + Self::TimerSeconds(timer_seconds) => safe_put_u32(buffer, 0, timer_seconds.0), + Self::OkNoData => Ok(0), + } + } + + fn discriminant(&self) -> u16 { + match self { + Self::Capabilities(_) => AcpiTimeAlarmResponseDiscriminant::Capabilities.into(), + Self::RealTime(_) => AcpiTimeAlarmResponseDiscriminant::RealTime.into(), + Self::TimerStatus(_) => AcpiTimeAlarmResponseDiscriminant::TimerStatus.into(), + Self::WakePolicy(_) => AcpiTimeAlarmResponseDiscriminant::WakePolicy.into(), + Self::TimerSeconds(_) => AcpiTimeAlarmResponseDiscriminant::TimerSeconds.into(), + Self::OkNoData => AcpiTimeAlarmResponseDiscriminant::OkNoData.into(), + } + } + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + let discriminant = AcpiTimeAlarmResponseDiscriminant::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))?; + match discriminant { + AcpiTimeAlarmResponseDiscriminant::Capabilities => Ok(Self::Capabilities(TimeAlarmDeviceCapabilities( + safe_get_u32(buffer, 0)?, + ))), + AcpiTimeAlarmResponseDiscriminant::RealTime => { + Ok(Self::RealTime(AcpiTimestamp::try_from_bytes(buffer).map_err(|_| { + MessageSerializationError::InvalidPayload("invalid timestamp") + })?)) + } + AcpiTimeAlarmResponseDiscriminant::TimerStatus => { + Ok(Self::TimerStatus(TimerStatus(safe_get_u32(buffer, 0)?))) + } + AcpiTimeAlarmResponseDiscriminant::WakePolicy => { + Ok(Self::WakePolicy(AlarmExpiredWakePolicy(safe_get_u32(buffer, 0)?))) + } + AcpiTimeAlarmResponseDiscriminant::TimerSeconds => { + Ok(Self::TimerSeconds(AlarmTimerSeconds(safe_get_u32(buffer, 0)?))) + } + AcpiTimeAlarmResponseDiscriminant::OkNoData => Ok(Self::OkNoData), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +pub enum AcpiTimeAlarmError { + UnspecifiedFailure = 1, +} + +impl SerializableMessage for AcpiTimeAlarmError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + Self::UnspecifiedFailure => Ok(0), + } + } + + fn discriminant(&self) -> u16 { + (*self).into() + } + + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + let discriminant = AcpiTimeAlarmError::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))?; + + match discriminant { + AcpiTimeAlarmError::UnspecifiedFailure => Ok(AcpiTimeAlarmError::UnspecifiedFailure), + } + } +} + +impl From for AcpiTimeAlarmError { + fn from(_error: embedded_mcu_hal::time::DatetimeError) -> Self { + AcpiTimeAlarmError::UnspecifiedFailure + } +} + +impl From> for AcpiTimeAlarmError { + fn from(_error: num_enum::TryFromPrimitiveError) -> Self { + AcpiTimeAlarmError::UnspecifiedFailure + } +} + +impl From for AcpiTimeAlarmError { + fn from(_error: TryFromSliceError) -> Self { + AcpiTimeAlarmError::UnspecifiedFailure + } +} + +pub type AcpiTimeAlarmResult = Result; + +fn safe_put_u32(buffer: &mut [u8], index: usize, val: u32) -> Result { + let val = val.to_le_bytes(); + buffer + .get_mut(index..index + val.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&val); + Ok(val.len()) +} + +fn safe_get_u32(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u32::from_le_bytes(bytes)) +} diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml new file mode 100644 index 000000000..3b3f3fcff --- /dev/null +++ b/time-alarm-service/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "time-alarm-service" +description = "Time and Alarm service implementation" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +bitfield.workspace = true +defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } +embassy-executor.workspace = true +embassy-futures.workspace = true +embassy-sync.workspace = true +embassy-time.workspace = true +embedded-mcu-hal.workspace = true +embedded-services.workspace = true +time-alarm-service-messages.workspace = true +zerocopy.workspace = true + +[features] +defmt = [ + "dep:defmt", + "embedded-services/defmt", + "embassy-time/defmt", + "embassy-sync/defmt", + "embassy-executor/defmt", + "time-alarm-service-messages/defmt", +] + +log = ["dep:log", "embedded-services/log", "embassy-time/log"] + +[lints] +workspace = true diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs new file mode 100644 index 000000000..10d5732c9 --- /dev/null +++ b/time-alarm-service/src/lib.rs @@ -0,0 +1,354 @@ +#![no_std] + +use core::cell::RefCell; +use embassy_futures::select::{Either, select}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::channel::Channel; +use embassy_sync::once_lock::OnceLock; +use embassy_sync::signal::Signal; +use embedded_mcu_hal::NvramStorage; +use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; +use embedded_services::{GlobalRawMutex, comms::MailboxDelegateError}; +use embedded_services::{comms, info, warn}; +use time_alarm_service_messages::*; + +pub mod task; +mod timer; +use timer::Timer; + +// ------------------------------------------------- + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TimeAlarmError { + UnknownCommand, + DoubleInitError, + MailboxFullError, + ClockError(DatetimeClockError), +} + +impl From for MailboxDelegateError { + fn from(error: TimeAlarmError) -> Self { + match error { + TimeAlarmError::UnknownCommand => MailboxDelegateError::InvalidData, + TimeAlarmError::DoubleInitError => MailboxDelegateError::Other, + TimeAlarmError::MailboxFullError => MailboxDelegateError::BufferFull, + TimeAlarmError::ClockError(_) => MailboxDelegateError::Other, + } + } +} + +impl From for TimeAlarmError { + fn from(e: DatetimeClockError) -> Self { + TimeAlarmError::ClockError(e) + } +} + +impl From for TimeAlarmError { + fn from(_error: embedded_services::intrusive_list::Error) -> Self { + TimeAlarmError::DoubleInitError + } +} + +// ------------------------------------------------- + +mod time_zone_data { + use crate::AcpiDaylightSavingsTimeStatus; + use crate::AcpiTimeZone; + use crate::NvramStorage; + + pub struct TimeZoneData { + // Storage used to back the timezone and DST settings. + storage: &'static mut dyn NvramStorage<'static, u32>, + } + + #[repr(C)] + #[derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::Immutable, Copy, Clone, Debug)] + struct RawTimeZoneData { + tz: i16, + dst: u8, + _padding: u8, + } + + impl TimeZoneData { + pub fn new(storage: &'static mut dyn NvramStorage<'static, u32>) -> Self { + Self { storage } + } + + /// Writes the given time zone and daylight savings time status to NVRAM. + /// + pub fn set_data(&mut self, tz: AcpiTimeZone, dst: AcpiDaylightSavingsTimeStatus) { + let representation = RawTimeZoneData { + tz: tz.into(), + dst: dst.into(), + _padding: 0, + }; + + self.storage.write(zerocopy::transmute!(representation)); + } + + /// Retrieves the current time zone / daylight savings time. + /// If the stored data is invalid, implying that the NVRAM has never been initialized, defaults to + /// (AcpiTimeZone::Unknown, AcpiDaylightSavingsTimeStatus::NotObserved). + /// + pub fn get_data(&self) -> (AcpiTimeZone, AcpiDaylightSavingsTimeStatus) { + let representation: RawTimeZoneData = zerocopy::transmute!(self.storage.read()); + (|| -> Result<(AcpiTimeZone, AcpiDaylightSavingsTimeStatus), time_alarm_service_messages::AcpiTimeAlarmError> { + Ok((representation.tz.try_into()?, representation.dst.try_into()?)) + })() + .unwrap_or((AcpiTimeZone::Unknown, AcpiDaylightSavingsTimeStatus::NotObserved)) + } + } +} +use time_zone_data::TimeZoneData; + +// ------------------------------------------------- + +struct ClockState { + datetime_clock: &'static mut dyn DatetimeClock, + tz_data: TimeZoneData, +} + +// ------------------------------------------------- + +struct Timers { + ac_timer: Timer, + dc_timer: Timer, +} + +impl Timers { + fn get_timer(&self, timer: AcpiTimerId) -> &Timer { + match timer { + AcpiTimerId::AcPower => &self.ac_timer, + AcpiTimerId::DcPower => &self.dc_timer, + } + } + + fn new( + ac_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, + ac_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + dc_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, + dc_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + ) -> Self { + Self { + ac_timer: Timer::new(ac_expiration_storage, ac_policy_storage), + dc_timer: Timer::new(dc_expiration_storage, dc_policy_storage), + } + } +} + +// ------------------------------------------------- + +pub struct Service { + endpoint: comms::Endpoint, + + // ACPI messages from the host are sent through this channel. + acpi_channel: Channel, + + clock_state: Mutex>, + + // TODO [POWER_SOURCE] signal this whenever the power source changes + power_source_signal: Signal, + + timers: Timers, + + capabilities: TimeAlarmDeviceCapabilities, +} + +impl Service { + pub async fn init( + service_storage: &'static OnceLock, + backing_clock: &'static mut impl DatetimeClock, + tz_storage: &'static mut dyn NvramStorage<'static, u32>, + ac_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, + ac_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + dc_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, + dc_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + ) -> Result<&'static Service, TimeAlarmError> { + info!("Starting time-alarm service task"); + + let service = service_storage.get_or_init(|| Service { + endpoint: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::TimeAlarm)), + acpi_channel: Channel::new(), + clock_state: Mutex::new(RefCell::new(ClockState { + datetime_clock: backing_clock, + tz_data: TimeZoneData::new(tz_storage), + })), + power_source_signal: Signal::new(), + timers: Timers::new( + ac_expiration_storage, + ac_policy_storage, + dc_expiration_storage, + dc_policy_storage, + ), + capabilities: { + // TODO [CONFIG] We could consider making some of these user-configurable, e.g. if we want to support devices that don't have a battery + let mut caps = TimeAlarmDeviceCapabilities(0); + caps.set_ac_wake_implemented(true); + caps.set_dc_wake_implemented(true); + caps.set_realtime_implemented(true); + caps.set_realtime_accuracy_in_milliseconds(false); + caps.set_get_wake_status_supported(true); + caps.set_ac_s4_wake_supported(true); + caps.set_ac_s5_wake_supported(true); + caps.set_dc_s4_wake_supported(true); + caps.set_dc_s5_wake_supported(true); + caps + }, + }); + + // TODO [POWER_SOURCE] we need to subscribe to messages that tell us if we're on AC or DC power so we can decide which alarms to trigger, but those notifications are not yet implemented - revisit when they are. + // TODO [POWER_SOURCE] if it's possible to learn which power source is active at init time, we should set that one active rather than defaulting to the AC timer. + service.timers.ac_timer.start(&service.clock_state, true)?; + service.timers.dc_timer.start(&service.clock_state, false)?; + + comms::register_endpoint(service, &service.endpoint).await?; + + Ok(service) + } + + pub(crate) async fn handle_requests(&'static self) -> ! { + loop { + let acpi_command = self.acpi_channel.receive(); + let power_source_change = self.power_source_signal.wait(); + + match select(acpi_command, power_source_change).await { + Either::First((respond_to_endpoint, acpi_command)) => { + info!("[Time/Alarm] Received command: {:?}", acpi_command); + + let result: AcpiTimeAlarmResult = self + .handle_acpi_command(acpi_command) + .await + .map_err(|_| time_alarm_service_messages::AcpiTimeAlarmError::UnspecifiedFailure); + + info!("[Time/Alarm] Responding with: {:?}", result); + + let _: Result<(), core::convert::Infallible> = + self.endpoint.send(respond_to_endpoint, &result).await; + } + Either::Second(new_power_source) => { + info!("[Time/Alarm] Power source changed to {:?}", new_power_source); + + self.timers + .get_timer(new_power_source.get_other_timer_id()) + .set_active(&self.clock_state, false); + self.timers + .get_timer(new_power_source) + .set_active(&self.clock_state, true); + } + } + } + } + + pub(crate) async fn handle_timer(&'static self, timer_id: AcpiTimerId) -> ! { + let timer = self.timers.get_timer(timer_id); + loop { + timer.wait_until_wake(&self.clock_state).await; + let _ = self + .timers + .get_timer(timer_id.get_other_timer_id()) + .set_timer_wake_policy(&self.clock_state, AlarmExpiredWakePolicy::NEVER); + + warn!( + "[Time/Alarm] Timer {:?} expired and would trigger a wake now, but the power service is not yet implemented so will currently do nothing", + timer_id + ); + // TODO [COMMS] We can't currently trigger a wake because the power service isn't implemented yet - when it is, we need to notify it here + } + } + + async fn handle_acpi_command( + &'static self, + command: AcpiTimeAlarmRequest, + ) -> Result { + match command { + AcpiTimeAlarmRequest::GetCapabilities => Ok(AcpiTimeAlarmResponse::Capabilities(self.capabilities)), + AcpiTimeAlarmRequest::GetRealTime => self.clock_state.lock(|clock_state| { + let clock_state = clock_state.borrow(); + let datetime = clock_state.datetime_clock.get_current_datetime()?; + let (time_zone, dst_status) = clock_state.tz_data.get_data(); + Ok(AcpiTimeAlarmResponse::RealTime(AcpiTimestamp { + datetime, + time_zone, + dst_status, + })) + }), + AcpiTimeAlarmRequest::SetRealTime(timestamp) => self.clock_state.lock(|clock_state| { + let mut clock_state = clock_state.borrow_mut(); + clock_state.datetime_clock.set_current_datetime(×tamp.datetime)?; + clock_state.tz_data.set_data(timestamp.time_zone, timestamp.dst_status); + + Ok(AcpiTimeAlarmResponse::OkNoData) + }), + AcpiTimeAlarmRequest::GetWakeStatus(timer_id) => { + let status = self.timers.get_timer(timer_id).get_wake_status(); + Ok(AcpiTimeAlarmResponse::TimerStatus(status)) + } + AcpiTimeAlarmRequest::ClearWakeStatus(timer_id) => { + self.timers.get_timer(timer_id).clear_wake_status(); + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::SetExpiredTimerPolicy(timer_id, timer_policy) => { + self.timers + .get_timer(timer_id) + .set_timer_wake_policy(&self.clock_state, timer_policy)?; + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::SetTimerValue(timer_id, timer_value) => { + let new_expiration_time = match timer_value { + AlarmTimerSeconds::DISABLED => None, + AlarmTimerSeconds(secs) => { + let current_time = self + .clock_state + .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; + + Some(Datetime::from_unix_time_seconds( + current_time.to_unix_time_seconds() + u64::from(secs), + )) + } + }; + + self.timers + .get_timer(timer_id) + .set_expiration_time(&self.clock_state, new_expiration_time)?; + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id) => Ok(AcpiTimeAlarmResponse::WakePolicy( + self.timers.get_timer(timer_id).get_timer_wake_policy(), + )), + AcpiTimeAlarmRequest::GetTimerValue(timer_id) => { + let expiration_time = self.timers.get_timer(timer_id).get_expiration_time(); + + let timer_wire_format = match expiration_time { + Some(expiration_time) => { + let current_time = self + .clock_state + .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; + + AlarmTimerSeconds( + expiration_time + .to_unix_time_seconds() + .saturating_sub(current_time.to_unix_time_seconds()) as u32, + ) + } + None => AlarmTimerSeconds::DISABLED, + }; + + Ok(AcpiTimeAlarmResponse::TimerSeconds(timer_wire_format)) + } + } + } +} + +impl comms::MailboxDelegate for Service { + fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { + if let Some(acpi_cmd) = message.data.get::() { + self.acpi_channel + .try_send((message.from, *acpi_cmd)) + .map_err(|_| MailboxDelegateError::BufferFull)?; + Ok(()) + } else { + Err(comms::MailboxDelegateError::InvalidData) + } + } +} diff --git a/time-alarm-service/src/task.rs b/time-alarm-service/src/task.rs new file mode 100644 index 000000000..d1f1e6d44 --- /dev/null +++ b/time-alarm-service/src/task.rs @@ -0,0 +1,20 @@ +use crate::{AcpiTimerId, Service}; +use embedded_services::info; + +/// Call this from a dedicated async task. Must be called exactly once per service. +pub async fn command_handler_task(service: &'static Service) { + info!("Starting time-alarm service task"); + service.handle_requests().await; +} + +/// Call this from a dedicated async task. Must be called exactly once per service. +pub async fn ac_timer_task(service: &'static Service) { + info!("Starting time-alarm AC timer task"); + service.handle_timer(AcpiTimerId::AcPower).await; +} + +/// Call this from a dedicated async task. Must be called exactly once per service. +pub async fn dc_timer_task(service: &'static Service) { + info!("Starting time-alarm DC timer task"); + service.handle_timer(AcpiTimerId::DcPower).await; +} diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs new file mode 100644 index 000000000..78d46cb85 --- /dev/null +++ b/time-alarm-service/src/timer.rs @@ -0,0 +1,400 @@ +use crate::{AlarmExpiredWakePolicy, ClockState, TimerStatus}; +use core::cell::RefCell; +use embassy_futures::select::{Either, select}; +use embassy_sync::{blocking_mutex::Mutex, signal::Signal}; +use embedded_mcu_hal::NvramStorage; +use embedded_mcu_hal::time::{Datetime, DatetimeClockError}; +use embedded_services::{GlobalRawMutex, error}; + +/// Represents where in the timer lifecycle the current timer is +#[derive(Copy, Clone, Debug, PartialEq)] +enum WakeState { + /// Timer is not active + Clear, + /// Timer is active and programmed with the original expiration time + Armed, + /// Timer is active but expired when on the wrong power source + /// Includes the time at which we started running down the policy delay and the number of seconds that had already elapsed on the policy delay when we started waiting + ExpiredWaitingForPolicyDelay(Datetime, u32), + /// Timer is active and waiting for power source to be consistent with the timer type. + /// Includes the number of seconds that we've spent in the ExpiredWaitingForPolicyDelay state for so far. + ExpiredWaitingForPowerSource(u32), + /// Expired while the policy was set to NEVER, so the timer is effectively dead until reprogrammed + ExpiredOrphaned, +} + +mod persistent_storage { + use crate::{AlarmExpiredWakePolicy, Datetime}; + use embedded_mcu_hal::NvramStorage; + + pub struct PersistentStorage { + /// When the timer is programmed to expire, or None if the timer is not set + /// This can't be part of the wake_state because we need to be able to report its value for _CWS even when the timer has expired and + /// we're handling the power source policy. + expiration_time_storage: &'static mut dyn NvramStorage<'static, u32>, + + // Persistent storage for the AlarmExpiredWakePolicy + wake_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + } + + impl PersistentStorage { + pub fn new( + expiration_time_storage: &'static mut dyn NvramStorage<'static, u32>, + wake_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + ) -> Self { + Self { + expiration_time_storage, + wake_policy_storage, + } + } + + const NO_EXPIRATION_TIME: u32 = u32::MAX; + + pub fn get_timer_wake_policy(&self) -> AlarmExpiredWakePolicy { + AlarmExpiredWakePolicy(self.wake_policy_storage.read()) + } + + pub fn set_timer_wake_policy(&mut self, wake_policy: AlarmExpiredWakePolicy) { + self.wake_policy_storage.write(wake_policy.0); + } + + pub fn get_expiration_time(&self) -> Option { + match self.expiration_time_storage.read() { + Self::NO_EXPIRATION_TIME => None, + secs => Some(Datetime::from_unix_time_seconds(secs.into())), + } + } + + pub fn set_expiration_time(&mut self, expiration_time: Option) { + match expiration_time { + Some(dt) => { + // This won't overflow until 2106, which is acceptable for our use case. + self.expiration_time_storage.write(dt.to_unix_time_seconds() as u32); + } + None => { + self.expiration_time_storage.write(Self::NO_EXPIRATION_TIME); + } + } + } + } +} +use persistent_storage::PersistentStorage; + +struct TimerState { + persistent_storage: PersistentStorage, + + wake_state: WakeState, + + timer_status: TimerStatus, + + // Whether or not this timer is currently active (i.e. the system is on the power source this timer manages) + // Even if it's not active, it still counts down if it's programmed - it just won't trigger a wake event if it expires while inactive. + is_active: bool, +} + +pub(crate) struct Timer { + timer_state: Mutex>, + + timer_signal: Signal>, +} + +impl Timer { + pub fn new( + expiration_time_storage: &'static mut dyn NvramStorage<'static, u32>, + wake_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + ) -> Self { + Self { + timer_state: Mutex::new(RefCell::new(TimerState { + persistent_storage: PersistentStorage::new(expiration_time_storage, wake_policy_storage), + wake_state: WakeState::Clear, + timer_status: Default::default(), + is_active: false, + })), + timer_signal: Signal::new(), + } + } + + pub fn start( + &self, + clock_state: &'static Mutex>, + active: bool, + ) -> Result<(), DatetimeClockError> { + self.set_timer_wake_policy( + clock_state, + self.timer_state + .lock(|timer_state| timer_state.borrow().persistent_storage.get_timer_wake_policy()), + )?; + + self.set_expiration_time( + clock_state, + self.timer_state + .lock(|timer_state| timer_state.borrow().persistent_storage.get_expiration_time()), + )?; + + self.set_active(clock_state, active); + + Ok(()) + } + + pub fn get_wake_status(&self) -> TimerStatus { + self.timer_state.lock(|timer_state| { + let timer_state = timer_state.borrow(); + timer_state.timer_status + }) + } + + pub fn clear_wake_status(&self) { + self.timer_state.lock(|timer_state| { + let mut timer_state = timer_state.borrow_mut(); + timer_state.timer_status = Default::default(); + }); + } + + pub fn get_timer_wake_policy(&self) -> AlarmExpiredWakePolicy { + self.timer_state + .lock(|timer_state| timer_state.borrow().persistent_storage.get_timer_wake_policy()) + } + + pub fn set_timer_wake_policy( + &self, + clock_state: &'static Mutex>, + wake_policy: AlarmExpiredWakePolicy, + ) -> Result<(), DatetimeClockError> { + self.timer_state.lock(|timer_state| { + let mut timer_state = timer_state.borrow_mut(); + if let WakeState::ExpiredWaitingForPolicyDelay(_, _) = timer_state.wake_state { + timer_state.wake_state = + WakeState::ExpiredWaitingForPolicyDelay(Self::get_current_datetime(clock_state)?, 0); + self.timer_signal.signal(Some(wake_policy.0)); + } + + timer_state.persistent_storage.set_timer_wake_policy(wake_policy); + + Ok(()) + }) + } + + pub fn set_expiration_time( + &self, + clock_state: &'static Mutex>, + expiration_time: Option, + ) -> Result<(), DatetimeClockError> { + self.timer_state.lock(|timer_state| { + let mut timer_state = timer_state.borrow_mut(); + + // Per ACPI 6.4 section 9.18.1: "The status of wake timers can be reset by setting the wake alarm". + timer_state.timer_status = Default::default(); + + match expiration_time { + Some(dt) => { + // Note: If the expiration time was in the past, this will immediately trigger the timer to expire. + self.timer_signal.signal(Some( + dt.to_unix_time_seconds() + .saturating_sub(Self::get_current_datetime(clock_state)?.to_unix_time_seconds()) + as u32, // The ACPI spec doesn't provide a facility to program a timer more than u32::MAX seconds in the future, so this cast is safe + )); + + timer_state.persistent_storage.set_expiration_time(expiration_time); + timer_state.wake_state = WakeState::Armed; + } + None => self.clear_expiration_time(&mut timer_state), + } + + Ok(()) + }) + } + + pub fn get_expiration_time(&self) -> Option { + self.timer_state + .lock(|timer_state| timer_state.borrow().persistent_storage.get_expiration_time()) + } + + pub fn set_active(&self, clock_state: &'static Mutex>, is_active: bool) { + self.timer_state.lock(|timer_state| { + let mut timer_state = timer_state.borrow_mut(); + + let was_active = timer_state.is_active; + timer_state.is_active = is_active; + + if was_active == is_active { + return; // No change + } + + if !was_active { + if let WakeState::ExpiredWaitingForPowerSource(seconds_already_elapsed) = timer_state.wake_state { + match Self::get_current_datetime(clock_state) { + Ok(now) => { + timer_state.wake_state = + WakeState::ExpiredWaitingForPolicyDelay(now, seconds_already_elapsed); + self.timer_signal.signal(Some( + timer_state + .persistent_storage + .get_timer_wake_policy() + .0 + .saturating_sub(seconds_already_elapsed), + )); + } + Err(_) => { + // This should never happen, because it means the clock is not working after we've successfully initialized (which + // requires the clock to be working). + // If it does, though, we don't have a way to communicate failure to the host PC at this point, so we'll just + // forego the power source policy and wake the device immediately. + error!( + "[Time/Alarm] Failed to get current datetime when transitioning timer to active state" + ); + timer_state.wake_state = WakeState::Armed; + self.timer_signal.signal(Some(0)); + } + } + } + } else if let WakeState::ExpiredWaitingForPolicyDelay(wait_start_time, seconds_elapsed_before_wait) = + timer_state.wake_state + { + let total_seconds_elapsed_on_policy_delay = match Self::get_current_datetime(clock_state) { + Ok(now) => { + seconds_elapsed_before_wait + + (now + .to_unix_time_seconds() + .saturating_sub(wait_start_time.to_unix_time_seconds()) + as u32) // The ACPI spec expresses timeouts in terms of u32s - it's impossible to schedule a timer u32::MAX seconds in the future + } + Err(_) => { + // This should never happen, because it means the clock is not working after we've successfully initialized (which + // requires the clock to be working). + // If it does, though, we don't have a way to communicate failure to the host PC at this point, so we'll just + // pretend that the entire policy delay has elapsed. This will trigger an immediate wake when the power source becomes active again. + error!( + "[Time/Alarm] Failed to get current datetime when transitioning expired timer waiting for policy delay to inactive state" + ); + u32::MAX + } + }; + + timer_state.wake_state = WakeState::ExpiredWaitingForPowerSource(total_seconds_elapsed_on_policy_delay); + self.timer_signal.signal(None); + } + }); + } + + pub(crate) async fn wait_until_wake(&self, clock_state: &'static Mutex>) { + loop { + let mut wait_duration: Option = self.timer_signal.wait().await; + 'waiting_for_timer: loop { + match wait_duration { + Some(seconds) => { + match select( + embassy_time::Timer::after_secs(seconds.into()), + self.timer_signal.wait(), + ) + .await + { + Either::First(()) => { + if self.process_expired_timer(clock_state) { + return; + } + } + Either::Second(new_wait_duration) => { + wait_duration = new_wait_duration; + } + } + } + None => { + // Wait until a new timer command comes in + break 'waiting_for_timer; + } + } + } + } + } + + /// Handles state changes for when the timer expires (figuring out what to do based on the current power source, etc). + /// Returns true if the timer's expiry indicates that a wake event should be signaled to the host. + fn process_expired_timer(&self, clock_state: &'static Mutex>) -> bool { + self.timer_state.lock(|timer_state| { + let mut timer_state = timer_state.borrow_mut(); + + match timer_state.wake_state { + // Clear: timer was disarmed right as we were waking - nothing to do. + // ExpiredOrphaned: shouldn't happen, but if we're in this state the timer should be dead, so nothing to do. + // ExpiredWaitingForPowerSource: shouldn't happen, but if we're in this state the timer is still waiting for power source so nothing to do. + WakeState::Clear | WakeState::ExpiredOrphaned | WakeState::ExpiredWaitingForPowerSource(_) => { + return false; + } + + WakeState::Armed | WakeState::ExpiredWaitingForPolicyDelay(_, _) => { + let expiration_time = match timer_state.persistent_storage.get_expiration_time() { + Some(expiration_time) => expiration_time, + None => { + error!( + "[Time/Alarm] Timer expired when no expiration time was set - this should never happen" + ); + return false; + } + }; + + match Self::get_current_datetime(clock_state) { + Ok(now) => { + if now.to_unix_time_seconds() < expiration_time.to_unix_time_seconds() { + // Time hasn't actually passed the mark yet - this can happen if we were reprogrammed with a different time right as the old timer was expiring. Reset the timer. + timer_state.wake_state = WakeState::Armed; + self.timer_signal.signal(Some( + expiration_time + .to_unix_time_seconds() + .saturating_sub(now.to_unix_time_seconds()) + as u32, + )); + return false; + } + } + Err(_) => { + // This should never happen, because it means the clock is not working after we've successfully initialized (which + // requires the clock to be working). + // If it does, though, we don't have a way to communicate failure to the host PC at this point, so we'll just + // wake the device immediately on the assumption that the alarm has actually expired. This gets it wrong in the case + // where the timer is reprogrammed immediately as it expires, but that's an extremely rare case and we can't do better + // than that if our clock is broken. + error!("[Time/Alarm] Failed to get current datetime when processing expired timer"); + } + } + + timer_state.timer_status.set_timer_expired(true); + if timer_state.is_active { + timer_state.timer_status.set_timer_triggered_wake(true); + timer_state + .persistent_storage + .set_timer_wake_policy(AlarmExpiredWakePolicy::NEVER); + self.clear_expiration_time(&mut timer_state); + return true; + } else { + if timer_state.persistent_storage.get_timer_wake_policy() == AlarmExpiredWakePolicy::NEVER { + timer_state.wake_state = WakeState::ExpiredOrphaned; + return false; + } + + if let WakeState::ExpiredWaitingForPolicyDelay(_, _) = timer_state.wake_state { + timer_state.wake_state = WakeState::ExpiredWaitingForPowerSource( + timer_state.persistent_storage.get_timer_wake_policy().0, + ); + } else { + timer_state.wake_state = WakeState::ExpiredWaitingForPowerSource(0); + } + } + } + } + + false + }) + } + + fn clear_expiration_time(&self, timer_state: &mut TimerState) { + timer_state.persistent_storage.set_expiration_time(None); + timer_state.wake_state = WakeState::Clear; + self.timer_signal.signal(None); + } + + fn get_current_datetime( + clock_state: &'static Mutex>, + ) -> Result { + clock_state.lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime()) + } +} diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml index f90ff1e86..8049d9d28 100644 --- a/uart-service/Cargo.toml +++ b/uart-service/Cargo.toml @@ -27,6 +27,7 @@ num_enum.workspace = true battery-service-messages.workspace = true debug-service-messages.workspace = true thermal-service-messages.workspace = true +time-alarm-service-messages.workspace = true [features] default = [] @@ -40,6 +41,7 @@ defmt = [ "thermal-service-messages/defmt", "battery-service-messages/defmt", "debug-service-messages/defmt", + "time-alarm-service-messages/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/uart-service/src/mctp.rs b/uart-service/src/mctp.rs index 3c9fe10c4..1d6fb8183 100644 --- a/uart-service/src/mctp.rs +++ b/uart-service/src/mctp.rs @@ -6,7 +6,8 @@ use embedded_services::{ // TODO We'd ideally like these types to be passed in as a generic or something when the UART service is instantiated // so the UART service can be extended to handle 3rd party message types without needing to fork the UART service impl_odp_mctp_relay_types!( - Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + TimeAlarm, 0x0B, (comms::EndpointID::Internal(comms::Internal::TimeAlarm)), time_alarm_service_messages::AcpiTimeAlarmRequest, time_alarm_service_messages::AcpiTimeAlarmResult; ); From fcb79845f2806f2efc255e4c1d7dcb40583adf02 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Thu, 29 Jan 2026 11:48:36 -0800 Subject: [PATCH 12/79] Add pico-de-gallo battery example (#676) Add support for examples with the [pico-de-gallo](https://github.com/OpenDevicePartnership/pico-de-gallo) development board. This PR sets up a new workspace for pico-de-gallo examples and adds an example that runs the battery service with the [bq40z50-r5](https://github.com/OpenDevicePartnership/bq40z50/) fuel gauge connected to the pico-de-gallo's I2C bus. --- .github/workflows/check.yml | 25 + .vscode/settings.json | 5 +- Cargo.lock | 36 +- Cargo.toml | 2 +- battery-service/src/lib.rs | 2 +- embedded-service/src/comms.rs | 12 +- examples/pico-de-gallo/Cargo.lock | 2162 +++++++++++++++++++++ examples/pico-de-gallo/Cargo.toml | 41 + examples/pico-de-gallo/src/bin/battery.rs | 298 +++ examples/rt633/Cargo.lock | 60 +- examples/rt633/Cargo.toml | 8 +- examples/rt633/src/bin/espi_battery.rs | 127 +- examples/rt685s-evk/Cargo.lock | 20 +- examples/std/Cargo.lock | 12 +- examples/std/Cargo.toml | 2 +- supply-chain/audits.toml | 49 +- supply-chain/imports.lock | 134 +- thermal-service/src/lib.rs | 5 +- 18 files changed, 2813 insertions(+), 187 deletions(-) create mode 100644 examples/pico-de-gallo/Cargo.lock create mode 100644 examples/pico-de-gallo/Cargo.toml create mode 100644 examples/pico-de-gallo/src/bin/battery.rs diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 7719a62f7..1defa5dd0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -237,3 +237,28 @@ jobs: working-directory: ${{ matrix.example_directory }} run: | cargo clippy --locked + + check-pico-de-gallo-examples: + runs-on: ubuntu-latest + # we use a matrix here just because env can't be used in job names + # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability + strategy: + fail-fast: false + matrix: + example_directory: ["examples/pico-de-gallo"] + name: ubuntu / check-examples + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: Download Cargo.lock files + if: ${{ inputs.download-lockfiles }} + uses: actions/download-artifact@v4 + with: + name: updated-lock-files + - name: cargo clippy + working-directory: ${{ matrix.example_directory }} + run: | + cargo clippy --locked diff --git a/.vscode/settings.json b/.vscode/settings.json index b8686b2cb..90b7f0524 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "Cargo.toml", "examples/rt633/Cargo.toml", "examples/rt685s-evk/Cargo.toml", - "examples/std/Cargo.toml" + "examples/std/Cargo.toml", + "examples/pico-de-gallo/Cargo.toml" ] -} +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6efaee808..dcab087b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,6 +270,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "bitfield-struct" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -767,7 +778,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", + "bitflags 2.9.4", + "defmt 0.3.100", + "embedded-hal 1.0.0", + "zerocopy", +] + +[[package]] +name = "embedded-batteries" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" +dependencies = [ + "bitfield-struct 0.12.1", "bitflags 2.9.4", "defmt 0.3.100", "embedded-hal 1.0.0", @@ -776,13 +800,13 @@ dependencies = [ [[package]] name = "embedded-batteries-async" -version = "0.2.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" +checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.12.1", "defmt 0.3.100", - "embedded-batteries", + "embedded-batteries 0.3.4", "embedded-hal 1.0.0", ] @@ -1349,7 +1373,7 @@ source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271 dependencies = [ "bit-register", "defmt 0.3.100", - "embedded-batteries", + "embedded-batteries 0.2.1", "espi-device", "num_enum", "smbus-pec", diff --git a/Cargo.toml b/Cargo.toml index 27aa09cd2..f9c4e6509 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" embassy-sync = "0.7.2" embassy-time = "0.5.0" embassy-time-driver = "0.2.1" -embedded-batteries-async = "0.2.0" +embedded-batteries-async = "0.3" embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } embedded-hal = "1.0" embedded-hal-async = "1.0" diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index 811d48c29..6a94e11a1 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -112,7 +112,7 @@ pub fn register_fuel_gauge(device: &'static device::Device) -> Result<(), embedd } /// Use the battery service endpoint to send data to other subsystems and services. -pub async fn comms_send(endpoint_id: EndpointID, data: &impl Any) -> Result<(), Infallible> { +pub async fn comms_send(endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { SERVICE.endpoint.send(endpoint_id, data).await } diff --git a/embedded-service/src/comms.rs b/embedded-service/src/comms.rs index c7ff0f36d..fc3eb0b30 100644 --- a/embedded-service/src/comms.rs +++ b/embedded-service/src/comms.rs @@ -101,17 +101,17 @@ impl From for EndpointID { #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Data<'a> { - contents: &'a dyn Any, + contents: &'a (dyn Any + Send + Sync), } impl<'a> Data<'a> { /// Construct a Data portion of a Message from some data input - pub fn new(from: &'a impl Any) -> Self { + pub fn new(from: &'a (impl Any + Send + Sync)) -> Self { Self { contents: from } } /// Attempt to retrieve data as type T -- None if incorrect type - pub fn get(&self) -> Option<&T> { + pub fn get(&self) -> Option<&T> { self.contents.downcast_ref() } @@ -143,7 +143,7 @@ impl<'a> Data<'a> { /// if `data.is_a::() {}` /// else if `data.is_a::() {}` /// etc. - pub fn is_a(&self) -> bool { + pub fn is_a(&self) -> bool { self.type_id() == TypeId::of::() } } @@ -224,7 +224,7 @@ impl Endpoint { } /// Send a generic message to an endpoint - pub async fn send(&self, to: EndpointID, data: &impl Any) -> Result<(), Infallible> { + pub async fn send(&self, to: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { send(self.id, to, data).await } @@ -304,7 +304,7 @@ fn get_list(target: EndpointID) -> &'static OnceLock { } /// Send a generic message to an endpoint -pub async fn send(from: EndpointID, to: EndpointID, data: &impl Any) -> Result<(), Infallible> { +pub async fn send(from: EndpointID, to: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { route(Message { from, to, diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock new file mode 100644 index 000000000..ed341cdb8 --- /dev/null +++ b/examples/pico-de-gallo/Cargo.lock @@ -0,0 +1,2162 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "winnow 0.7.13", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "battery-service" +version = "0.1.0" +dependencies = [ + "battery-service-messages", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-batteries-async", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-services", + "log", + "mctp-rs", + "zerocopy", +] + +[[package]] +name = "battery-service-messages" +version = "0.1.0" +dependencies = [ + "embedded-batteries-async", + "embedded-services", + "num_enum", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "unty", +] + +[[package]] +name = "bit-register" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" +dependencies = [ + "num-traits", +] + +[[package]] +name = "bit-register" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/odp-utilities#583015c08ad9855f310bdb25d5cf9abff77b5e08" +dependencies = [ + "num-traits", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" + +[[package]] +name = "bitfield" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" +dependencies = [ + "bitfield-macros", +] + +[[package]] +name = "bitfield-macros" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitfield-struct" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bq40z50-rx" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" +dependencies = [ + "device-driver", + "embassy-time", + "embedded-batteries-async", + "embedded-hal 1.0.0", + "embedded-hal-async", + "smbus-pec", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "dd-manifest-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" +dependencies = [ + "yaml-rust2", +] + +[[package]] +name = "device-driver" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" +dependencies = [ + "device-driver-macros", + "embedded-io 0.6.1", + "embedded-io-async", +] + +[[package]] +name = "device-driver-generation" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3935aec9cf5bb2ab927f59ca69faecf976190390b0ce34c6023889e9041040c0" +dependencies = [ + "anyhow", + "askama", + "bitvec", + "convert_case", + "dd-manifest-tree", + "itertools 0.14.0", + "kdl", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "device-driver-macros" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fdc68ed515c4eddff2e95371185b4becba066085bf36d50f07f09782af98e17" +dependencies = [ + "device-driver-generation", + "proc-macro2", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-sync" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-core", + "futures-sink", + "heapless 0.8.0", + "log", +] + +[[package]] +name = "embassy-time" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embassy-time-queue-utils", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", + "log", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" +dependencies = [ + "embassy-executor-timer-queue", + "heapless 0.8.0", +] + +[[package]] +name = "embedded-batteries" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" +dependencies = [ + "bitfield-struct", + "bitflags", + "embedded-hal 1.0.0", + "zerocopy", +] + +[[package]] +name = "embedded-batteries-async" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" +dependencies = [ + "bitfield-struct", + "embedded-batteries", + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-cfu-protocol" +version = "0.2.0" +source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" +dependencies = [ + "embedded-io-async", + "log", +] + +[[package]] +name = "embedded-crc-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-services" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "bitflags", + "bitvec", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section", + "document-features", + "embassy-sync", + "embassy-time", + "embedded-batteries-async", + "embedded-cfu-protocol", + "embedded-hal-async", + "embedded-io 0.6.1", + "embedded-io-async", + "embedded-usb-pd", + "heapless 0.8.0", + "log", + "mctp-rs", + "num_enum", + "portable-atomic", + "serde", + "uuid", +] + +[[package]] +name = "embedded-usb-pd" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#7b618f5689cef191171d81d33a2fa6b5af46d33f" +dependencies = [ + "aquamarine", + "bincode", + "bitfield 0.19.4", + "embedded-hal-async", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "espi-device" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +dependencies = [ + "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bitflags", + "num-traits", + "num_enum", + "static_assertions", + "subenum", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version 0.4.1", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "kdl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a29e7b50079ff44549f68c0becb1c73d7f6de2a4ea952da77966daf3d4761e" +dependencies = [ + "miette", + "num", + "winnow 0.6.24", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "maitake-sync" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5" +dependencies = [ + "cordyceps", + "loom", + "mycelium-bitfield", + "pin-project", + "portable-atomic", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "mctp-rs" +version = "0.1.0" +source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +dependencies = [ + "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "espi-device", + "num_enum", + "smbus-pec", + "thiserror", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "unicode-width", +] + +[[package]] +name = "mycelium-bitfield" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nusb" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f861541f15de120eae5982923d073bfc0c1a65466561988c82d6e197734c19e" +dependencies = [ + "atomic-waker", + "core-foundation", + "core-foundation-sys", + "futures-core", + "io-kit-sys", + "libc", + "log", + "once_cell", + "rustix", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pico-de-gallo" +version = "0.1.0" +dependencies = [ + "battery-service", + "bq40z50-rx", + "critical-section", + "embassy-futures", + "embassy-time", + "embassy-time-driver", + "embedded-batteries-async", + "embedded-hal-async", + "embedded-services", + "env_logger", + "log", + "pico-de-gallo-hal", + "static_cell", + "tokio", +] + +[[package]] +name = "pico-de-gallo-hal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc711fd7d8c333ac344e434a61a90a9e9076cc7c9fdb9d7d790a4425a9187c0a" +dependencies = [ + "embedded-hal 1.0.0", + "embedded-hal-async", + "pico-de-gallo-lib", + "tokio", +] + +[[package]] +name = "pico-de-gallo-internal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5f5d62ccfc4edc551e404b9c7eb3a6cbeeca1aadb893983667b549ad76a7a0" +dependencies = [ + "postcard-rpc", + "postcard-schema", + "serde", +] + +[[package]] +name = "pico-de-gallo-lib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e874d5446bd937d6f45cd34711f8e179c122c7eb1e5768e09d2575e6cac09da0" +dependencies = [ + "embedded-hal 1.0.0", + "nusb", + "pico-de-gallo-internal", + "postcard-rpc", + "tokio", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless 0.7.17", + "serde", +] + +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "postcard-rpc" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e1944dfb9859e440511700c442edce3eacd5862f90f5a9997d004bd3553f3b" +dependencies = [ + "heapless 0.8.0", + "maitake-sync", + "nusb", + "portable-atomic", + "postcard", + "postcard-schema", + "serde", + "ssmarshal", + "thiserror", + "tokio", + "tracing", + "trait-variant", +] + +[[package]] +name = "postcard-schema" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475666d89f42231a0a57da32d5f6ca7f9b5cd4c335ea1fe8f3278215b7a21ff" +dependencies = [ + "postcard-derive", + "serde", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smbus-pec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" +dependencies = [ + "embedded-crc-macros", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "static_cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "subenum" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3d08fe7078c57309d5c3d938e50eba95ba1d33b9c3a101a8465fc6861a5416" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yaml-rust2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/examples/pico-de-gallo/Cargo.toml b/examples/pico-de-gallo/Cargo.toml new file mode 100644 index 000000000..2528ce370 --- /dev/null +++ b/examples/pico-de-gallo/Cargo.toml @@ -0,0 +1,41 @@ + +[workspace] + +[package] +name = "pico-de-gallo" +version = "0.1.0" +edition = "2024" + +[workspace.lints.rust] +warnings = "deny" + +[lints] +workspace = true + +[dependencies] +tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros"] } +embedded-services = { path = "../../embedded-service", features = ["log"] } +embassy-time = { version = "0.5.0", features = [ + "log", + "std", + "generic-queue-8", +] } +embassy-time-driver = { version = "0.2.1", optional = true } +embassy-futures = "0.1.2" + +embedded-batteries-async = "0.3" +battery-service = { path = "../../battery-service", features = ["log"] } + +pico-de-gallo-hal = "0.1.0" + +env_logger = "0.11" +log = "0.4.14" +static_cell = "2" +embedded-hal-async = "1.0.0" + +critical-section = { version = "1.1", features = ["std"] } +bq40z50-rx = { version = "0.8", features = ["r5", "embassy-timeout"] } + +# Needed otherwise cargo will pull from git +[patch."https://github.com/OpenDevicePartnership/embedded-services"] +embedded-services = { path = "../../embedded-service" } diff --git a/examples/pico-de-gallo/src/bin/battery.rs b/examples/pico-de-gallo/src/bin/battery.rs new file mode 100644 index 000000000..64ee1f39c --- /dev/null +++ b/examples/pico-de-gallo/src/bin/battery.rs @@ -0,0 +1,298 @@ +//! Pico-de-gallo battery example +//! +//! Runs the ODP battery service in a std environment, using the [pico-de-gallo](https://github.com/OpenDevicePartnership/pico-de-gallo) +//! as a sensor bridge to a [Texas Instruments BQ40Z50-R5 battery fuel gauge EVK](https://github.com/OpenDevicePartnership/bq40z50). +//! +//! The hardware setup should be as follows: +//! +//! ___________ ___________ ____________ +//! | | | pico- | <--SDA--> | BQ40Z50-R5 | +//! | Host PC | <-USB--> | de-gallo | <--SCL--> | Fuel Gauge | +//! |___________| |___________| <--GND--> |____________| +//! +//! The host PC running the battery-service should be connected via USB to the pico-de-gallo. The BQ40Z50-R5 EVK should be connected +//! to the pico-de-gallo's I2C lines (don't forget GND!). The BQ40Z50-R5 EVK should be connected to the appropriate power supply and +//! battery cells, as outlined in its datasheet. +//! +//! The example can be run simply by typing `cargo run --bin battery` + +use battery_service as bs; +use bq40z50_rx::{BQ40Z50Error, Bq40z50R5}; +use embedded_batteries_async::smart_battery::{BatteryModeFields, SmartBattery}; +use static_cell::StaticCell; + +/// Platform specific battery errors. +#[derive(Debug)] +enum BatteryError { + /// Generic failure + Failed, +} + +impl embedded_batteries_async::smart_battery::Error for BatteryError { + fn kind(&self) -> embedded_batteries_async::smart_battery::ErrorKind { + embedded_batteries_async::smart_battery::ErrorKind::Other + } +} + +impl From> for BatteryError { + fn from(_value: BQ40Z50Error) -> Self { + BatteryError::Failed + } +} + +/// Platform specific battery controller. +struct Battery { + pub driver: Bq40z50R5, +} + +embedded_batteries_async::impl_smart_battery_for_wrapper_type!(Battery, driver, BatteryError); + +impl bs::controller::Controller for Battery { + type ControllerError = BatteryError; + + async fn initialize(&mut self) -> Result<(), Self::ControllerError> { + self.driver + // Milliamps + .set_battery_mode(BatteryModeFields::with_capacity_mode(BatteryModeFields::new(), false)) + .await + .inspect_err(|_| embedded_services::error!("FG: failed to initialize"))?; + + embedded_services::info!("FG: initialized"); + Ok(()) + } + + async fn get_static_data(&mut self) -> Result { + let mut buf = [0u8; 21]; + let mut new_msgs = bs::device::StaticBatteryMsgs { + design_capacity_mwh: match self.design_capacity().await? { + embedded_batteries_async::smart_battery::CapacityModeValue::CentiWattUnsigned(_) => 0xDEADBEEF, + embedded_batteries_async::smart_battery::CapacityModeValue::MilliAmpUnsigned(design_capacity) => { + design_capacity.into() + } + }, + design_voltage_mv: self.design_voltage().await?, + ..Default::default() + }; + + let buf_len = new_msgs.device_chemistry.len(); + self.device_chemistry(&mut buf[..buf_len]).await?; + new_msgs.device_chemistry.copy_from_slice(&buf[..buf_len]); + + Ok(new_msgs) + } + + async fn get_dynamic_data(&mut self) -> Result { + let new_msgs = bs::device::DynamicBatteryMsgs { + average_current_ma: self.average_current().await?, + battery_status: self.battery_status().await?.into(), + max_power_mw: self + .driver + .device + .max_turbo_power() + .read_async() + .await? + .max_turbo_power() + .unsigned_abs() + .into(), + battery_temp_dk: self.temperature().await?, + sus_power_mw: self + .driver + .device + .sus_turbo_power() + .read_async() + .await? + .sus_turbo_power() + .unsigned_abs() + .into(), + charging_current_ma: self.charging_current().await?, + charging_voltage_mv: self.charging_voltage().await?, + voltage_mv: self.voltage().await?, + current_ma: self.current().await?, + full_charge_capacity_mwh: match self.full_charge_capacity().await? { + embedded_batteries_async::smart_battery::CapacityModeValue::CentiWattUnsigned(_) => 0xDEADBEEF, + embedded_batteries_async::smart_battery::CapacityModeValue::MilliAmpUnsigned(capacity) => { + capacity.into() + } + }, + remaining_capacity_mwh: match self.remaining_capacity().await? { + embedded_batteries_async::smart_battery::CapacityModeValue::CentiWattUnsigned(_) => 0xDEADBEEF, + embedded_batteries_async::smart_battery::CapacityModeValue::MilliAmpUnsigned(capacity) => { + capacity.into() + } + }, + relative_soc_pct: self.relative_state_of_charge().await?.into(), + cycle_count: self.cycle_count().await?, + max_error_pct: self.max_error().await?.into(), + bmd_status: embedded_batteries_async::acpi::BmdStatusFlags::default(), + turbo_vload_mv: 0, + turbo_rhf_effective_mohm: 0, + }; + Ok(new_msgs) + } + + async fn get_device_event(&mut self) -> bs::controller::ControllerEvent { + loop { + tokio::task::yield_now().await; + } + } + + async fn ping(&mut self) -> Result<(), Self::ControllerError> { + if let Err(e) = self.driver.voltage().await { + embedded_services::error!("FG: failed to ping"); + Err(e.into()) + } else { + embedded_services::info!("FG: ping success"); + Ok(()) + } + } + + fn set_timeout(&mut self, _duration: embassy_time::Duration) { + embassy_time::Duration::from_secs(60); + } +} + +async fn init_and_run_service(i2c: pico_de_gallo_hal::I2c, delay: pico_de_gallo_hal::Delay) -> ! { + embedded_services::debug!("Initializing battery service"); + embedded_services::init().await; + + static BATTERY_DEVICE: StaticCell = StaticCell::new(); + static BATTERY_WRAPPER: StaticCell> = StaticCell::new(); + let device = BATTERY_DEVICE.init(bs::device::Device::new(bs::device::DeviceId(0))); + + battery_service::register_fuel_gauge(device).expect("Failed to register fuel gauge"); + + let wrapper = BATTERY_WRAPPER.init(bs::wrapper::Wrapper::new( + device, + Battery { + driver: Bq40z50R5::new(i2c, delay), + }, + )); + + // Run battery service + let _ = embassy_futures::join::join( + tokio::spawn(battery_service::task::task()), + tokio::spawn(wrapper.process()), + ) + .await; + unreachable!() +} + +async fn init_state_machine() -> Result<(), bs::context::ContextError> { + battery_service::execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::DoInit, + device_id: bs::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge init error: {:?}", f))?; + + battery_service::execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollStaticData, + device_id: bs::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge static data error: {:?}", f))?; + + battery_service::execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollDynamicData, + device_id: bs::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge dynamic data error: {:?}", f))?; + + Ok(()) +} + +async fn recover_state_machine() -> Result<(), ()> { + loop { + match battery_service::execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::Timeout, + device_id: bs::device::DeviceId(0), + }) + .await + { + Ok(_) => { + embedded_services::info!("FG recovered!"); + return Ok(()); + } + Err(e) => match e { + battery_service::context::ContextError::StateError(e) => match e { + battery_service::context::StateMachineError::DeviceTimeout => { + embedded_services::trace!("Recovery failed, trying again after a backoff period"); + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + } + battery_service::context::StateMachineError::NoOpRecoveryFailed => { + embedded_services::error!("Couldn't recover, reinit needed"); + return Err(()); + } + _ => embedded_services::debug!("Unexpected error"), + }, + _ => embedded_services::debug!("Unexpected error"), + }, + } + } +} + +pub async fn run_app() { + // Initialize battery state machine. + let mut retries = 5; + while let Err(e) = init_state_machine().await { + retries -= 1; + if retries <= 0 { + embedded_services::error!("Failed to initialize Battery: {:?}", e); + return; + } + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + let mut failures: u32 = 0; + let mut count: usize = 1; + loop { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + if count.is_multiple_of(const { 60 * 60 * 60 }) { + if let Err(e) = battery_service::execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollStaticData, + device_id: bs::device::DeviceId(0), + }) + .await + { + failures += 1; + embedded_services::error!("Fuel gauge static data error: {:#?}", e); + } + } + if let Err(e) = battery_service::execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollDynamicData, + device_id: bs::device::DeviceId(0), + }) + .await + { + failures += 1; + embedded_services::error!("Fuel gauge dynamic data error: {:#?}", e); + } + + if failures > 10 { + failures = 0; + count = 0; + embedded_services::error!("FG: Too many errors, timing out and starting recovery..."); + if recover_state_machine().await.is_err() { + embedded_services::error!("FG: Fatal error"); + return; + } + } + + count = count.wrapping_add(1); + } +} + +#[tokio::main] +async fn main() { + env_logger::builder().filter_level(log::LevelFilter::Info).init(); + embedded_services::info!("host: battery example started"); + + let p = pico_de_gallo_hal::Hal::new(); + + let _ = embassy_futures::join::join( + tokio::spawn(run_app()), + tokio::spawn(init_and_run_service(p.i2c(), p.delay())), + ) + .await; +} diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 2f02deefa..0695fa9b0 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -107,7 +107,7 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", @@ -120,7 +120,7 @@ name = "battery-service-messages" version = "0.1.0" dependencies = [ "defmt 0.3.100", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-services", "num_enum", ] @@ -191,6 +191,17 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "bitfield-struct" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -221,21 +232,22 @@ version = "0.1.0" source = "git+https://github.com/OpenDevicePartnership/bq25773#20bc26219b5372bc6146cdc509c21f6d43e257b3" dependencies = [ "device-driver", - "embedded-batteries-async", + "embedded-batteries-async 0.2.1", "embedded-hal 1.0.0", "embedded-hal-async", ] [[package]] name = "bq40z50-rx" -version = "0.1.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6490641e82ed3770614a0f2a301fc26422ee83b52eb7d2802805e41f471be318" +checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" dependencies = [ "device-driver", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-hal 1.0.0", "embedded-hal-async", + "smbus-pec", ] [[package]] @@ -631,7 +643,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", + "bitflags 2.9.1", + "defmt 0.3.100", + "embedded-hal 1.0.0", + "zerocopy", +] + +[[package]] +name = "embedded-batteries" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8996d7168535579180a0eead82efaba718ebd598782f986bfd635458259df2" +dependencies = [ + "bitfield-struct 0.10.1", "bitflags 2.9.1", "defmt 0.3.100", "embedded-hal 1.0.0", @@ -644,9 +669,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", + "embedded-batteries 0.2.1", + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-batteries-async" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" +dependencies = [ + "bitfield-struct 0.12.1", "defmt 0.3.100", - "embedded-batteries", + "embedded-batteries 0.3.1", "embedded-hal 1.0.0", ] @@ -739,7 +775,7 @@ dependencies = [ "document-features", "embassy-sync", "embassy-time", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-cfu-protocol", "embedded-hal-async", "embedded-io", @@ -1056,7 +1092,7 @@ source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271 dependencies = [ "bit-register", "defmt 0.3.100", - "embedded-batteries", + "embedded-batteries 0.2.1", "espi-device", "num_enum", "smbus-pec", @@ -1365,7 +1401,7 @@ dependencies = [ "embassy-imxrt", "embassy-sync", "embassy-time", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-hal-async", "embedded-services", "espi-service", diff --git a/examples/rt633/Cargo.toml b/examples/rt633/Cargo.toml index 1212762c4..64a312b82 100644 --- a/examples/rt633/Cargo.toml +++ b/examples/rt633/Cargo.toml @@ -29,9 +29,7 @@ embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" "unstable-pac", ] } -embassy-sync = { version = "0.7.2", features = [ - "defmt", -] } +embassy-sync = { version = "0.7.2", features = ["defmt"] } embassy-executor = { version = "0.9.1", features = [ "arch-cortex-m", "executor-thread", @@ -52,10 +50,10 @@ rand = { version = "0.8.5", default-features = false } espi-service = { path = "../../espi-service", features = ["defmt"] } embedded-services = { path = "../../embedded-service", features = ["defmt"] } -embedded-batteries-async = { version = "0.2.0", features = ["defmt"] } +embedded-batteries-async = { version = "0.3", features = ["defmt"] } bq25773 = { git = "https://github.com/OpenDevicePartnership/bq25773" } battery-service = { path = "../../battery-service", features = ["defmt"] } -bq40z50-rx = { version = "0.1", features = ["r5"] } +bq40z50-rx = { version = "0.8", features = ["r5"] } static_cell = "2.1.0" embassy-embedded-hal = { version = "0.5.0", default-features = false } diff --git a/examples/rt633/src/bin/espi_battery.rs b/examples/rt633/src/bin/espi_battery.rs index 081ef0a02..e19f16441 100644 --- a/examples/rt633/src/bin/espi_battery.rs +++ b/examples/rt633/src/bin/espi_battery.rs @@ -7,17 +7,13 @@ use battery_service::context::BatteryEvent; use core::slice::{self}; use embassy_imxrt::dma::NoDma; use embassy_time::{Duration, Timer}; -use embedded_batteries_async::charger::MilliAmps; -use embedded_batteries_async::smart_battery::{ - BatteryModeFields, BatteryStatusFields, CapacityModeSignedValue, CapacityModeValue, Cycles, DeciKelvin, - ManufactureDate, MilliAmpsSigned, MilliVolts, Minutes, Percent, SmartBattery, SpecificationInfoFields, -}; +use embedded_batteries_async::smart_battery::SmartBattery; use embedded_services::{error, info}; use battery_service::controller::{Controller, ControllerEvent}; use battery_service::device::{Device, DeviceId, DynamicBatteryMsgs, StaticBatteryMsgs}; use battery_service::wrapper::Wrapper; -use bq40z50_rx::Bq40z50; +use bq40z50_rx::Bq40z50R5; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_executor::Spawner; use embassy_imxrt::bind_interrupts; @@ -42,125 +38,16 @@ static FG_DEVICE: StaticCell = StaticCell::new(); /// Wrapper struct for the fuel gauge driver struct Bq40z50Controller { - driver: Bq40z50< + driver: Bq40z50R5< I2cDevice<'static, NoopRawMutex, embassy_imxrt::i2c::master::I2cMaster<'static, embassy_imxrt::i2c::Async>>, + embassy_time::Delay, >, } -impl embedded_batteries_async::smart_battery::ErrorType for Bq40z50Controller { - type Error = >> as embedded_batteries_async::smart_battery::ErrorType>::Error; -} - -impl embedded_batteries_async::smart_battery::SmartBattery for Bq40z50Controller { - async fn absolute_state_of_charge(&mut self) -> Result { - self.driver.absolute_state_of_charge().await - } - async fn at_rate(&mut self) -> Result { - self.driver.at_rate().await - } - async fn at_rate_ok(&mut self) -> Result { - self.driver.at_rate_ok().await - } - async fn at_rate_time_to_empty(&mut self) -> Result { - self.driver.at_rate_time_to_empty().await - } - async fn at_rate_time_to_full(&mut self) -> Result { - self.driver.at_rate_time_to_full().await - } - async fn average_current(&mut self) -> Result { - self.driver.average_current().await - } - async fn average_time_to_empty(&mut self) -> Result { - self.driver.average_time_to_empty().await - } - async fn average_time_to_full(&mut self) -> Result { - self.driver.average_time_to_full().await - } - async fn battery_mode(&mut self) -> Result { - self.driver.battery_mode().await - } - async fn battery_status(&mut self) -> Result { - self.driver.battery_status().await - } - async fn current(&mut self) -> Result { - self.driver.current().await - } - async fn cycle_count(&mut self) -> Result { - self.driver.cycle_count().await - } - async fn design_capacity(&mut self) -> Result { - self.driver.design_capacity().await - } - async fn design_voltage(&mut self) -> Result { - self.driver.design_voltage().await - } - async fn device_chemistry(&mut self, chemistry: &mut [u8]) -> Result<(), Self::Error> { - self.driver.device_chemistry(chemistry).await - } - async fn device_name(&mut self, name: &mut [u8]) -> Result<(), Self::Error> { - self.driver.device_name(name).await - } - async fn full_charge_capacity(&mut self) -> Result { - self.driver.full_charge_capacity().await - } - async fn manufacture_date(&mut self) -> Result { - self.driver.manufacture_date().await - } - async fn manufacturer_name(&mut self, name: &mut [u8]) -> Result<(), Self::Error> { - self.driver.manufacturer_name(name).await - } - async fn max_error(&mut self) -> Result { - self.driver.max_error().await - } - async fn relative_state_of_charge(&mut self) -> Result { - self.driver.relative_state_of_charge().await - } - async fn remaining_capacity(&mut self) -> Result { - self.driver.remaining_capacity().await - } - async fn remaining_capacity_alarm(&mut self) -> Result { - self.driver.remaining_capacity_alarm().await - } - async fn remaining_time_alarm(&mut self) -> Result { - self.driver.remaining_time_alarm().await - } - async fn run_time_to_empty(&mut self) -> Result { - self.driver.run_time_to_empty().await - } - async fn serial_number(&mut self) -> Result { - self.driver.serial_number().await - } - async fn set_at_rate(&mut self, rate: CapacityModeSignedValue) -> Result<(), Self::Error> { - self.driver.set_at_rate(rate).await - } - async fn set_battery_mode(&mut self, flags: BatteryModeFields) -> Result<(), Self::Error> { - self.driver.set_battery_mode(flags).await - } - async fn set_remaining_capacity_alarm(&mut self, capacity: CapacityModeValue) -> Result<(), Self::Error> { - self.driver.set_remaining_capacity_alarm(capacity).await - } - async fn set_remaining_time_alarm(&mut self, time: Minutes) -> Result<(), Self::Error> { - self.driver.set_remaining_time_alarm(time).await - } - async fn specification_info(&mut self) -> Result { - self.driver.specification_info().await - } - async fn temperature(&mut self) -> Result { - self.driver.temperature().await - } - async fn voltage(&mut self) -> Result { - self.driver.voltage().await - } - async fn charging_current(&mut self) -> Result { - self.driver.charging_current().await - } - async fn charging_voltage(&mut self) -> Result { - self.driver.charging_voltage().await - } -} +embedded_batteries_async::impl_smart_battery_for_wrapper_type!(Bq40z50Controller, driver, >, embassy_time::Delay> as embedded_batteries_async::smart_battery::ErrorType>::Error); impl Controller for Bq40z50Controller { - type ControllerError = >> as embedded_batteries_async::smart_battery::ErrorType>::Error; + type ControllerError = >, embassy_time::Delay> as embedded_batteries_async::smart_battery::ErrorType>::Error; async fn initialize(&mut self) -> Result<(), Self::ControllerError> { info!("Fuel gauge inited!"); @@ -328,7 +215,7 @@ async fn main(spawner: Spawner) { let wrap = Wrapper::new( fg, Bq40z50Controller { - driver: Bq40z50::new(fg_bus), + driver: Bq40z50R5::new(fg_bus, embassy_time::Delay), }, ); diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 8a3945ed4..6a4f7f14c 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -606,14 +606,26 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "embedded-batteries" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8996d7168535579180a0eead82efaba718ebd598782f986bfd635458259df2" +dependencies = [ + "bitfield-struct", + "bitflags 2.9.4", + "embedded-hal 1.0.0", + "zerocopy", +] + [[package]] name = "embedded-batteries-async" -version = "0.2.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" +checksum = "6465f32edac01ccd4d55931896177b3a61a5286205c5269f30b91ce7dbf51b5e" dependencies = [ "bitfield-struct", - "embedded-batteries", + "embedded-batteries 0.3.1", "embedded-hal 1.0.0", ] @@ -1005,7 +1017,7 @@ source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271 dependencies = [ "bit-register", "defmt 0.3.100", - "embedded-batteries", + "embedded-batteries 0.2.1", "espi-device", "num_enum", "smbus-pec", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index b2ee67381..818a34e42 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "bitfield-struct" -version = "0.10.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" dependencies = [ "proc-macro2", "quote", @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "embedded-batteries" -version = "0.2.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" +checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" dependencies = [ "bitfield-struct", "bitflags 2.9.4", @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "embedded-batteries-async" -version = "0.2.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" +checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" dependencies = [ "bitfield-struct", "embedded-batteries", diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 39ffeb396..d1b298b00 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -32,7 +32,7 @@ power-policy-service = { path = "../../power-policy-service", features = [ cfu-service = { path = "../../cfu-service", features = ["log"] } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } -embedded-batteries-async = "0.2.0" +embedded-batteries-async = "0.3" battery-service = { path = "../../battery-service", features = ["log"] } type-c-service = { path = "../../type-c-service", features = ["log"] } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index b268a971d..201934906 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -26,6 +26,12 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "0.3.75" +[[audits.bitfield-struct]] +who = "matteotullo " +criteria = "safe-to-deploy" +delta = "0.10.1 -> 0.12.1" +notes = "Adds hash and bitenum derives, mostly parsing and refactoring changes. No code execution nor writing to the filesystem." + [[audits.block-device-driver]] who = "jerrysxie " criteria = "safe-to-deploy" @@ -54,6 +60,24 @@ criteria = "safe-to-deploy" version = "0.2.1" notes = "ODP crates are always trusted." +[[audits.embedded-batteries]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "ODP crates are always trusted." + +[[audits.embedded-batteries]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "ODP crates are always trusted." + +[[audits.embedded-batteries]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "0.3.4" +notes = "ODP crates are always trusted." + [[audits.embedded-batteries-async]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -71,6 +95,29 @@ who = "Matteo Tullo " criteria = "safe-to-deploy" version = "0.2.1" +[[audits.embedded-batteries-async]] +who = "Matteo Tullo " +criteria = "safe-to-deploy" +version = "0.3.0" + +[[audits.embedded-batteries-async]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "0.3.0" +notes = "ODP crates are always trusted." + +[[audits.embedded-batteries-async]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "ODP crates are always trusted." + +[[audits.embedded-batteries-async]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "0.3.4" +notes = "ODP crates are always trusted." + [[audits.embedded-crc-macros]] who = "Matteo Tullo " criteria = "safe-to-deploy" @@ -509,7 +556,7 @@ end = "2026-09-03" [[trusted.smallvec]] criteria = "safe-to-deploy" -user-id = 2017 # Matt Brubeck (mbrubeck) +user-id = 2017 start = "2019-10-28" end = "2026-09-03" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index fb6528a0c..6c5db299f 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -69,13 +69,6 @@ user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" -[[publisher.rustversion]] -version = "1.0.22" -when = "2025-08-08" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - [[publisher.ryu]] version = "1.0.20" when = "2025-03-04" @@ -97,13 +90,6 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" -[[publisher.smallvec]] -version = "1.15.1" -when = "2025-06-06" -user-id = 2017 -user-login = "mbrubeck" -user-name = "Matt Brubeck" - [[publisher.syn]] version = "1.0.109" when = "2023-02-24" @@ -202,13 +188,6 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[[publisher.windows-link]] -version = "0.1.3" -when = "2025-06-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - [[publisher.windows-numerics]] version = "0.2.0" when = "2025-03-18" @@ -498,6 +477,12 @@ criteria = "safe-to-deploy" version = "1.1.0" notes = "Only minor `unsafe` code blocks which look valid and otherwise does what it says on the tin." +[[audits.bytecode-alliance.audits.smallvec]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "1.13.2 -> 1.14.0" +notes = "Minor new feature, nothing out of the ordinary." + [[audits.bytecode-alliance.audits.static_assertions]] who = "Andrew Brown " criteria = "safe-to-deploy" @@ -887,6 +872,74 @@ delta = "0.4.0 -> 0.4.1" notes = "No unsafe, net or fs." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.rustversion]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.0.14" +notes = """ +Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'``, `'\bnet\b'``, `'\bunsafe\b'`` +and there were no hits except for: + +* Using trivially-safe `unsafe` in test code: + + ``` + tests/test_const.rs:unsafe fn _unsafe() {} + tests/test_const.rs:const _UNSAFE: () = unsafe { _unsafe() }; + ``` + +* Using `unsafe` in a string: + + ``` + src/constfn.rs: "unsafe" => Qualifiers::Unsafe, + ``` + +* Using `std::fs` in `build/build.rs` to write `${OUT_DIR}/version.expr` + which is later read back via `include!` used in `src/lib.rs`. + +Version `1.0.6` of this crate has been added to Chromium in +https://source.chromium.org/chromium/chromium/src/+/28841c33c77833cc30b286f9ae24c97e7a8f4057 +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.rustversion]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.0.14 -> 1.0.15" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.rustversion]] +who = "danakj " +criteria = "safe-to-deploy" +delta = "1.0.15 -> 1.0.16" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.rustversion]] +who = "Dustin J. Mitchell " +criteria = "safe-to-deploy" +delta = "1.0.16 -> 1.0.17" +notes = "Just updates windows compat" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.rustversion]] +who = "Liza Burakova " +criteria = "safe-to-deploy" +delta = "1.0.17 -> 1.0.18" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.rustversion]] +who = "Dustin J. Mitchell " +criteria = "safe-to-deploy" +delta = "1.0.18 -> 1.0.19" +notes = "No unsafe, just doc changes" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.rustversion]] +who = "Daniel Cheng " +criteria = "safe-to-deploy" +delta = "1.0.19 -> 1.0.20" +notes = "Only minor updates to documentation and the mock today used for testing." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.semver]] who = "Daniel Cheng " criteria = "safe-to-run" @@ -1129,6 +1182,12 @@ delta = "1.0.218 -> 1.0.219" notes = "Minor changes (clippy tweaks, using `mem::take` instead of `mem::replace`)." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.smallvec]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +version = "1.13.2" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.stable_deref_trait]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -1427,6 +1486,12 @@ criteria = "safe-to-deploy" delta = "1.1.0 -> 1.3.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.smallvec]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.14.0 -> 1.15.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.strsim]] who = "Ben Dean-Kawamura " criteria = "safe-to-deploy" @@ -1492,6 +1557,13 @@ criteria = "safe-to-deploy" delta = "0.3.19 -> 0.3.20" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.windows-link]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "A microsoft crate allowing unsafe calls to windows apis." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.zcash.audits.adler2]] who = "Jack Grigg " criteria = "safe-to-deploy" @@ -1515,6 +1587,20 @@ was being selected by the target OS instead of the host OS. """ aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.rustversion]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "1.0.20 -> 1.0.21" +notes = "Build script change is to fix building with `-Zfmt-debug=none`." +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.rustversion]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "1.0.21 -> 1.0.22" +notes = "Changes to generated code are to prepend a clippy annotation." +aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" + [[audits.zcash.audits.thread_local]] who = "Jack Grigg " criteria = "safe-to-deploy" @@ -1548,3 +1634,9 @@ criteria = "safe-to-deploy" delta = "0.1.0 -> 0.1.1" notes = "Build script changes are for linting." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.windows-link]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.1.1 -> 0.1.3" +aggregated-from = "https://raw.githubusercontent.com/zcash/librustzcash/main/supply-chain/audits.toml" diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index a0d075193..fcd7b81e1 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -80,7 +80,10 @@ pub async fn init() -> Result<(), Error> { /// Used to send messages to other services from the Thermal service, /// such as notifying the Host of thresholds crossed or the Power service if CRT TEMP is reached. -pub async fn send_service_msg(to: comms::EndpointID, data: &impl embedded_services::Any) -> Result<(), Error> { +pub async fn send_service_msg( + to: comms::EndpointID, + data: &(impl embedded_services::Any + Send + Sync), +) -> Result<(), Error> { // TODO: When this gets updated to return error, handle retrying send on failure SERVICE.get().await.endpoint.send(to, data).await.map_err(|_| Error)?; Ok(()) From b21b275ca360a869677100e9f991566144060437 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Tue, 3 Feb 2026 10:43:12 -0800 Subject: [PATCH 13/79] Battery service refactor (#698) - Remove static `SERVICE` in battery service - Replace battery service free functions with methods on `Service` - Add battery `Device` as an argument to `battery_service::task()` to ensure all devices are registered before the state machine starts - Update examples --- Cargo.lock | 1 + battery-service/Cargo.toml | 1 + battery-service/src/context.rs | 19 ++-- battery-service/src/lib.rs | 92 ++++++++++--------- battery-service/src/task.rs | 35 ++++++-- examples/pico-de-gallo/Cargo.lock | 1 + examples/pico-de-gallo/src/bin/battery.rs | 102 ++++++++++++---------- examples/rt633/Cargo.lock | 1 + examples/rt633/src/bin/espi_battery.rs | 55 +++++++----- examples/std/Cargo.lock | 1 + examples/std/src/bin/battery.rs | 50 +++++------ 11 files changed, 203 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcab087b6..538958eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,6 +168,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", + "heapless", "log", "mctp-rs", "zerocopy", diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 2e12930a7..87d36baf8 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -23,6 +23,7 @@ embedded-services.workspace = true log = { workspace = true, optional = true } zerocopy.workspace = true mctp-rs = { workspace = true, features = ["espi"] } +heapless.workspace = true [features] default = [] diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index 73edb63d0..b43a03fea 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -402,8 +402,11 @@ impl Context { } } - pub(super) async fn process_acpi_cmd(&self, acpi_msg: &AcpiBatteryRequest) { - let response: Result = match *acpi_msg { + pub(super) async fn process_acpi_cmd( + &self, + acpi_msg: &AcpiBatteryRequest, + ) -> Result { + match *acpi_msg { AcpiBatteryRequest::BatteryGetBixRequest { battery_id } => self.bix_handler(DeviceId(battery_id)).await, AcpiBatteryRequest::BatteryGetBstRequest { battery_id } => self.bst_handler(DeviceId(battery_id)).await, AcpiBatteryRequest::BatteryGetPsrRequest { battery_id } => self.psr_handler(DeviceId(battery_id)).await, @@ -434,19 +437,7 @@ impl Context { self.bma_handler(DeviceId(battery_id), bma).await } AcpiBatteryRequest::BatteryGetStaRequest { battery_id } => self.sta_handler(DeviceId(battery_id)).await, - }; - - if let Err(e) = response { - error!("Battery service command failed: {:?}", e); } - - // TODO We should probably be responding to the requestor rather than just assuming the request came from the host - super::comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &response, - ) - .await - .expect("comms_send is infallible"); } pub(crate) fn get_fuel_gauge(&self, id: DeviceId) -> Option<&'static Device> { diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index 6a94e11a1..993f0ff86 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -7,7 +7,7 @@ use context::BatteryEvent; use embassy_futures::select::select; use embedded_services::{ comms::{self, EndpointID}, - trace, + error, trace, }; mod acpi; @@ -30,7 +30,7 @@ impl Service { } /// Create a new battery service instance with context configuration. - pub fn new_with_ctx_config(config: context::Config) -> Self { + pub const fn new_with_ctx_config(config: context::Config) -> Self { Self::new_inner(config) } @@ -64,10 +64,59 @@ impl Service { } Event::AcpiRequest(acpi_msg) => { trace!("Battery service: ACPI cmd recvd"); - self.context.process_acpi_cmd(&acpi_msg).await + match self.context.process_acpi_cmd(&acpi_msg).await { + Ok(response) => { + // TODO We should probably be responding to the requestor rather than just assuming the request came from the host + self.comms_send( + crate::EndpointID::External(embedded_services::comms::External::Host), + &response, + ) + .await + .expect("comms_send is infallible") + } + Err(e) => error!("Battery service command failed: {:?}", e), + } } } } + + /// Register fuel gauge device with the battery service. + /// + /// Must be done before sending the battery service commands so that hardware device is visible + /// to the battery service. + pub(crate) fn register_fuel_gauge( + &self, + device: &'static device::Device, + ) -> Result<(), embedded_services::intrusive_list::Error> { + self.context.register_fuel_gauge(device)?; + + Ok(()) + } + + /// Use the battery service endpoint to send data to other subsystems and services. + pub async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { + self.endpoint.send(endpoint_id, data).await + } + + /// Send the battery service state machine an event and await a response. + /// + /// This is an alternative method of interacting with the battery service (instead of using the comms service), + /// and is a useful fn if you want to send an event and await a response sequentially. + pub async fn execute_event(&self, event: BatteryEvent) -> context::BatteryResponse { + self.context.execute_event(event).await + } + + /// Wait for a response from the battery service. + /// + /// Use this function after sending the battery service a message via the comms system. + pub async fn wait_for_battery_response(&self) -> context::BatteryResponse { + self.context.wait_response().await + } + + /// Asynchronously query the state from the state machine. + pub async fn get_state(&self) -> context::State { + self.context.get_state().await + } } #[derive(Clone, Copy)] @@ -98,40 +147,3 @@ impl comms::MailboxDelegate for Service { Ok(()) } } - -static SERVICE: Service = Service::new(); - -/// Register fuel gauge device with the battery service. -/// -/// Must be done before sending the battery service commands so that hardware device is visible -/// to the battery service. -pub fn register_fuel_gauge(device: &'static device::Device) -> Result<(), embedded_services::intrusive_list::Error> { - SERVICE.context.register_fuel_gauge(device)?; - - Ok(()) -} - -/// Use the battery service endpoint to send data to other subsystems and services. -pub async fn comms_send(endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { - SERVICE.endpoint.send(endpoint_id, data).await -} - -/// Send the battery service state machine an event and await a response. -/// -/// This is an alternative method of interacting with the battery service (instead of using the comms service), -/// and is a useful fn if you want to send an event and await a response sequentially. -pub async fn execute_event(event: BatteryEvent) -> context::BatteryResponse { - SERVICE.context.execute_event(event).await -} - -/// Wait for a response from the battery service. -/// -/// Use this function after sending the battery service a message via the comms system. -pub async fn wait_for_battery_response() -> context::BatteryResponse { - SERVICE.context.wait_response().await -} - -/// Asynchronously query the state from the state machine. -pub async fn get_state() -> context::State { - SERVICE.context.get_state().await -} diff --git a/battery-service/src/task.rs b/battery-service/src/task.rs index 9e63bc3b5..8f38285a4 100644 --- a/battery-service/src/task.rs +++ b/battery-service/src/task.rs @@ -1,17 +1,42 @@ +use battery_service_messages::DeviceId; use embedded_services::{comms, error, info}; -use crate::SERVICE; +use crate::{Service, device::Device}; + +/// Standard dynamic battery data cache +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InitError { + DeviceRegistrationFailed(heapless::Vec), + CommsRegistrationFailed, +} /// Battery service task. -pub async fn task() { +pub async fn task( + service: &'static Service, + devices: [&'static Device; N], +) -> Result<(), InitError> { info!("Starting battery-service task"); - if comms::register_endpoint(&SERVICE, &SERVICE.endpoint).await.is_err() { + let mut failed_devices = heapless::Vec::new(); + for device in devices { + if service.register_fuel_gauge(device).is_err() { + error!("Failed to register battery device with DeviceId {:?}", device.id()); + // Infallible as the Vec is as large as the list of devices passed in. + let _ = failed_devices.push(device.id()); + } + } + + if !failed_devices.is_empty() { + return Err(InitError::DeviceRegistrationFailed(failed_devices)); + } + + if comms::register_endpoint(service, &service.endpoint).await.is_err() { error!("Failed to register battery service endpoint"); - return; + return Err(InitError::CommsRegistrationFailed); } loop { - SERVICE.process_next().await; + service.process_next().await; } } diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index ed341cdb8..6bfd10296 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -178,6 +178,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", + "heapless 0.8.0", "log", "mctp-rs", "zerocopy", diff --git a/examples/pico-de-gallo/src/bin/battery.rs b/examples/pico-de-gallo/src/bin/battery.rs index 64ee1f39c..e79b2edda 100644 --- a/examples/pico-de-gallo/src/bin/battery.rs +++ b/examples/pico-de-gallo/src/bin/battery.rs @@ -151,7 +151,11 @@ impl bs::controller::Controller for Battery { } } -async fn init_and_run_service(i2c: pico_de_gallo_hal::I2c, delay: pico_de_gallo_hal::Delay) -> ! { +async fn init_and_run_service( + battery_service: &'static battery_service::Service, + i2c: pico_de_gallo_hal::I2c, + delay: pico_de_gallo_hal::Delay, +) -> ! { embedded_services::debug!("Initializing battery service"); embedded_services::init().await; @@ -159,8 +163,6 @@ async fn init_and_run_service(i2c: pico_de_gallo_hal::I2c, delay: pico_de_gallo_ static BATTERY_WRAPPER: StaticCell> = StaticCell::new(); let device = BATTERY_DEVICE.init(bs::device::Device::new(bs::device::DeviceId(0))); - battery_service::register_fuel_gauge(device).expect("Failed to register fuel gauge"); - let wrapper = BATTERY_WRAPPER.init(bs::wrapper::Wrapper::new( device, Battery { @@ -170,45 +172,49 @@ async fn init_and_run_service(i2c: pico_de_gallo_hal::I2c, delay: pico_de_gallo_ // Run battery service let _ = embassy_futures::join::join( - tokio::spawn(battery_service::task::task()), + tokio::spawn(battery_service::task::task(battery_service, [device])), tokio::spawn(wrapper.process()), ) .await; unreachable!() } -async fn init_state_machine() -> Result<(), bs::context::ContextError> { - battery_service::execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::DoInit, - device_id: bs::device::DeviceId(0), - }) - .await - .inspect_err(|f| embedded_services::debug!("Fuel gauge init error: {:?}", f))?; - - battery_service::execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::PollStaticData, - device_id: bs::device::DeviceId(0), - }) - .await - .inspect_err(|f| embedded_services::debug!("Fuel gauge static data error: {:?}", f))?; - - battery_service::execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::PollDynamicData, - device_id: bs::device::DeviceId(0), - }) - .await - .inspect_err(|f| embedded_services::debug!("Fuel gauge dynamic data error: {:?}", f))?; +async fn init_state_machine(battery_service: &'static bs::Service) -> Result<(), bs::context::ContextError> { + battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::DoInit, + device_id: bs::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge init error: {:?}", f))?; - Ok(()) -} + battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollStaticData, + device_id: bs::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge static data error: {:?}", f))?; -async fn recover_state_machine() -> Result<(), ()> { - loop { - match battery_service::execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::Timeout, + battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollDynamicData, device_id: bs::device::DeviceId(0), }) .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge dynamic data error: {:?}", f))?; + + Ok(()) +} + +async fn recover_state_machine(battery_service: &'static battery_service::Service) -> Result<(), ()> { + loop { + match battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::Timeout, + device_id: bs::device::DeviceId(0), + }) + .await { Ok(_) => { embedded_services::info!("FG recovered!"); @@ -232,10 +238,10 @@ async fn recover_state_machine() -> Result<(), ()> { } } -pub async fn run_app() { +pub async fn run_app(battery_service: &'static battery_service::Service) { // Initialize battery state machine. let mut retries = 5; - while let Err(e) = init_state_machine().await { + while let Err(e) = init_state_machine(battery_service).await { retries -= 1; if retries <= 0 { embedded_services::error!("Failed to initialize Battery: {:?}", e); @@ -249,21 +255,23 @@ pub async fn run_app() { loop { tokio::time::sleep(std::time::Duration::from_secs(1)).await; if count.is_multiple_of(const { 60 * 60 * 60 }) { - if let Err(e) = battery_service::execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::PollStaticData, - device_id: bs::device::DeviceId(0), - }) - .await + if let Err(e) = battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollStaticData, + device_id: bs::device::DeviceId(0), + }) + .await { failures += 1; embedded_services::error!("Fuel gauge static data error: {:#?}", e); } } - if let Err(e) = battery_service::execute_event(battery_service::context::BatteryEvent { - event: battery_service::context::BatteryEventInner::PollDynamicData, - device_id: bs::device::DeviceId(0), - }) - .await + if let Err(e) = battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollDynamicData, + device_id: bs::device::DeviceId(0), + }) + .await { failures += 1; embedded_services::error!("Fuel gauge dynamic data error: {:#?}", e); @@ -273,7 +281,7 @@ pub async fn run_app() { failures = 0; count = 0; embedded_services::error!("FG: Too many errors, timing out and starting recovery..."); - if recover_state_machine().await.is_err() { + if recover_state_machine(battery_service).await.is_err() { embedded_services::error!("FG: Fatal error"); return; } @@ -288,11 +296,13 @@ async fn main() { env_logger::builder().filter_level(log::LevelFilter::Info).init(); embedded_services::info!("host: battery example started"); + static BATTERY_SERVICE: bs::Service = bs::Service::new(); + let p = pico_de_gallo_hal::Hal::new(); let _ = embassy_futures::join::join( - tokio::spawn(run_app()), - tokio::spawn(init_and_run_service(p.i2c(), p.delay())), + tokio::spawn(run_app(&BATTERY_SERVICE)), + tokio::spawn(init_and_run_service(&BATTERY_SERVICE, p.i2c(), p.delay())), ) .await; } diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 0695fa9b0..59a783c81 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -111,6 +111,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", + "heapless", "mctp-rs", "zerocopy", ] diff --git a/examples/rt633/src/bin/espi_battery.rs b/examples/rt633/src/bin/espi_battery.rs index e19f16441..eb7359765 100644 --- a/examples/rt633/src/bin/espi_battery.rs +++ b/examples/rt633/src/bin/espi_battery.rs @@ -100,19 +100,20 @@ unsafe extern "C" { } #[embassy_executor::task] -async fn battery_publish_task(fg_device: &'static Device) { +async fn battery_publish_task(battery_service: &'static battery_service::Service, fg_device: &'static Device) { loop { Timer::after_secs(1).await; // Get dynamic cache let cache = fg_device.get_dynamic_battery_cache().await; // Send cache data to eSpi service - battery_service::comms_send( - embedded_services::comms::EndpointID::External(embedded_services::comms::External::Host), - &embedded_services::ec_type::message::BatteryMessage::CycleCount(cache.cycle_count.into()), - ) - .await - .unwrap(); + battery_service + .comms_send( + embedded_services::comms::EndpointID::External(embedded_services::comms::External::Host), + &embedded_services::ec_type::message::BatteryMessage::CycleCount(cache.cycle_count.into()), + ) + .await + .unwrap(); } } @@ -131,9 +132,13 @@ async fn espi_service_task(espi: embassy_imxrt::espi::Espi<'static>, memory_map_ } #[embassy_executor::task] -async fn battery_service_task() -> ! { - battery_service::task::task().await; - unreachable!() +async fn battery_service_task( + service: &'static battery_service::Service, + device: [&'static battery_service::device::Device; 1], +) { + if battery_service::task::task(service, device).await.is_err() { + error!("Failed to start battery service") + } } #[embassy_executor::main] @@ -210,6 +215,8 @@ async fn main(spawner: Spawner) { let fg_bus = I2cDevice::new(i2c_bus_fg); + static BATTERY_SERVICE: battery_service::Service = battery_service::Service::new(); + let fg = FG_DEVICE.init(Device::new(DeviceId(0))); let wrap = Wrapper::new( @@ -220,17 +227,16 @@ async fn main(spawner: Spawner) { ); spawner.must_spawn(wrapper_task(wrap)); - spawner.must_spawn(battery_service_task()); + spawner.must_spawn(battery_service_task(&BATTERY_SERVICE, [fg])); - battery_service::register_fuel_gauge(fg).unwrap(); + spawner.must_spawn(battery_publish_task(&BATTERY_SERVICE, fg)); - spawner.must_spawn(battery_publish_task(fg)); - - if let Err(e) = battery_service::execute_event(BatteryEvent { - device_id: DeviceId(0), - event: battery_service::context::BatteryEventInner::DoInit, - }) - .await + if let Err(e) = BATTERY_SERVICE + .execute_event(BatteryEvent { + device_id: DeviceId(0), + event: battery_service::context::BatteryEventInner::DoInit, + }) + .await { error!("Error initializing fuel gauge, error: {:?}", e); } @@ -249,11 +255,12 @@ async fn main(spawner: Spawner) { info!("Memory map contents: {:?}", data[..64]); - if let Err(e) = battery_service::execute_event(BatteryEvent { - device_id: DeviceId(0), - event: battery_service::context::BatteryEventInner::PollDynamicData, - }) - .await + if let Err(e) = BATTERY_SERVICE + .execute_event(BatteryEvent { + device_id: DeviceId(0), + event: battery_service::context::BatteryEventInner::PollDynamicData, + }) + .await { error!("Error getting dynamic fuel gauge data, error: {:?}", e); } diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 818a34e42..7bd330cbe 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -124,6 +124,7 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", + "heapless", "log", "mctp-rs", "zerocopy", diff --git a/examples/std/src/bin/battery.rs b/examples/std/src/bin/battery.rs index 4ff2430fc..605416e11 100644 --- a/examples/std/src/bin/battery.rs +++ b/examples/std/src/bin/battery.rs @@ -12,7 +12,7 @@ use embedded_batteries_async::smart_battery::{ ManufactureDate, MilliAmpsSigned, Minutes, Percent, SmartBattery, SpecificationInfoFields, }; use embedded_hal_mock::eh1::i2c::Mock; -use embedded_services::info; +use embedded_services::{error, info}; mod espi_service { use battery_service::context::{BatteryEvent, BatteryEventInner}; @@ -67,7 +67,7 @@ mod espi_service { } #[embassy_executor::task] - pub async fn task() { + pub async fn task(battery_service: &'static battery_service::Service) { let espi_service = ESPI_SERVICE.get().await; espi_service @@ -81,14 +81,9 @@ mod espi_service { ) .await .unwrap(); - info!("Sent init request"); - match battery_service::wait_for_battery_response().await { - Ok(_) => { - info!("Init request succeeded!") - } - Err(e) => { - error!("Init request failed with {:?}", e); - } + + if let Err(e) = battery_service.wait_for_battery_response().await { + error!("Init request failed with {:?}", e); } Timer::after_secs(5).await; @@ -104,6 +99,10 @@ mod espi_service { .await .unwrap(); + if let Err(e) = battery_service.wait_for_battery_response().await { + error!("static data request failed with {:?}", e); + } + loop { espi_service .endpoint @@ -116,14 +115,9 @@ mod espi_service { ) .await .unwrap(); - info!("Sent dynamic data request"); - match battery_service::wait_for_battery_response().await { - Ok(_) => { - info!("dynamic data request succeeded!") - } - Err(e) => { - error!("dynamic data request failed with {:?}", e); - } + + if let Err(e) = battery_service.wait_for_battery_response().await { + error!("dynamic data request failed with {:?}", e); } Timer::after_secs(5).await; } @@ -470,14 +464,18 @@ async fn wrapper_task(wrapper: Wrapper<'static, FuelGaugeController>) { } #[embassy_executor::task] -async fn battery_service_task() -> ! { - battery_service::task::task().await; - unreachable!() +async fn battery_service_task( + service: &'static battery_service::Service, + device: [&'static battery_service::device::Device; 1], +) { + if let Err(e) = battery_service::task::task(service, device).await { + error!("Failed to start battery service with error {:?}", e) + } } #[embassy_executor::main] async fn main(spawner: Spawner) { - env_logger::builder().filter_level(log::LevelFilter::Trace).init(); + env_logger::builder().filter_level(log::LevelFilter::Info).init(); let expectations = vec![]; @@ -485,6 +483,8 @@ async fn main(spawner: Spawner) { let dev = DEV.get_or_init(|| Device::new(DeviceId(0))); + static SERVICE: battery_service::Service = battery_service::Service::new(); + let wrap = Wrapper::new( dev, FuelGaugeController { @@ -498,9 +498,7 @@ async fn main(spawner: Spawner) { espi_service::init().await; info!("espi service init'd"); - battery_service::register_fuel_gauge(dev).unwrap(); - - spawner.must_spawn(espi_service::task()); + spawner.must_spawn(espi_service::task(&SERVICE)); spawner.must_spawn(wrapper_task(wrap)); - spawner.must_spawn(battery_service_task()); + spawner.must_spawn(battery_service_task(&SERVICE, [dev])); } From 99c702358af7d0fbbb96c2ed138609f5afe6a155 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Tue, 3 Feb 2026 11:41:28 -0800 Subject: [PATCH 14/79] Thermal service refactor (#701) Depends on #700 Thermal service refactor - Enforce initialization order by forcing registration of all objects before a Service instance can be constructed - Replace free functions with methods on the Service - All tasks need an instance of Service to be initialized before any message passing logic starts - Updated std example --- examples/std/src/bin/thermal.rs | 85 ++++++++------- thermal-service/src/context.rs | 2 +- thermal-service/src/fan.rs | 18 +-- thermal-service/src/lib.rs | 183 ++++++++++++++++--------------- thermal-service/src/mptf.rs | 188 ++++++++++++++++++++++---------- thermal-service/src/sensor.rs | 44 +++++--- thermal-service/src/task.rs | 32 ++++-- 7 files changed, 334 insertions(+), 218 deletions(-) diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 653ca8204..0f5a7b9d7 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -15,6 +15,8 @@ use thermal_service as ts; use thermal_service_messages::ThermalRequest; use ts::mptf; +const SAMPLE_BUF_LEN: usize = 16; + // Mock host service mod host { use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; @@ -303,14 +305,14 @@ async fn host() { } } -async fn init_sensor(spawner: Spawner) { +async fn create_sensor() -> &'static ts::sensor::Sensor { info!("Initializing mock bus"); static BUS: OnceLock = OnceLock::new(); let bus = BUS.get_or_init(MockBus::new); info!("Initializing mock sensor"); let mock_sensor = MockSensor::new(bus); - static SENSOR: OnceLock> = OnceLock::new(); + static SENSOR: OnceLock> = OnceLock::new(); let profile = ts::sensor::Profile { warn_high_threshold: 40.0, @@ -318,57 +320,63 @@ async fn init_sensor(spawner: Spawner) { crt_threshold: 80.0, ..Default::default() }; - let sensor = SENSOR.get_or_init(|| ts::sensor::Sensor::new(ts::sensor::DeviceId(0), mock_sensor, profile)); - - ts::register_sensor(sensor.device()).await.unwrap(); - spawner.must_spawn(mock_sensor_task(sensor)); + SENSOR.get_or_init(|| ts::sensor::Sensor::new(ts::sensor::DeviceId(0), mock_sensor, profile)) } -async fn init_fan(spawner: Spawner) { +async fn create_fan() -> &'static ts::fan::Fan { info!("Initializing mock fan"); let mock_fan = MockFan::new(); - static FAN: OnceLock> = OnceLock::new(); - let fan = FAN.get_or_init(|| ts::fan::Fan::new(ts::fan::DeviceId(0), mock_fan, ts::fan::Profile::default())); - - ts::register_fan(fan.device()).await.unwrap(); - spawner.must_spawn(mock_fan_task(fan)); + static FAN: OnceLock> = OnceLock::new(); + FAN.get_or_init(|| ts::fan::Fan::new(ts::fan::DeviceId(0), mock_fan, ts::fan::Profile::default())) } async fn init_thermal(spawner: Spawner) { info!("Initializing thermal service"); - ts::init().await.unwrap(); - init_sensor(spawner).await; - init_fan(spawner).await; + static SERVICE: OnceLock = OnceLock::new(); + let sensor = create_sensor().await; + let fan = create_fan().await; + + if let Ok(service) = ts::Service::new(&SERVICE, &[sensor.device()], &[fan.device()]).await { + spawner.must_spawn(mock_sensor_task(sensor, service)); + spawner.must_spawn(mock_fan_task(fan, service)); + spawner.must_spawn(handle_requests(service)); + spawner.must_spawn(handle_alerts(service)); + } else { + panic!("Failed to initialize thermal service!") + } } #[embassy_executor::task] -async fn handle_alerts() { +async fn handle_alerts(service: &'static ts::Service) { loop { - match ts::wait_event().await { + match service.wait_event().await { ts::Event::ThresholdExceeded(ts::sensor::DeviceId(sensor_id), ts::sensor::ThresholdType::WarnHigh, _) => { warn!("Sensor {sensor_id} exceeded WARN threshold"); - ts::send_service_msg(comms::EndpointID::External(comms::External::Host), &mptf::Notify::Warn) + service + .send_service_msg(comms::EndpointID::External(comms::External::Host), &mptf::Notify::Warn) .await .unwrap() } ts::Event::ThresholdExceeded(ts::sensor::DeviceId(sensor_id), ts::sensor::ThresholdType::Prochot, _) => { warn!("Sensor {sensor_id} exceeded PROCHOT threshold"); - ts::send_service_msg( - comms::EndpointID::External(comms::External::Host), - &mptf::Notify::ProcHot, - ) - .await - .unwrap() + service + .send_service_msg( + comms::EndpointID::External(comms::External::Host), + &mptf::Notify::ProcHot, + ) + .await + .unwrap() } ts::Event::ThresholdExceeded(ts::sensor::DeviceId(sensor_id), ts::sensor::ThresholdType::Critical, _) => { warn!("Sensor {sensor_id} exceeded CRITICAL threshold"); - ts::send_service_msg( - comms::EndpointID::External(comms::External::Host), - &mptf::Notify::Critical, - ) - .await - .unwrap() + service + .send_service_msg( + comms::EndpointID::External(comms::External::Host), + &mptf::Notify::Critical, + ) + .await + .unwrap() } event => warn!("Event: {event:?}"), } @@ -376,8 +384,8 @@ async fn handle_alerts() { } #[embassy_executor::task] -async fn handle_requests() -> ! { - ts::task::handle_requests().await; +async fn handle_requests(service: &'static ts::Service) -> ! { + ts::task::handle_requests(service).await; unreachable!() } @@ -386,8 +394,6 @@ async fn run(spawner: Spawner) { embedded_services::init().await; init_thermal(spawner).await; spawner.must_spawn(host()); - spawner.must_spawn(handle_alerts()); - spawner.must_spawn(handle_requests()); } fn main() { @@ -401,13 +407,16 @@ fn main() { } #[embassy_executor::task] -async fn mock_sensor_task(sensor: &'static ts::sensor::Sensor) -> ! { - ts::task::sensor_task(sensor).await; +async fn mock_sensor_task( + sensor: &'static ts::sensor::Sensor, + service: &'static ts::Service, +) -> ! { + ts::task::sensor_task(sensor, service).await; unreachable!() } #[embassy_executor::task] -async fn mock_fan_task(fan: &'static ts::fan::Fan) -> ! { - ts::task::fan_task(fan).await; +async fn mock_fan_task(fan: &'static ts::fan::Fan, service: &'static ts::Service) -> ! { + ts::task::fan_task(fan, service).await; unreachable!() } diff --git a/thermal-service/src/context.rs b/thermal-service/src/context.rs index 35ed3a127..7975a4793 100644 --- a/thermal-service/src/context.rs +++ b/thermal-service/src/context.rs @@ -16,7 +16,7 @@ pub(crate) struct Context { } impl Context { - pub(crate) fn new() -> Self { + pub(crate) const fn new() -> Self { Self { sensors: intrusive_list::IntrusiveList::new(), fans: intrusive_list::IntrusiveList::new(), diff --git a/thermal-service/src/fan.rs b/thermal-service/src/fan.rs index 524d3d386..fe8e8c62d 100644 --- a/thermal-service/src/fan.rs +++ b/thermal-service/src/fan.rs @@ -1,6 +1,6 @@ //! Fan Device +use crate::Event; use crate::utils::SampleBuf; -use crate::{Event, send_event}; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Timer; @@ -394,14 +394,12 @@ impl Fan { } } - pub async fn handle_auto_control(&self) { + pub async fn handle_auto_control(&self, thermal_service: &'static crate::Service) { loop { if self.profile.lock().await.auto_control { - let temp = match crate::execute_sensor_request( - self.profile.lock().await.sensor_id, - crate::sensor::Request::GetTemp, - ) - .await + let temp = match thermal_service + .execute_sensor_request(self.profile.lock().await.sensor_id, crate::sensor::Request::GetTemp) + .await { Ok(crate::sensor::ResponseData::Temp(temp)) => temp, _ => { @@ -415,13 +413,15 @@ impl Fan { error!("Fan {} failed to set speed to max!", self.device.id.0); } - send_event(Event::FanFailure(self.device.id, Error::Hardware)).await; + thermal_service + .send_event(Event::FanFailure(self.device.id, Error::Hardware)) + .await; continue; } }; if let Err(e) = self.handle_fan_state(temp).await { - send_event(Event::FanFailure(self.device.id, e)).await; + thermal_service.send_event(Event::FanFailure(self.device.id, e)).await; error!("Fan {} error handling fan state transition: {:?}", self.device.id.0, e); } diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index fcd7b81e1..312f34137 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -3,7 +3,6 @@ #![allow(clippy::todo)] #![allow(clippy::unwrap_used)] -use embassy_sync::once_lock::OnceLock; use embedded_sensors_hal_async::temperature::DegreesCelsius; use embedded_services::{comms, error, info, intrusive_list}; @@ -33,118 +32,132 @@ pub enum Event { FanFailure(fan::DeviceId, fan::Error), } -struct Service { +pub struct Service { context: context::Context, endpoint: comms::Endpoint, } impl Service { - fn new() -> Self { - Self { + pub async fn new( + service_storage: &'static embassy_sync::once_lock::OnceLock, + sensors: &[&'static sensor::Device], + fans: &[&'static fan::Device], + ) -> Result<&'static Self, Error> { + let service = service_storage.get_or_init(|| Self { context: context::Context::new(), endpoint: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Thermal)), + }); + + for sensor in sensors { + service.register_sensor(sensor).unwrap(); } - } -} -impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - // Queue for later processing - if let Some(msg) = message.data.get::() { - self.context - .queue_mptf_request(*msg) - .map_err(|_| comms::MailboxDelegateError::BufferFull) - } else { - Err(comms::MailboxDelegateError::InvalidData) + for fan in fans { + service.register_fan(fan).unwrap(); } + + service.init().await?; + + Ok(service) } -} -// Just one instance of the service should be running -static SERVICE: OnceLock = OnceLock::new(); + async fn init(&'static self) -> Result<(), Error> { + info!("Starting thermal service task"); -/// This must be called to initialize the Thermal service -pub async fn init() -> Result<(), Error> { - info!("Starting thermal service task"); - let service = SERVICE.get_or_init(Service::new); + if comms::register_endpoint(self, &self.endpoint).await.is_err() { + error!("Failed to register thermal service endpoint"); + Err(Error) + } else { + Ok(()) + } + } - if comms::register_endpoint(service, &service.endpoint).await.is_err() { - error!("Failed to register thermal service endpoint"); - Err(Error) - } else { + /// Used to send messages to other services from the Thermal service, + /// such as notifying the Host of thresholds crossed or the Power service if CRT TEMP is reached. + pub async fn send_service_msg( + &self, + to: comms::EndpointID, + data: &(impl embedded_services::Any + Send + Sync), + ) -> Result<(), Error> { + // TODO: When this gets updated to return error, handle retrying send on failure + self.endpoint.send(to, data).await.map_err(|_| Error)?; Ok(()) } -} -// TODO: Don't like the code duplication from all these wrappers, consider better approach - -/// Used to send messages to other services from the Thermal service, -/// such as notifying the Host of thresholds crossed or the Power service if CRT TEMP is reached. -pub async fn send_service_msg( - to: comms::EndpointID, - data: &(impl embedded_services::Any + Send + Sync), -) -> Result<(), Error> { - // TODO: When this gets updated to return error, handle retrying send on failure - SERVICE.get().await.endpoint.send(to, data).await.map_err(|_| Error)?; - Ok(()) -} + /// Send a MPTF request + pub fn queue_mptf_request(&self, msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { + self.context.queue_mptf_request(msg) + } -/// Send a MPTF request -pub async fn queue_mptf_request(msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { - SERVICE.get().await.context.queue_mptf_request(msg) -} + /// Wait for a MPTF request + pub async fn wait_mptf_request(&self) -> thermal_service_messages::ThermalRequest { + self.context.wait_mptf_request().await + } -/// Wait for a MPTF request -pub(crate) async fn wait_mptf_request() -> thermal_service_messages::ThermalRequest { - SERVICE.get().await.context.wait_mptf_request().await -} + /// Send a thermal event + pub(crate) async fn send_event(&self, event: Event) { + self.context.send_event(event).await + } -/// Send a thermal event -pub async fn send_event(event: Event) { - SERVICE.get().await.context.send_event(event).await -} + /// Wait for a thermal event + pub async fn wait_event(&self) -> Event { + self.context.wait_event().await + } -/// Wait for a thermal event -pub async fn wait_event() -> Event { - SERVICE.get().await.context.wait_event().await -} + /// Register a sensor with the thermal service + pub fn register_sensor(&self, sensor: &'static sensor::Device) -> Result<(), intrusive_list::Error> { + self.context.register_sensor(sensor) + } -/// Register a sensor with the thermal service -pub async fn register_sensor(sensor: &'static sensor::Device) -> Result<(), intrusive_list::Error> { - SERVICE.get().await.context.register_sensor(sensor) -} + /// Provides access to the sensors list + pub fn sensors(&'static self) -> &'static intrusive_list::IntrusiveList { + self.context.sensors() + } -/// Provides access to the sensors list -pub async fn sensors() -> &'static intrusive_list::IntrusiveList { - SERVICE.get().await.context.sensors() -} + /// Find a sensor by its ID + pub fn get_sensor(&self, id: sensor::DeviceId) -> Option<&'static sensor::Device> { + self.context.get_sensor(id) + } -/// Find a sensor by its ID -pub async fn get_sensor(id: sensor::DeviceId) -> Option<&'static sensor::Device> { - SERVICE.get().await.context.get_sensor(id) -} + /// Send a request to a sensor through the thermal service instead of directly. + pub(crate) async fn execute_sensor_request( + &self, + id: sensor::DeviceId, + request: sensor::Request, + ) -> sensor::Response { + self.context.execute_sensor_request(id, request).await + } -/// Send a request to a sensor through the thermal service instead of directly. -pub async fn execute_sensor_request(id: sensor::DeviceId, request: sensor::Request) -> sensor::Response { - SERVICE.get().await.context.execute_sensor_request(id, request).await -} + /// Register a fan with the thermal service + pub fn register_fan(&self, fan: &'static fan::Device) -> Result<(), intrusive_list::Error> { + self.context.register_fan(fan) + } -/// Register a fan with the thermal service -pub async fn register_fan(fan: &'static fan::Device) -> Result<(), intrusive_list::Error> { - SERVICE.get().await.context.register_fan(fan) -} + /// Provides access to the fans list + pub fn fans(&'static self) -> &'static intrusive_list::IntrusiveList { + self.context.fans() + } -/// Provides access to the fans list -pub async fn fans() -> &'static intrusive_list::IntrusiveList { - SERVICE.get().await.context.fans() -} + /// Find a fan by its ID + pub fn get_fan(&self, id: fan::DeviceId) -> Option<&'static fan::Device> { + self.context.get_fan(id) + } -/// Find a fan by its ID -pub async fn get_fan(id: fan::DeviceId) -> Option<&'static fan::Device> { - SERVICE.get().await.context.get_fan(id) + /// Send a request to a fan through the thermal service instead of directly. + pub(crate) async fn execute_fan_request(&self, id: fan::DeviceId, request: fan::Request) -> fan::Response { + self.context.execute_fan_request(id, request).await + } } -/// Send a request to a fan through the thermal service instead of directly. -pub async fn execute_fan_request(id: fan::DeviceId, request: fan::Request) -> fan::Response { - SERVICE.get().await.context.execute_fan_request(id, request).await +impl comms::MailboxDelegate for Service { + fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { + // Queue for later processing + if let Some(msg) = message.data.get::() { + self.context + .queue_mptf_request(*msg) + .map_err(|_| comms::MailboxDelegateError::BufferFull) + } else { + Err(comms::MailboxDelegateError::InvalidData) + } + } } diff --git a/thermal-service/src/mptf.rs b/thermal-service/src/mptf.rs index dc9e39401..24b968859 100644 --- a/thermal-service/src/mptf.rs +++ b/thermal-service/src/mptf.rs @@ -33,9 +33,13 @@ pub enum Notify { Critical, } -async fn sensor_get_tmp(instance_id: u8) -> thermal_service_messages::ThermalResult { - if let Ok(ts::sensor::ResponseData::Temp(temp)) = - ts::execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp).await +async fn sensor_get_tmp( + instance_id: u8, + thermal_service: &'static crate::Service, +) -> thermal_service_messages::ThermalResult { + if let Ok(ts::sensor::ResponseData::Temp(temp)) = thermal_service + .execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp) + .await { Ok(thermal_service_messages::ThermalResponse::ThermalGetTmpResponse { temperature: utils::c_to_dk(temp), @@ -45,21 +49,27 @@ async fn sensor_get_tmp(instance_id: u8) -> thermal_service_messages::ThermalRes } } -async fn get_var_handler(instance_id: u8, var_uuid: uuid::Bytes) -> thermal_service_messages::ThermalResult { +async fn get_var_handler( + instance_id: u8, + var_uuid: uuid::Bytes, + thermal_service: &'static crate::Service, +) -> thermal_service_messages::ThermalResult { match var_uuid { - uuid_standard::CRT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Critical).await, - uuid_standard::PROC_HOT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot).await, + uuid_standard::CRT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Critical, thermal_service).await, + uuid_standard::PROC_HOT_TEMP => { + sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot, thermal_service).await + } // TODO: Add a SetProfileId request type? But for sensor or fan? uuid_standard::PROFILE_TYPE => { todo!() } - uuid_standard::FAN_ON_TEMP => fan_get_temp(instance_id, fan::Request::GetOnTemp).await, + uuid_standard::FAN_ON_TEMP => fan_get_temp(instance_id, fan::Request::GetOnTemp, thermal_service).await, - uuid_standard::FAN_RAMP_TEMP => fan_get_temp(instance_id, fan::Request::GetRampTemp).await, - uuid_standard::FAN_MAX_TEMP => fan_get_temp(instance_id, fan::Request::GetMaxTemp).await, - uuid_standard::FAN_MIN_RPM => fan_get_rpm(instance_id, fan::Request::GetMinRpm).await, - uuid_standard::FAN_MAX_RPM => fan_get_rpm(instance_id, fan::Request::GetMaxRpm).await, - uuid_standard::FAN_CURRENT_RPM => fan_get_rpm(instance_id, fan::Request::GetRpm).await, + uuid_standard::FAN_RAMP_TEMP => fan_get_temp(instance_id, fan::Request::GetRampTemp, thermal_service).await, + uuid_standard::FAN_MAX_TEMP => fan_get_temp(instance_id, fan::Request::GetMaxTemp, thermal_service).await, + uuid_standard::FAN_MIN_RPM => fan_get_rpm(instance_id, fan::Request::GetMinRpm, thermal_service).await, + uuid_standard::FAN_MAX_RPM => fan_get_rpm(instance_id, fan::Request::GetMaxRpm, thermal_service).await, + uuid_standard::FAN_CURRENT_RPM => fan_get_rpm(instance_id, fan::Request::GetRpm, thermal_service).await, // TODO: Allow OEM to handle these? uuid => { error!("Received GetVar for unrecognized UUID: {:?}", uuid); @@ -72,20 +82,42 @@ async fn set_var_handler( instance_id: u8, var_uuid: uuid::Bytes, set_var: u32, + thermal_service: &'static crate::Service, ) -> thermal_service_messages::ThermalResult { match var_uuid { - uuid_standard::CRT_TEMP => sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var).await, - uuid_standard::PROC_HOT_TEMP => sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var).await, + uuid_standard::CRT_TEMP => { + sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var, thermal_service).await + } + uuid_standard::PROC_HOT_TEMP => { + sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var, thermal_service).await + } // TODO: Add a SetProfileId request type? But for sensor or fan? uuid_standard::PROFILE_TYPE => { todo!() } - uuid_standard::FAN_ON_TEMP => fan_set_var(instance_id, fan::Request::SetOnTemp(utils::dk_to_c(set_var))).await, + uuid_standard::FAN_ON_TEMP => { + fan_set_var( + instance_id, + fan::Request::SetOnTemp(utils::dk_to_c(set_var)), + thermal_service, + ) + .await + } uuid_standard::FAN_RAMP_TEMP => { - fan_set_var(instance_id, fan::Request::SetRampTemp(utils::dk_to_c(set_var))).await + fan_set_var( + instance_id, + fan::Request::SetRampTemp(utils::dk_to_c(set_var)), + thermal_service, + ) + .await } uuid_standard::FAN_MAX_TEMP => { - fan_set_var(instance_id, fan::Request::SetMaxTemp(utils::dk_to_c(set_var))).await + fan_set_var( + instance_id, + fan::Request::SetMaxTemp(utils::dk_to_c(set_var)), + thermal_service, + ) + .await } // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? uuid_standard::FAN_MIN_RPM => { @@ -95,7 +127,9 @@ async fn set_var_handler( uuid_standard::FAN_MAX_RPM => { todo!() } - uuid_standard::FAN_CURRENT_RPM => fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16)).await, + uuid_standard::FAN_CURRENT_RPM => { + fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16), thermal_service).await + } // TODO: Allow OEM to handle these? uuid => { error!("Received SetVar for unrecognized UUID: {:?}", uuid); @@ -104,17 +138,22 @@ async fn set_var_handler( } } -async fn sensor_get_warn_thrs(instance_id: u8) -> thermal_service_messages::ThermalResult { - let low = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), - ) - .await; - let high = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), - ) - .await; +async fn sensor_get_warn_thrs( + instance_id: u8, + thermal_service: &'static crate::Service, +) -> thermal_service_messages::ThermalResult { + let low = thermal_service + .execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), + ) + .await; + let high = thermal_service + .execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), + ) + .await; match (low, high) { (Ok(sensor::ResponseData::Threshold(low)), Ok(sensor::ResponseData::Threshold(high))) => { @@ -133,17 +172,20 @@ async fn sensor_set_warn_thrs( _timeout: Milliseconds, low: DeciKelvin, high: DeciKelvin, + thermal_service: &'static crate::Service, ) -> thermal_service_messages::ThermalResult { - let low_res = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), - ) - .await; - let high_res = ts::execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), - ) - .await; + let low_res = thermal_service + .execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), + ) + .await; + let high_res = thermal_service + .execute_sensor_request( + sensor::DeviceId(instance_id), + sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), + ) + .await; if low_res.is_ok() && high_res.is_ok() { Ok(thermal_service_messages::ThermalResponse::ThermalSetThrsResponse) @@ -155,12 +197,14 @@ async fn sensor_set_warn_thrs( async fn sensor_get_thrs( instance: u8, threshold_type: sensor::ThresholdType, + thermal_service: &'static crate::Service, ) -> thermal_service_messages::ThermalResult { - match ts::execute_sensor_request( - sensor::DeviceId(instance), - sensor::Request::GetThreshold(threshold_type), - ) - .await + match thermal_service + .execute_sensor_request( + sensor::DeviceId(instance), + sensor::Request::GetThreshold(threshold_type), + ) + .await { Ok(sensor::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { val: utils::c_to_dk(temp), @@ -169,8 +213,15 @@ async fn sensor_get_thrs( } } -async fn fan_get_temp(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { - match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { +async fn fan_get_temp( + instance: u8, + fan_request: fan::Request, + thermal_service: &'static crate::Service, +) -> thermal_service_messages::ThermalResult { + match thermal_service + .execute_fan_request(fan::DeviceId(instance), fan_request) + .await + { Ok(fan::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { val: utils::c_to_dk(temp), }), @@ -178,8 +229,15 @@ async fn fan_get_temp(instance: u8, fan_request: fan::Request) -> thermal_servic } } -async fn fan_get_rpm(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { - match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { +async fn fan_get_rpm( + instance: u8, + fan_request: fan::Request, + thermal_service: &'static crate::Service, +) -> thermal_service_messages::ThermalResult { + match thermal_service + .execute_fan_request(fan::DeviceId(instance), fan_request) + .await + { Ok(fan::ResponseData::Rpm(rpm)) => { Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) } @@ -191,20 +249,29 @@ async fn sensor_set_thrs( instance: u8, threshold_type: sensor::ThresholdType, threshold_dk: u32, + thermal_service: &'static crate::Service, ) -> thermal_service_messages::ThermalResult { - match ts::execute_sensor_request( - sensor::DeviceId(instance), - sensor::Request::SetThreshold(threshold_type, utils::dk_to_c(threshold_dk)), - ) - .await + match thermal_service + .execute_sensor_request( + sensor::DeviceId(instance), + sensor::Request::SetThreshold(threshold_type, utils::dk_to_c(threshold_dk)), + ) + .await { Ok(sensor::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), _ => Err(thermal_service_messages::ThermalError::HardwareError), } } -async fn fan_set_var(instance: u8, fan_request: fan::Request) -> thermal_service_messages::ThermalResult { - match ts::execute_fan_request(fan::DeviceId(instance), fan_request).await { +async fn fan_set_var( + instance: u8, + fan_request: fan::Request, + thermal_service: &'static crate::Service, +) -> thermal_service_messages::ThermalResult { + match thermal_service + .execute_fan_request(fan::DeviceId(instance), fan_request) + .await + { Ok(fan::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), _ => Err(thermal_service_messages::ThermalError::HardwareError), } @@ -212,19 +279,20 @@ async fn fan_set_var(instance: u8, fan_request: fan::Request) -> thermal_service pub(crate) async fn process_request( request: &thermal_service_messages::ThermalRequest, + thermal_service: &'static crate::Service, ) -> thermal_service_messages::ThermalResult { match request { thermal_service_messages::ThermalRequest::ThermalGetTmpRequest { instance_id } => { - sensor_get_tmp(*instance_id).await + sensor_get_tmp(*instance_id, thermal_service).await } thermal_service_messages::ThermalRequest::ThermalSetThrsRequest { instance_id, timeout, low, high, - } => sensor_set_warn_thrs(*instance_id, *timeout, *low, *high).await, + } => sensor_set_warn_thrs(*instance_id, *timeout, *low, *high, thermal_service).await, thermal_service_messages::ThermalRequest::ThermalGetThrsRequest { instance_id } => { - sensor_get_warn_thrs(*instance_id).await + sensor_get_warn_thrs(*instance_id, thermal_service).await } // TODO: How do we handle this generically? thermal_service_messages::ThermalRequest::ThermalSetScpRequest { .. } => todo!(), @@ -232,12 +300,12 @@ pub(crate) async fn process_request( instance_id, len: _len, var_uuid, - } => get_var_handler(*instance_id, *var_uuid).await, + } => get_var_handler(*instance_id, *var_uuid, thermal_service).await, thermal_service_messages::ThermalRequest::ThermalSetVarRequest { instance_id, len: _len, var_uuid, set_var, - } => set_var_handler(*instance_id, *var_uuid, *set_var).await, + } => set_var_handler(*instance_id, *var_uuid, *set_var, thermal_service).await, } } diff --git a/thermal-service/src/sensor.rs b/thermal-service/src/sensor.rs index 58da7e38a..e057a84e0 100644 --- a/thermal-service/src/sensor.rs +++ b/thermal-service/src/sensor.rs @@ -1,6 +1,6 @@ //! Sensor Device +use crate::Event; use crate::utils::SampleBuf; -use crate::{Event, send_event}; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Timer; @@ -396,45 +396,61 @@ impl Sensor { } } - async fn check_thresholds(&self, temp: DegreesCelsius) { + async fn check_thresholds(&self, temp: DegreesCelsius, thermal_service: &'static crate::Service) { let profile = self.profile.lock().await; let mut state = self.state.lock().await; if temp >= profile.warn_high_threshold && !state.is_warn_high { - send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::WarnHigh, temp)).await; + thermal_service + .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::WarnHigh, temp)) + .await; state.is_warn_high = true; } else if temp < (profile.warn_high_threshold - profile.hysteresis) && state.is_warn_high { - send_event(Event::ThresholdCleared(self.device.id, ThresholdType::WarnHigh)).await; + thermal_service + .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::WarnHigh)) + .await; state.is_warn_high = false; } if temp <= profile.warn_low_threshold && !state.is_warn_low { - send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::WarnLow, temp)).await; + thermal_service + .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::WarnLow, temp)) + .await; state.is_warn_low = true; } else if temp > (profile.warn_low_threshold + profile.hysteresis) && state.is_warn_low { - send_event(Event::ThresholdCleared(self.device.id, ThresholdType::WarnLow)).await; + thermal_service + .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::WarnLow)) + .await; state.is_warn_low = false; } if temp >= profile.prochot_threshold && !state.is_prochot { - send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::Prochot, temp)).await; + thermal_service + .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::Prochot, temp)) + .await; state.is_prochot = true; } else if temp < (profile.prochot_threshold - profile.hysteresis) && state.is_prochot { - send_event(Event::ThresholdCleared(self.device.id, ThresholdType::Prochot)).await; + thermal_service + .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::Prochot)) + .await; state.is_prochot = false; } if temp >= profile.crt_threshold && !state.is_critical { - send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::Critical, temp)).await; + thermal_service + .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::Critical, temp)) + .await; state.is_critical = true; } else if temp < (profile.crt_threshold - profile.hysteresis) && state.is_critical { - send_event(Event::ThresholdCleared(self.device.id, ThresholdType::Critical)).await; + thermal_service + .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::Critical)) + .await; state.is_critical = false; } } /// Periodically samples temperature from physical sensor and caches it - pub async fn handle_sampling(&self) { + pub async fn handle_sampling(&self, thermal_service: &'static crate::Service) { loop { // Only sample temperature if enabled if self.profile.lock().await.sampling_enabled { @@ -442,7 +458,9 @@ impl Sensor { Ok(temp) => temp, _ => { self.profile.lock().await.sampling_enabled = false; - send_event(Event::SensorFailure(self.device.id, Error::Hardware)).await; + thermal_service + .send_event(Event::SensorFailure(self.device.id, Error::Hardware)) + .await; error!("Error sampling sensor {}, disabling sampling", self.device.id.0); continue; } @@ -455,7 +473,7 @@ impl Sensor { self.samples.lock().await.push(temp); // Check thresholds - self.check_thresholds(temp).await; + self.check_thresholds(temp, thermal_service).await; // Adjust sampling rate based on how hot we are getting let profile = self.profile.lock().await; diff --git a/thermal-service/src/task.rs b/thermal-service/src/task.rs index d66bc95bb..75d02596e 100644 --- a/thermal-service/src/task.rs +++ b/thermal-service/src/task.rs @@ -1,17 +1,18 @@ use embedded_services::{comms, error}; -use crate::{self as ts, mptf::process_request}; +use crate::mptf::process_request; -pub async fn handle_requests() { +pub async fn handle_requests(service: &'static crate::Service) { loop { - let request = ts::wait_mptf_request().await; - let result = process_request(&request).await; - let send_result = ts::send_service_msg( - // TODO we should probably respond to the endpoint that requested us rather than hardcoding the return address like this - comms::EndpointID::External(comms::External::Host), - &result, - ) - .await; + let request = service.wait_mptf_request().await; + let result = process_request(&request, service).await; + let send_result = service + .send_service_msg( + // TODO we should probably respond to the endpoint that requested us rather than hardcoding the return address like this + comms::EndpointID::External(comms::External::Host), + &result, + ) + .await; if send_result.is_err() { error!("Failed to send response to MPTF request!"); @@ -21,12 +22,19 @@ pub async fn handle_requests() { pub async fn fan_task( fan: &'static crate::fan::Fan, + thermal_service: &'static crate::Service, ) { - let _ = embassy_futures::join::join3(fan.handle_rx(), fan.handle_sampling(), fan.handle_auto_control()).await; + let _ = embassy_futures::join::join3( + fan.handle_rx(), + fan.handle_sampling(), + fan.handle_auto_control(thermal_service), + ) + .await; } pub async fn sensor_task( sensor: &'static crate::sensor::Sensor, + thermal_service: &'static crate::Service, ) { - let _ = embassy_futures::join::join(sensor.handle_rx(), sensor.handle_sampling()).await; + let _ = embassy_futures::join::join(sensor.handle_rx(), sensor.handle_sampling(thermal_service)).await; } From 02017d7a31cb8e27ea314a035e24551c2b9a3925 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:07:22 -0800 Subject: [PATCH 15/79] Remove deprecated eSPI memory map (#707) The eSPI memory map is no longer in use, so this change removes it to simplify. As part of this, also remove examples that were intended to exercise the now-removed functionality. Resolves #692 --- .../ec_type/generator/ec-memory-generator.py | 116 --- .../src/ec_type/generator/ec_memory_map.yaml | 148 --- embedded-service/src/ec_type/message.rs | 88 -- embedded-service/src/ec_type/mod.rs | 885 ------------------ embedded-service/src/ec_type/structure.rs | 107 --- embedded-service/src/lib.rs | 1 - espi-service/src/espi_service.rs | 8 +- espi-service/src/task.rs | 23 +- examples/rt633/src/bin/espi.rs | 177 ---- examples/rt633/src/bin/espi_battery.rs | 268 ------ examples/std/src/bin/battery.rs | 504 ---------- 11 files changed, 5 insertions(+), 2320 deletions(-) delete mode 100644 embedded-service/src/ec_type/generator/ec-memory-generator.py delete mode 100644 embedded-service/src/ec_type/generator/ec_memory_map.yaml delete mode 100644 embedded-service/src/ec_type/message.rs delete mode 100644 embedded-service/src/ec_type/mod.rs delete mode 100644 embedded-service/src/ec_type/structure.rs delete mode 100644 examples/rt633/src/bin/espi.rs delete mode 100644 examples/rt633/src/bin/espi_battery.rs delete mode 100644 examples/std/src/bin/battery.rs diff --git a/embedded-service/src/ec_type/generator/ec-memory-generator.py b/embedded-service/src/ec_type/generator/ec-memory-generator.py deleted file mode 100644 index 000196b1f..000000000 --- a/embedded-service/src/ec_type/generator/ec-memory-generator.py +++ /dev/null @@ -1,116 +0,0 @@ -import sys,yaml - -# Function to convert YAML data to Rust structures -def yaml_to_rust(data): - rust_code = "//! EC Internal Data Structures\n\n" - rust_code += "#[allow(missing_docs)]\n" - rust_code += "pub const EC_MEMMAP_VERSION: Version = Version {major: 0, minor: 1, spin: 0, res0: 0};\n\n" - for key, value in data.items(): - rust_code += "#[allow(missing_docs)]\n" - rust_code += "#[repr(C, packed)]\n" - rust_code += "#[derive(Clone, Copy, Debug, Default)]\n" - rust_code += f"pub struct {key} {{\n" - for sub_key, sub_value in value.items(): - if isinstance(sub_value, dict) and 'type' in sub_value: - rust_code += f" pub {sub_key}: {sub_value['type']},\n" - else: - rust_code += f" pub {sub_key}: {sub_value},\n" - rust_code += "}\n\n" - - return rust_code - -# Function to convert YAML data to C structures -def yaml_to_c(data): - c_code = "#pragma once\n\n" - c_code += "#include \n\n" - c_code += "#pragma pack(push, 1)\n\n" - for key, value in data.items(): - c_code += "typedef struct {\n" - for sub_key, sub_value in value.items(): - if isinstance(sub_value, dict) and 'type' in sub_value: - c_code += f" {type_to_c_type(sub_value['type'])} {sub_key};\n" - else: - c_code += f" {sub_value} {sub_key};\n" - c_code += f"}} {key};\n\n" - - c_code += "#pragma pack(pop)\n\n" - - c_code += "const Version EC_MEMMAP_VERSION = {0x00, 0x01, 0x00, 0x00};\n" - return c_code - -def type_to_c_type(type_str): - if type_str == 'u32': - return 'uint32_t' - elif type_str == 'u16': - return 'uint16_t' - elif type_str == 'u8': - return 'uint8_t' - elif type_str == 'i32': - return 'int32_t' - elif type_str == 'i16': - return 'int16_t' - elif type_str == 'i8': - return 'int8_t' - else: - return type_str - -def open_file(file_path): - try: - with open(file_path, 'r') as file: - data = file.read() - return data - except FileNotFoundError: - print(f"File not found: {file_path}") - except Exception as e: - print(f"An error occurred: {e}") - -def check_for_32bit_alignment(data): - sizes = {'u32': 4, 'u16': 2, 'u8': 1, 'i32': 4, 'i16': 2, 'i8': 1} - for key, value in data.items(): - size = 0 - for sub_key, sub_value in value.items(): - if isinstance(sub_value, dict) and 'type' in sub_value: - size += sizes[sub_value['type']] - sizes[key] = size - - for key, size in sizes.items(): - if not is_primitive_type(key) and size % 4 != 0: - print(f"Warning: {key} is not 32-bit aligned. Size: {size} bytes") - -def is_primitive_type(type_str): - return type_str in ['u32', 'u16', 'u8', 'i32', 'i16', 'i8'] - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("Usage: python yamltorust.py ") - sys.exit(1) - else: - file_path = sys.argv[1] - yaml_data = open_file(file_path) - - # Load the YAML data - data = yaml.safe_load(yaml_data) - - check_for_32bit_alignment(data) - - # Convert the YAML data to Rust structures and print the result - rust_code = yaml_to_rust(data) - - c_code = yaml_to_c(data) - - rust_output_filename = "structure.rs" - c_output_filename = "ecmemory.h" - - try: - with open(rust_output_filename, "w") as output_file: - output_file.write(rust_code) - print(f"Rust code has been written to {rust_output_filename}") - except Exception as e: - print(f"An error occurred while writing to {rust_output_filename}: {e}") - - try: - with open(c_output_filename, "w") as output_file: - output_file.write(c_code) - print(f"C code has been written to {c_output_filename}") - except Exception as e: - print(f"An error occurred while writing to {c_output_filename}: {e}") diff --git a/embedded-service/src/ec_type/generator/ec_memory_map.yaml b/embedded-service/src/ec_type/generator/ec_memory_map.yaml deleted file mode 100644 index 59e20535a..000000000 --- a/embedded-service/src/ec_type/generator/ec_memory_map.yaml +++ /dev/null @@ -1,148 +0,0 @@ -# EC Memory layout definition - -Version: - major: - type: u8 - minor: - type: u8 - spin: - type: u8 - res0: - type: u8 - -# Size 0x14 -Capabilities: - events: - type: u32 - fw_version: - type: Version - secure_state: - type: u8 - variants: - INSECURE: 0 - SECURE: 1 - boot_status: - type: u8 - variants: - SUCCESS: 0 - ERROR: 1 - fan_mask: - type: u8 - battery_mask: - type: u8 - temp_mask: - type: u16 - key_mask: - type: u16 - debug_mask: - type: u16 - res0: - type: u16 - -# Size 0x64 -Battery: - events: - type: u32 - status: - type: u32 - last_full_charge: - type: u32 - cycle_count: - type: u32 - state: - type: u32 - present_rate: - type: u32 - remain_cap: - type: u32 - present_volt: - type: u32 - psr_state: - type: u32 - psr_max_out: - type: u32 - psr_max_in: - type: u32 - peak_level: - type: u32 - peak_power: - type: u32 - sus_level: - type: u32 - sus_power: - type: u32 - peak_thres: - type: u32 - sus_thres: - type: u32 - trip_thres: - type: u32 - bmc_data: - type: u32 - bmd_data: - type: u32 - bmd_flags: - type: u32 - bmd_count: - type: u32 - charge_time: - type: u32 - run_time: - type: u32 - sample_time: - type: u32 - -# Size 0x38 -Thermal: - events: - type: u32 - cool_mode: - type: u32 - dba_limit: - type: u32 - sonne_limit: - type: u32 - ma_limit: - type: u32 - fan1_on_temp: - type: u32 - fan1_ramp_temp: - type: u32 - fan1_max_temp: - type: u32 - fan1_crt_temp: - type: u32 - fan1_hot_temp: - type: u32 - fan1_max_rpm: - type: u32 - fan1_cur_rpm: - type: u32 - tmp1_val: - type: u32 - tmp1_timeout: - type: u32 - tmp1_low: - type: u32 - tmp1_high: - type: u32 - -Notifications: - service: - type: u16 - event: - type: u16 - -ECMemory: - ver: - type: Version - caps: - type: Capabilities - notif: - type: Notifications - alarm: - type: TimeAlarm - batt: - type: Battery - therm: - type: Thermal diff --git a/embedded-service/src/ec_type/message.rs b/embedded-service/src/ec_type/message.rs deleted file mode 100644 index f31f178a9..000000000 --- a/embedded-service/src/ec_type/message.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! EC Internal Messages - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] -pub enum CapabilitiesMessage { - Events(u32), - FwVersion(super::structure::Version), - SecureState(u8), - BootStatus(u8), - FanMask(u8), - BatteryMask(u8), - TempMask(u16), - KeyMask(u16), - DebugMask(u16), -} - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TimeAlarmMessage { - Events(u32), - Capability(u32), - Year(u16), - Month(u8), - Day(u8), - Hour(u8), - Minute(u8), - Second(u8), - Valid(u8), - Daylight(u8), - Res1(u8), - Milli(u16), - TimeZone(u16), - Res2(u16), - AlarmStatus(u32), - AcTimeVal(u32), - DcTimeVal(u32), -} - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum BatteryMessage { - Events(u32), - Status(u32), - LastFullCharge(u32), - CycleCount(u32), - State(u32), - PresentRate(u32), - RemainCap(u32), - PresentVolt(u32), - PsrState(u32), - PsrMaxOut(u32), - PsrMaxIn(u32), - PeakLevel(u32), - PeakPower(u32), - SusLevel(u32), - SusPower(u32), - PeakThres(u32), - SusThres(u32), - TripThres(u32), - BmcData(u32), - BmdData(u32), - BmdFlags(u32), - BmdCount(u32), - ChargeTime(u32), - RunTime(u32), - SampleTime(u32), -} - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ThermalMessage { - Events(u32), - CoolMode(u32), - DbaLimit(u32), - SonneLimit(u32), - MaLimit(u32), - Fan1OnTemp(u32), - Fan1RampTemp(u32), - Fan1MaxTemp(u32), - Fan1CrtTemp(u32), - Fan1HotTemp(u32), - Fan1MaxRpm(u32), - Fan1CurRpm(u32), - Tmp1Val(u32), - Tmp1Timeout(u32), - Tmp1Low(u32), - Tmp1High(u32), -} diff --git a/embedded-service/src/ec_type/mod.rs b/embedded-service/src/ec_type/mod.rs deleted file mode 100644 index 0a73a17c1..000000000 --- a/embedded-service/src/ec_type/mod.rs +++ /dev/null @@ -1,885 +0,0 @@ -//! Standard EC types -use core::mem::offset_of; - -pub mod message; -pub mod structure; - -/// Error type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Error { - /// The requested base + offset is invalid - InvalidLocation, -} - -/// Update battery section of memory map based on battery message -pub fn update_battery_section(msg: &message::BatteryMessage, memory_map: &mut structure::ECMemory) { - match msg { - message::BatteryMessage::Events(events) => memory_map.batt.events = *events, - message::BatteryMessage::Status(status) => memory_map.batt.status = *status, - message::BatteryMessage::LastFullCharge(last_full_charge) => { - memory_map.batt.last_full_charge = *last_full_charge - } - message::BatteryMessage::CycleCount(cycle_count) => memory_map.batt.cycle_count = *cycle_count, - message::BatteryMessage::State(state) => memory_map.batt.state = *state, - message::BatteryMessage::PresentRate(present_rate) => memory_map.batt.present_rate = *present_rate, - message::BatteryMessage::RemainCap(remain_cap) => memory_map.batt.remain_cap = *remain_cap, - message::BatteryMessage::PresentVolt(present_volt) => memory_map.batt.present_volt = *present_volt, - message::BatteryMessage::PsrState(psr_state) => memory_map.batt.psr_state = *psr_state, - message::BatteryMessage::PsrMaxOut(psr_max_out) => memory_map.batt.psr_max_out = *psr_max_out, - message::BatteryMessage::PsrMaxIn(psr_max_in) => memory_map.batt.psr_max_in = *psr_max_in, - message::BatteryMessage::PeakLevel(peak_level) => memory_map.batt.peak_level = *peak_level, - message::BatteryMessage::PeakPower(peak_power) => memory_map.batt.peak_power = *peak_power, - message::BatteryMessage::SusLevel(sus_level) => memory_map.batt.sus_level = *sus_level, - message::BatteryMessage::SusPower(sus_power) => memory_map.batt.sus_power = *sus_power, - message::BatteryMessage::PeakThres(peak_thres) => memory_map.batt.peak_thres = *peak_thres, - message::BatteryMessage::SusThres(sus_thres) => memory_map.batt.sus_thres = *sus_thres, - message::BatteryMessage::TripThres(trip_thres) => memory_map.batt.trip_thres = *trip_thres, - message::BatteryMessage::BmcData(bmc_data) => memory_map.batt.bmc_data = *bmc_data, - message::BatteryMessage::BmdData(bmd_data) => memory_map.batt.bmd_data = *bmd_data, - message::BatteryMessage::BmdFlags(bmd_flags) => memory_map.batt.bmd_flags = *bmd_flags, - message::BatteryMessage::BmdCount(bmd_count) => memory_map.batt.bmd_count = *bmd_count, - message::BatteryMessage::ChargeTime(charge_time) => memory_map.batt.charge_time = *charge_time, - message::BatteryMessage::RunTime(run_time) => memory_map.batt.run_time = *run_time, - message::BatteryMessage::SampleTime(sample_time) => memory_map.batt.sample_time = *sample_time, - } -} - -/// Update capabilities section of memory map based on battery message -pub fn update_capabilities_section(msg: &message::CapabilitiesMessage, memory_map: &mut structure::ECMemory) { - match msg { - message::CapabilitiesMessage::Events(events) => memory_map.caps.events = *events, - message::CapabilitiesMessage::FwVersion(fw_version) => memory_map.caps.fw_version = *fw_version, - message::CapabilitiesMessage::SecureState(secure_state) => memory_map.caps.secure_state = *secure_state, - message::CapabilitiesMessage::BootStatus(boot_status) => memory_map.caps.boot_status = *boot_status, - message::CapabilitiesMessage::FanMask(fan_mask) => memory_map.caps.fan_mask = *fan_mask, - message::CapabilitiesMessage::BatteryMask(battery_mask) => memory_map.caps.battery_mask = *battery_mask, - message::CapabilitiesMessage::TempMask(temp_mask) => memory_map.caps.temp_mask = *temp_mask, - message::CapabilitiesMessage::KeyMask(key_mask) => memory_map.caps.key_mask = *key_mask, - message::CapabilitiesMessage::DebugMask(debug_mask) => memory_map.caps.debug_mask = *debug_mask, - } -} - -/// Update thermal section of memory map based on battery message -pub fn update_thermal_section(msg: &message::ThermalMessage, memory_map: &mut structure::ECMemory) { - match msg { - message::ThermalMessage::Events(events) => memory_map.therm.events = *events, - message::ThermalMessage::CoolMode(cool_mode) => memory_map.therm.cool_mode = *cool_mode, - message::ThermalMessage::DbaLimit(dba_limit) => memory_map.therm.dba_limit = *dba_limit, - message::ThermalMessage::SonneLimit(sonne_limit) => memory_map.therm.sonne_limit = *sonne_limit, - message::ThermalMessage::MaLimit(ma_limit) => memory_map.therm.ma_limit = *ma_limit, - message::ThermalMessage::Fan1OnTemp(fan1_on_temp) => memory_map.therm.fan1_on_temp = *fan1_on_temp, - message::ThermalMessage::Fan1RampTemp(fan1_ramp_temp) => memory_map.therm.fan1_ramp_temp = *fan1_ramp_temp, - message::ThermalMessage::Fan1MaxTemp(fan1_max_temp) => memory_map.therm.fan1_max_temp = *fan1_max_temp, - message::ThermalMessage::Fan1CrtTemp(fan1_crt_temp) => memory_map.therm.fan1_crt_temp = *fan1_crt_temp, - message::ThermalMessage::Fan1HotTemp(fan1_hot_temp) => memory_map.therm.fan1_hot_temp = *fan1_hot_temp, - message::ThermalMessage::Fan1MaxRpm(fan1_max_rpm) => memory_map.therm.fan1_max_rpm = *fan1_max_rpm, - message::ThermalMessage::Fan1CurRpm(fan1_cur_rpm) => memory_map.therm.fan1_cur_rpm = *fan1_cur_rpm, - message::ThermalMessage::Tmp1Val(tmp1_val) => memory_map.therm.tmp1_val = *tmp1_val, - message::ThermalMessage::Tmp1Timeout(tmp1_timeout) => memory_map.therm.tmp1_timeout = *tmp1_timeout, - message::ThermalMessage::Tmp1Low(tmp1_low) => memory_map.therm.tmp1_low = *tmp1_low, - message::ThermalMessage::Tmp1High(tmp1_high) => memory_map.therm.tmp1_high = *tmp1_high, - } -} - -/// Helper macro to simplify the conversion of memory map to message -macro_rules! into_message { - ($offset:ident, $length:ident, $member:expr, $msg:expr) => { - let value = $member; - *$offset += size_of_val(&value); - *$length -= size_of_val(&value); - return Ok($msg(value)); - }; -} - -/// Convert from memory map offset and length to battery message -/// Modifies offset and length -pub fn mem_map_to_battery_msg( - memory_map: &structure::ECMemory, - offset: &mut usize, - length: &mut usize, -) -> Result { - let local_offset = *offset - offset_of!(structure::ECMemory, batt); - - if local_offset == offset_of!(structure::Battery, events) { - into_message!(offset, length, memory_map.batt.events, message::BatteryMessage::Events); - } else if local_offset == offset_of!(structure::Battery, status) { - into_message!(offset, length, memory_map.batt.status, message::BatteryMessage::Status); - } else if local_offset == offset_of!(structure::Battery, last_full_charge) { - into_message!( - offset, - length, - memory_map.batt.last_full_charge, - message::BatteryMessage::LastFullCharge - ); - } else if local_offset == offset_of!(structure::Battery, cycle_count) { - into_message!( - offset, - length, - memory_map.batt.cycle_count, - message::BatteryMessage::CycleCount - ); - } else if local_offset == offset_of!(structure::Battery, state) { - into_message!(offset, length, memory_map.batt.state, message::BatteryMessage::State); - } else if local_offset == offset_of!(structure::Battery, present_rate) { - into_message!( - offset, - length, - memory_map.batt.present_rate, - message::BatteryMessage::PresentRate - ); - } else if local_offset == offset_of!(structure::Battery, remain_cap) { - into_message!( - offset, - length, - memory_map.batt.remain_cap, - message::BatteryMessage::RemainCap - ); - } else if local_offset == offset_of!(structure::Battery, present_volt) { - into_message!( - offset, - length, - memory_map.batt.present_volt, - message::BatteryMessage::PresentVolt - ); - } else if local_offset == offset_of!(structure::Battery, psr_state) { - into_message!( - offset, - length, - memory_map.batt.psr_state, - message::BatteryMessage::PsrState - ); - } else if local_offset == offset_of!(structure::Battery, psr_max_out) { - into_message!( - offset, - length, - memory_map.batt.psr_max_out, - message::BatteryMessage::PsrMaxOut - ); - } else if local_offset == offset_of!(structure::Battery, psr_max_in) { - into_message!( - offset, - length, - memory_map.batt.psr_max_in, - message::BatteryMessage::PsrMaxIn - ); - } else if local_offset == offset_of!(structure::Battery, peak_level) { - into_message!( - offset, - length, - memory_map.batt.peak_level, - message::BatteryMessage::PeakLevel - ); - } else if local_offset == offset_of!(structure::Battery, peak_power) { - into_message!( - offset, - length, - memory_map.batt.peak_power, - message::BatteryMessage::PeakPower - ); - } else if local_offset == offset_of!(structure::Battery, sus_level) { - into_message!( - offset, - length, - memory_map.batt.sus_level, - message::BatteryMessage::SusLevel - ); - } else if local_offset == offset_of!(structure::Battery, sus_power) { - into_message!( - offset, - length, - memory_map.batt.sus_power, - message::BatteryMessage::SusPower - ); - } else if local_offset == offset_of!(structure::Battery, peak_thres) { - into_message!( - offset, - length, - memory_map.batt.peak_thres, - message::BatteryMessage::PeakThres - ); - } else if local_offset == offset_of!(structure::Battery, sus_thres) { - into_message!( - offset, - length, - memory_map.batt.sus_thres, - message::BatteryMessage::SusThres - ); - } else if local_offset == offset_of!(structure::Battery, trip_thres) { - into_message!( - offset, - length, - memory_map.batt.trip_thres, - message::BatteryMessage::TripThres - ); - } else if local_offset == offset_of!(structure::Battery, bmc_data) { - into_message!( - offset, - length, - memory_map.batt.bmc_data, - message::BatteryMessage::BmcData - ); - } else if local_offset == offset_of!(structure::Battery, bmd_data) { - into_message!( - offset, - length, - memory_map.batt.bmd_data, - message::BatteryMessage::BmdData - ); - } else if local_offset == offset_of!(structure::Battery, bmd_flags) { - into_message!( - offset, - length, - memory_map.batt.bmd_flags, - message::BatteryMessage::BmdFlags - ); - } else if local_offset == offset_of!(structure::Battery, bmd_count) { - into_message!( - offset, - length, - memory_map.batt.bmd_count, - message::BatteryMessage::BmdCount - ); - } else if local_offset == offset_of!(structure::Battery, charge_time) { - into_message!( - offset, - length, - memory_map.batt.charge_time, - message::BatteryMessage::ChargeTime - ); - } else if local_offset == offset_of!(structure::Battery, run_time) { - into_message!( - offset, - length, - memory_map.batt.run_time, - message::BatteryMessage::RunTime - ); - } else if local_offset == offset_of!(structure::Battery, sample_time) { - into_message!( - offset, - length, - memory_map.batt.sample_time, - message::BatteryMessage::SampleTime - ); - } else { - Err(Error::InvalidLocation) - } -} - -/// Convert from memory map offset and length to thermal message -/// Modifies offset and length -pub fn mem_map_to_thermal_msg( - memory_map: &structure::ECMemory, - offset: &mut usize, - length: &mut usize, -) -> Result { - let local_offset = *offset - offset_of!(structure::ECMemory, therm); - - if local_offset == offset_of!(structure::Thermal, events) { - into_message!(offset, length, memory_map.therm.events, message::ThermalMessage::Events); - } else if local_offset == offset_of!(structure::Thermal, cool_mode) { - into_message!( - offset, - length, - memory_map.therm.cool_mode, - message::ThermalMessage::CoolMode - ); - } else if local_offset == offset_of!(structure::Thermal, dba_limit) { - into_message!( - offset, - length, - memory_map.therm.dba_limit, - message::ThermalMessage::DbaLimit - ); - } else if local_offset == offset_of!(structure::Thermal, sonne_limit) { - into_message!( - offset, - length, - memory_map.therm.sonne_limit, - message::ThermalMessage::SonneLimit - ); - } else if local_offset == offset_of!(structure::Thermal, ma_limit) { - into_message!( - offset, - length, - memory_map.therm.ma_limit, - message::ThermalMessage::MaLimit - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_on_temp) { - into_message!( - offset, - length, - memory_map.therm.fan1_on_temp, - message::ThermalMessage::Fan1OnTemp - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_ramp_temp) { - into_message!( - offset, - length, - memory_map.therm.fan1_ramp_temp, - message::ThermalMessage::Fan1RampTemp - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_max_temp) { - into_message!( - offset, - length, - memory_map.therm.fan1_max_temp, - message::ThermalMessage::Fan1MaxTemp - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_crt_temp) { - into_message!( - offset, - length, - memory_map.therm.fan1_crt_temp, - message::ThermalMessage::Fan1CrtTemp - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_hot_temp) { - into_message!( - offset, - length, - memory_map.therm.fan1_hot_temp, - message::ThermalMessage::Fan1HotTemp - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_max_rpm) { - into_message!( - offset, - length, - memory_map.therm.fan1_max_rpm, - message::ThermalMessage::Fan1MaxRpm - ); - } else if local_offset == offset_of!(structure::Thermal, fan1_cur_rpm) { - into_message!( - offset, - length, - memory_map.therm.fan1_cur_rpm, - message::ThermalMessage::Fan1CurRpm - ); - } else if local_offset == offset_of!(structure::Thermal, tmp1_val) { - into_message!( - offset, - length, - memory_map.therm.tmp1_val, - message::ThermalMessage::Tmp1Val - ); - } else if local_offset == offset_of!(structure::Thermal, tmp1_timeout) { - into_message!( - offset, - length, - memory_map.therm.tmp1_timeout, - message::ThermalMessage::Tmp1Timeout - ); - } else if local_offset == offset_of!(structure::Thermal, tmp1_low) { - into_message!( - offset, - length, - memory_map.therm.tmp1_low, - message::ThermalMessage::Tmp1Low - ); - } else if local_offset == offset_of!(structure::Thermal, tmp1_high) { - into_message!( - offset, - length, - memory_map.therm.tmp1_high, - message::ThermalMessage::Tmp1High - ); - } else { - Err(Error::InvalidLocation) - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::*; - - macro_rules! test_field { - ($memory_map:ident, $offset:ident, $length:ident, $field:expr, $func:ident, $msg:expr) => { - let field = $field; - let next_offset = $offset + size_of_val(&field); - let next_length = $length - size_of_val(&field); - let msg = $func(&$memory_map, &mut $offset, &mut $length).unwrap(); - assert_eq!(msg, $msg(field)); - assert_eq!($offset, next_offset); - assert_eq!($length, next_length); - }; - } - - #[test] - fn test_mem_map_to_battery_msg() { - use crate::ec_type::message::BatteryMessage; - use crate::ec_type::structure::{Battery, ECMemory}; - - let memory_map = ECMemory { - batt: Battery { - events: 1, - status: 2, - last_full_charge: 3, - cycle_count: 4, - state: 5, - present_rate: 6, - remain_cap: 7, - present_volt: 8, - psr_state: 9, - psr_max_out: 10, - psr_max_in: 11, - peak_level: 12, - peak_power: 13, - sus_level: 14, - sus_power: 15, - peak_thres: 16, - sus_thres: 17, - trip_thres: 18, - bmc_data: 19, - bmd_data: 20, - bmd_flags: 21, - bmd_count: 22, - charge_time: 23, - run_time: 24, - sample_time: 25, - }, - ..Default::default() - }; - - let mut offset = offset_of!(ECMemory, batt); - let mut length = size_of::(); - - test_field!( - memory_map, - offset, - length, - memory_map.batt.events, - mem_map_to_battery_msg, - BatteryMessage::Events - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.status, - mem_map_to_battery_msg, - BatteryMessage::Status - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.last_full_charge, - mem_map_to_battery_msg, - BatteryMessage::LastFullCharge - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.cycle_count, - mem_map_to_battery_msg, - BatteryMessage::CycleCount - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.state, - mem_map_to_battery_msg, - BatteryMessage::State - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.present_rate, - mem_map_to_battery_msg, - BatteryMessage::PresentRate - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.remain_cap, - mem_map_to_battery_msg, - BatteryMessage::RemainCap - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.present_volt, - mem_map_to_battery_msg, - BatteryMessage::PresentVolt - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.psr_state, - mem_map_to_battery_msg, - BatteryMessage::PsrState - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.psr_max_out, - mem_map_to_battery_msg, - BatteryMessage::PsrMaxOut - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.psr_max_in, - mem_map_to_battery_msg, - BatteryMessage::PsrMaxIn - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.peak_level, - mem_map_to_battery_msg, - BatteryMessage::PeakLevel - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.peak_power, - mem_map_to_battery_msg, - BatteryMessage::PeakPower - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.sus_level, - mem_map_to_battery_msg, - BatteryMessage::SusLevel - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.sus_power, - mem_map_to_battery_msg, - BatteryMessage::SusPower - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.peak_thres, - mem_map_to_battery_msg, - BatteryMessage::PeakThres - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.sus_thres, - mem_map_to_battery_msg, - BatteryMessage::SusThres - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.trip_thres, - mem_map_to_battery_msg, - BatteryMessage::TripThres - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.bmc_data, - mem_map_to_battery_msg, - BatteryMessage::BmcData - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.bmd_data, - mem_map_to_battery_msg, - BatteryMessage::BmdData - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.bmd_flags, - mem_map_to_battery_msg, - BatteryMessage::BmdFlags - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.bmd_count, - mem_map_to_battery_msg, - BatteryMessage::BmdCount - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.charge_time, - mem_map_to_battery_msg, - BatteryMessage::ChargeTime - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.run_time, - mem_map_to_battery_msg, - BatteryMessage::RunTime - ); - test_field!( - memory_map, - offset, - length, - memory_map.batt.sample_time, - mem_map_to_battery_msg, - BatteryMessage::SampleTime - ); - - assert_eq!(length, 0); - } - - #[test] - fn test_mem_map_to_battery_msg_error() { - use crate::ec_type::structure::{Battery, ECMemory}; - - let memory_map = ECMemory { - batt: Battery { - events: 1, - status: 2, - last_full_charge: 3, - cycle_count: 4, - state: 5, - present_rate: 6, - remain_cap: 7, - present_volt: 8, - psr_state: 9, - psr_max_out: 10, - psr_max_in: 11, - peak_level: 12, - peak_power: 13, - sus_level: 14, - sus_power: 15, - peak_thres: 16, - sus_thres: 17, - trip_thres: 18, - bmc_data: 19, - bmd_data: 20, - bmd_flags: 21, - bmd_count: 22, - charge_time: 23, - run_time: 24, - sample_time: 25, - }, - ..Default::default() - }; - - let mut offset = offset_of!(ECMemory, batt) + 1; - let mut length = size_of::(); - - let res = mem_map_to_battery_msg(&memory_map, &mut offset, &mut length); - assert!(res.is_err() && res.unwrap_err() == Error::InvalidLocation); - } - - #[test] - fn test_mem_map_to_thermal_msg() { - use crate::ec_type::message::ThermalMessage; - use crate::ec_type::structure::{ECMemory, Thermal}; - - let memory_map = ECMemory { - therm: Thermal { - events: 1, - cool_mode: 2, - dba_limit: 3, - sonne_limit: 4, - ma_limit: 5, - fan1_on_temp: 6, - fan1_ramp_temp: 7, - fan1_max_temp: 8, - fan1_crt_temp: 9, - fan1_hot_temp: 10, - fan1_max_rpm: 11, - fan1_cur_rpm: 12, - tmp1_val: 13, - tmp1_timeout: 14, - tmp1_low: 15, - tmp1_high: 16, - }, - ..Default::default() - }; - - let mut offset = offset_of!(ECMemory, therm); - let mut length = size_of::(); - - test_field!( - memory_map, - offset, - length, - memory_map.therm.events, - mem_map_to_thermal_msg, - ThermalMessage::Events - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.cool_mode, - mem_map_to_thermal_msg, - ThermalMessage::CoolMode - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.dba_limit, - mem_map_to_thermal_msg, - ThermalMessage::DbaLimit - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.sonne_limit, - mem_map_to_thermal_msg, - ThermalMessage::SonneLimit - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.ma_limit, - mem_map_to_thermal_msg, - ThermalMessage::MaLimit - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_on_temp, - mem_map_to_thermal_msg, - ThermalMessage::Fan1OnTemp - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_ramp_temp, - mem_map_to_thermal_msg, - ThermalMessage::Fan1RampTemp - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_max_temp, - mem_map_to_thermal_msg, - ThermalMessage::Fan1MaxTemp - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_crt_temp, - mem_map_to_thermal_msg, - ThermalMessage::Fan1CrtTemp - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_hot_temp, - mem_map_to_thermal_msg, - ThermalMessage::Fan1HotTemp - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_max_rpm, - mem_map_to_thermal_msg, - ThermalMessage::Fan1MaxRpm - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.fan1_cur_rpm, - mem_map_to_thermal_msg, - ThermalMessage::Fan1CurRpm - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.tmp1_val, - mem_map_to_thermal_msg, - ThermalMessage::Tmp1Val - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.tmp1_timeout, - mem_map_to_thermal_msg, - ThermalMessage::Tmp1Timeout - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.tmp1_low, - mem_map_to_thermal_msg, - ThermalMessage::Tmp1Low - ); - test_field!( - memory_map, - offset, - length, - memory_map.therm.tmp1_high, - mem_map_to_thermal_msg, - ThermalMessage::Tmp1High - ); - - assert_eq!(length, 0); - } - - #[test] - fn test_mem_map_to_thermal_msg_error() { - use crate::ec_type::structure::{ECMemory, Thermal}; - - let memory_map = ECMemory { - therm: Thermal { - events: 1, - cool_mode: 2, - dba_limit: 3, - sonne_limit: 4, - ma_limit: 5, - fan1_on_temp: 6, - fan1_ramp_temp: 7, - fan1_max_temp: 8, - fan1_crt_temp: 9, - fan1_hot_temp: 10, - fan1_max_rpm: 11, - fan1_cur_rpm: 12, - tmp1_val: 13, - tmp1_timeout: 14, - tmp1_low: 15, - tmp1_high: 16, - }, - ..Default::default() - }; - - let mut offset = offset_of!(ECMemory, therm) + 1; - let mut length = size_of::(); - - let res = mem_map_to_thermal_msg(&memory_map, &mut offset, &mut length); - assert!(res.is_err() && res.unwrap_err() == Error::InvalidLocation); - } -} diff --git a/embedded-service/src/ec_type/structure.rs b/embedded-service/src/ec_type/structure.rs deleted file mode 100644 index 558b9d47f..000000000 --- a/embedded-service/src/ec_type/structure.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! EC Internal Data Structures - -#[allow(missing_docs)] -pub const EC_MEMMAP_VERSION: Version = Version { - major: 0, - minor: 1, - spin: 0, - res0: 0, -}; - -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct Version { - pub major: u8, - pub minor: u8, - pub spin: u8, - pub res0: u8, -} - -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct Capabilities { - pub events: u32, - pub fw_version: Version, - pub secure_state: u8, - pub boot_status: u8, - pub fan_mask: u8, - pub battery_mask: u8, - pub temp_mask: u16, - pub key_mask: u16, - pub debug_mask: u16, - pub res0: u16, -} - -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct Battery { - pub events: u32, - pub status: u32, - pub last_full_charge: u32, - pub cycle_count: u32, - pub state: u32, - pub present_rate: u32, - pub remain_cap: u32, - pub present_volt: u32, - pub psr_state: u32, - pub psr_max_out: u32, - pub psr_max_in: u32, - pub peak_level: u32, - pub peak_power: u32, - pub sus_level: u32, - pub sus_power: u32, - pub peak_thres: u32, - pub sus_thres: u32, - pub trip_thres: u32, - pub bmc_data: u32, - pub bmd_data: u32, - pub bmd_flags: u32, - pub bmd_count: u32, - pub charge_time: u32, - pub run_time: u32, - pub sample_time: u32, -} - -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct Thermal { - pub events: u32, - pub cool_mode: u32, - pub dba_limit: u32, - pub sonne_limit: u32, - pub ma_limit: u32, - pub fan1_on_temp: u32, - pub fan1_ramp_temp: u32, - pub fan1_max_temp: u32, - pub fan1_crt_temp: u32, - pub fan1_hot_temp: u32, - pub fan1_max_rpm: u32, - pub fan1_cur_rpm: u32, - pub tmp1_val: u32, - pub tmp1_timeout: u32, - pub tmp1_low: u32, - pub tmp1_high: u32, -} - -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct Notifications { - pub service: u16, - pub event: u16, -} - -#[allow(missing_docs)] -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default)] -pub struct ECMemory { - pub ver: Version, - pub caps: Capabilities, - pub notif: Notifications, - pub batt: Battery, - pub therm: Thermal, -} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 7facbbe0d..660aaf5af 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -16,7 +16,6 @@ pub mod broadcaster; pub mod buffer; pub mod cfu; pub mod comms; -pub mod ec_type; pub mod fmt; pub mod hid; pub mod init; diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 74fd55826..79b2ac03f 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -4,11 +4,10 @@ use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService use core::borrow::BorrowMut; use embassy_imxrt::espi; use embassy_sync::channel::Channel; -use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embedded_services::buffer::OwnedRef; use embedded_services::comms::{self, EndpointID, External}; -use embedded_services::{GlobalRawMutex, debug, ec_type, error, info, trace}; +use embedded_services::{GlobalRawMutex, debug, error, info, trace}; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -39,16 +38,15 @@ pub enum Error { pub struct Service<'a> { endpoint: comms::Endpoint, - _ec_memory: Mutex, host_tx_queue: Channel, assembly_buf_owned_ref: OwnedRef<'a, u8>, } impl Service<'_> { - pub fn new(ec_memory: &'static mut ec_type::structure::ECMemory) -> Self { + #[allow(clippy::new_without_default)] // When we break the dependency on embassy-imxrt, we'll need to take an eSPI trait implementation as an argument, so it doesn't make sense to provide a default implementation + pub fn new() -> Self { Service { endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), - _ec_memory: Mutex::new(ec_memory), host_tx_queue: Channel::new(), assembly_buf_owned_ref: assembly_buf::get_mut().unwrap(), } diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs index dc7ac53c4..458302593 100644 --- a/espi-service/src/task.rs +++ b/espi-service/src/task.rs @@ -1,34 +1,15 @@ use embassy_futures::select::select; use embassy_imxrt::espi; -use embedded_services::{comms, ec_type, info}; +use embedded_services::comms; use crate::{ESPI_SERVICE, Service, process_controller_event}; pub async fn espi_service( mut espi: espi::Espi<'static>, - memory_map_buffer: &'static mut [u8], ) -> Result { - info!("Reserved eSPI memory map buffer size: {}", memory_map_buffer.len()); - info!("eSPI MemoryMap size: {}", size_of::()); - - if size_of::() > memory_map_buffer.len() { - panic!("eSPI MemoryMap is too big for reserved memory buffer!!!"); - } - - memory_map_buffer.fill(0); - - let memory_map: &mut ec_type::structure::ECMemory = - unsafe { &mut *(memory_map_buffer.as_mut_ptr() as *mut ec_type::structure::ECMemory) }; - espi.wait_for_plat_reset().await; - info!("Initializing memory map"); - memory_map.ver.major = ec_type::structure::EC_MEMMAP_VERSION.major; - memory_map.ver.minor = ec_type::structure::EC_MEMMAP_VERSION.minor; - memory_map.ver.spin = ec_type::structure::EC_MEMMAP_VERSION.spin; - memory_map.ver.res0 = ec_type::structure::EC_MEMMAP_VERSION.res0; - - let espi_service = ESPI_SERVICE.get_or_init(|| Service::new(memory_map)); + let espi_service = ESPI_SERVICE.get_or_init(Service::new); comms::register_endpoint(espi_service, espi_service.endpoint()) .await .unwrap(); diff --git a/examples/rt633/src/bin/espi.rs b/examples/rt633/src/bin/espi.rs deleted file mode 100644 index 60850559f..000000000 --- a/examples/rt633/src/bin/espi.rs +++ /dev/null @@ -1,177 +0,0 @@ -#![no_std] -#![no_main] - -extern crate rt633_examples; - -use core::slice::{self}; - -use defmt::info; -use embassy_executor::Spawner; -use embassy_imxrt::bind_interrupts; -use embassy_imxrt::espi::BaseOrAsz; -use embassy_imxrt::espi::{Base, Capabilities, Config, Direction, Espi, InterruptHandler, Len, Maxspd, PortConfig}; -use embassy_imxrt::peripherals::ESPI; -use {defmt_rtt as _, panic_probe as _}; - -// Mock battery service -mod battery_service { - use defmt::info; - use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; - use embassy_sync::once_lock::OnceLock; - use embassy_sync::signal::Signal; - use embedded_services::comms::{self, EndpointID, External, Internal}; - use embedded_services::ec_type; - - struct Service { - endpoint: comms::Endpoint, - - // This is can be an Embassy signal or channel or whatever Embassy async notification construct - signal: Signal, - } - - impl Service { - fn new() -> Self { - Service { - endpoint: comms::Endpoint::uninit(EndpointID::Internal(Internal::Battery)), - signal: Signal::new(), - } - } - } - - impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - let msg = message - .data - .get::() - .ok_or(comms::MailboxDelegateError::MessageNotFound)?; - - self.signal.signal(*msg); - - Ok(()) - } - } - - static BATTERY_SERVICE: OnceLock = OnceLock::new(); - - // Initialize battery service - pub async fn init() { - let battery_service = BATTERY_SERVICE.get_or_init(Service::new); - - comms::register_endpoint(battery_service, &battery_service.endpoint) - .await - .unwrap(); - } - - // Service to update the battery value in the memory map periodically - #[embassy_executor::task] - pub async fn battery_update_service() { - let battery_service = BATTERY_SERVICE.get().await; - - let mut battery_remain_cap = u32::MAX; - - loop { - battery_service - .endpoint - .send( - EndpointID::External(External::Host), - &ec_type::message::BatteryMessage::RemainCap(battery_remain_cap), - ) - .await - .unwrap(); - info!("Sending updated battery status to espi service"); - battery_remain_cap -= 1; - - embassy_time::Timer::after_secs(1).await; - } - } -} - -bind_interrupts!(struct Irqs { - ESPI => InterruptHandler; -}); - -// SAFETY: These are symbols defined by the linker and guaranteed to point to valid memory -unsafe extern "C" { - static __start_espi_data: u8; - static __end_espi_data: u8; -} - -#[embassy_executor::task] -async fn espi_service_task(espi: embassy_imxrt::espi::Espi<'static>, memory_map_buffer: &'static mut [u8]) -> ! { - let Err(e) = espi_service::task::espi_service(espi, memory_map_buffer).await; - panic!("espi_service_task error: {e:?}"); -} - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - let p = embassy_imxrt::init(Default::default()); - - embedded_services::init().await; - - let espi = Espi::new( - p.ESPI, - p.PIO7_29, - p.PIO7_26, - p.PIO7_27, - p.PIO7_28, - p.PIO7_30, - p.PIO7_31, - p.PIO7_25, - p.PIO7_24, - Irqs, - Config { - caps: Capabilities { - max_speed: Maxspd::SmallThan20m, - alert_as_a_pin: true, - ..Default::default() - }, - ram_base: 0x2000_0000, - base0_addr: 0x2002_0000, - base1_addr: 0x2003_0000, - status_addr: Some(0x480), - status_base: Base::OffsetFrom0, - ports_config: [ - PortConfig::MailboxShared { - direction: Direction::BidirectionalUnenforced, - base_sel: BaseOrAsz::OffsetFrom0, - offset: 0, - length: Len::Len256, - }, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ], - ..Default::default() - }, - ); - - let memory_map_buffer = unsafe { - let start_espi_data = &__start_espi_data as *const u8 as *mut u8; - let end_espi_data = &__end_espi_data as *const u8 as *mut u8; - let espi_data_len = end_espi_data.offset_from(start_espi_data) as usize; - - slice::from_raw_parts_mut(start_espi_data, espi_data_len) - }; - - spawner.must_spawn(espi_service_task(espi, memory_map_buffer)); - - battery_service::init().await; - - spawner.spawn(battery_service::battery_update_service()).unwrap(); - - loop { - embassy_time::Timer::after_secs(10).await; - info!("The uptime is {} secs", embassy_time::Instant::now().as_secs()); - - let data = unsafe { - let start_espi_data = &__start_espi_data as *const u8 as *mut u8; - let end_espi_data = &__end_espi_data as *const u8 as *mut u8; - let espi_data_len = end_espi_data.offset_from(start_espi_data) as usize; - - slice::from_raw_parts_mut(start_espi_data, espi_data_len) - }; - - info!("Memory map contents: {:?}", data[..256]); - } -} diff --git a/examples/rt633/src/bin/espi_battery.rs b/examples/rt633/src/bin/espi_battery.rs deleted file mode 100644 index eb7359765..000000000 --- a/examples/rt633/src/bin/espi_battery.rs +++ /dev/null @@ -1,268 +0,0 @@ -#![no_std] -#![no_main] - -extern crate rt633_examples; - -use battery_service::context::BatteryEvent; -use core::slice::{self}; -use embassy_imxrt::dma::NoDma; -use embassy_time::{Duration, Timer}; -use embedded_batteries_async::smart_battery::SmartBattery; -use embedded_services::{error, info}; - -use battery_service::controller::{Controller, ControllerEvent}; -use battery_service::device::{Device, DeviceId, DynamicBatteryMsgs, StaticBatteryMsgs}; -use battery_service::wrapper::Wrapper; -use bq40z50_rx::Bq40z50R5; -use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; -use embassy_executor::Spawner; -use embassy_imxrt::bind_interrupts; -use embassy_imxrt::espi::BaseOrAsz; -use embassy_imxrt::espi::{Base, Capabilities, Config, Direction, Espi, InterruptHandler, Len, Maxspd, PortConfig}; -use embassy_imxrt::i2c::Async; -use embassy_imxrt::i2c::master::I2cMaster; -use embassy_imxrt::peripherals::ESPI; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::mutex::Mutex; -use static_cell::StaticCell; -use {defmt_rtt as _, panic_probe as _}; - -bind_interrupts!(struct IrqsFg { - FLEXCOMM15 => embassy_imxrt::i2c::InterruptHandler; -}); - -static I2C_BUS_FG: StaticCell< - Mutex>, -> = StaticCell::new(); -static FG_DEVICE: StaticCell = StaticCell::new(); - -/// Wrapper struct for the fuel gauge driver -struct Bq40z50Controller { - driver: Bq40z50R5< - I2cDevice<'static, NoopRawMutex, embassy_imxrt::i2c::master::I2cMaster<'static, embassy_imxrt::i2c::Async>>, - embassy_time::Delay, - >, -} - -embedded_batteries_async::impl_smart_battery_for_wrapper_type!(Bq40z50Controller, driver, >, embassy_time::Delay> as embedded_batteries_async::smart_battery::ErrorType>::Error); - -impl Controller for Bq40z50Controller { - type ControllerError = >, embassy_time::Delay> as embedded_batteries_async::smart_battery::ErrorType>::Error; - - async fn initialize(&mut self) -> Result<(), Self::ControllerError> { - info!("Fuel gauge inited!"); - Ok(()) - } - - async fn get_static_data(&mut self) -> Result { - info!("Sending static data"); - - Ok(StaticBatteryMsgs { ..Default::default() }) - } - - async fn get_dynamic_data(&mut self) -> Result { - info!("Sending dynamic data"); - info!("Voltage = {}", self.voltage().await?); - info!("Current = {}", self.current().await?); - info!("Cycle count = {}", self.cycle_count().await?); - - Ok(DynamicBatteryMsgs { ..Default::default() }) - } - - async fn get_device_event(&mut self) -> ControllerEvent { - loop { - Timer::after_secs(1000000).await; - } - } - - async fn ping(&mut self) -> Result<(), Self::ControllerError> { - info!("Ping!"); - Ok(()) - } - - fn get_timeout(&self) -> Duration { - unimplemented!() - } - - fn set_timeout(&mut self, _duration: Duration) { - unimplemented!() - } -} - -bind_interrupts!(struct Irqs { - ESPI => InterruptHandler; -}); - -// SAFETY: These are symbols defined by the linker and guaranteed to point to valid memory -unsafe extern "C" { - static __start_espi_data: u8; - static __end_espi_data: u8; -} - -#[embassy_executor::task] -async fn battery_publish_task(battery_service: &'static battery_service::Service, fg_device: &'static Device) { - loop { - Timer::after_secs(1).await; - // Get dynamic cache - let cache = fg_device.get_dynamic_battery_cache().await; - - // Send cache data to eSpi service - battery_service - .comms_send( - embedded_services::comms::EndpointID::External(embedded_services::comms::External::Host), - &embedded_services::ec_type::message::BatteryMessage::CycleCount(cache.cycle_count.into()), - ) - .await - .unwrap(); - } -} - -#[embassy_executor::task] -async fn wrapper_task(wrapper: Wrapper<'static, Bq40z50Controller>) { - loop { - wrapper.process().await; - info!("Got new wrapper message"); - } -} - -#[embassy_executor::task] -async fn espi_service_task(espi: embassy_imxrt::espi::Espi<'static>, memory_map_buffer: &'static mut [u8]) -> ! { - let Err(e) = espi_service::task::espi_service(espi, memory_map_buffer).await; - panic!("espi_service_task error: {e:?}"); -} - -#[embassy_executor::task] -async fn battery_service_task( - service: &'static battery_service::Service, - device: [&'static battery_service::device::Device; 1], -) { - if battery_service::task::task(service, device).await.is_err() { - error!("Failed to start battery service") - } -} - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - let p = embassy_imxrt::init(Default::default()); - - embedded_services::init().await; - - let espi = Espi::new( - p.ESPI, - p.PIO7_29, - p.PIO7_26, - p.PIO7_27, - p.PIO7_28, - p.PIO7_30, - p.PIO7_31, - p.PIO7_25, - p.PIO7_24, - Irqs, - Config { - caps: Capabilities { - max_speed: Maxspd::SmallThan20m, - alert_as_a_pin: true, - ..Default::default() - }, - ram_base: 0x2000_0000, - base0_addr: 0x2002_0000, - base1_addr: 0x2003_0000, - status_addr: Some(0x480), - status_base: Base::OffsetFrom0, - ports_config: [ - PortConfig::MailboxShared { - direction: Direction::BidirectionalUnenforced, - base_sel: BaseOrAsz::OffsetFrom0, - offset: 0, - length: Len::Len512, - }, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ], - ..Default::default() - }, - ); - - let memory_map_buffer = unsafe { - let start_espi_data = &__start_espi_data as *const u8 as *mut u8; - let end_espi_data = &__end_espi_data as *const u8 as *mut u8; - let espi_data_len = end_espi_data.offset_from(start_espi_data) as usize; - - slice::from_raw_parts_mut(start_espi_data, espi_data_len) - }; - - spawner.must_spawn(espi_service_task(espi, memory_map_buffer)); - - let config = embassy_imxrt::i2c::master::Config { - speed: embassy_imxrt::i2c::master::Speed::Standard, - duty_cycle: embassy_imxrt::i2c::master::DutyCycle::new(50).unwrap(), - strict_mode: false, - }; - - let i2c_fg = embassy_imxrt::i2c::master::I2cMaster::new_async( - p.FLEXCOMM15, - p.PIOFC15_SCL, - p.PIOFC15_SDA, - IrqsFg, - config, - unsafe { embassy_imxrt::Peri::new_unchecked(NoDma) }, - ) - .unwrap(); - - let i2c_bus_fg = I2C_BUS_FG.init(Mutex::new(i2c_fg)); - - let fg_bus = I2cDevice::new(i2c_bus_fg); - - static BATTERY_SERVICE: battery_service::Service = battery_service::Service::new(); - - let fg = FG_DEVICE.init(Device::new(DeviceId(0))); - - let wrap = Wrapper::new( - fg, - Bq40z50Controller { - driver: Bq40z50R5::new(fg_bus, embassy_time::Delay), - }, - ); - - spawner.must_spawn(wrapper_task(wrap)); - spawner.must_spawn(battery_service_task(&BATTERY_SERVICE, [fg])); - - spawner.must_spawn(battery_publish_task(&BATTERY_SERVICE, fg)); - - if let Err(e) = BATTERY_SERVICE - .execute_event(BatteryEvent { - device_id: DeviceId(0), - event: battery_service::context::BatteryEventInner::DoInit, - }) - .await - { - error!("Error initializing fuel gauge, error: {:?}", e); - } - - loop { - embassy_time::Timer::after_secs(10).await; - info!("The uptime is {} secs", embassy_time::Instant::now().as_secs()); - - let data = unsafe { - let start_espi_data = &__start_espi_data as *const u8 as *mut u8; - let end_espi_data = &__end_espi_data as *const u8 as *mut u8; - let espi_data_len = end_espi_data.offset_from(start_espi_data) as usize; - - slice::from_raw_parts_mut(start_espi_data, espi_data_len) - }; - - info!("Memory map contents: {:?}", data[..64]); - - if let Err(e) = BATTERY_SERVICE - .execute_event(BatteryEvent { - device_id: DeviceId(0), - event: battery_service::context::BatteryEventInner::PollDynamicData, - }) - .await - { - error!("Error getting dynamic fuel gauge data, error: {:?}", e); - } - } -} diff --git a/examples/std/src/bin/battery.rs b/examples/std/src/bin/battery.rs deleted file mode 100644 index 605416e11..000000000 --- a/examples/std/src/bin/battery.rs +++ /dev/null @@ -1,504 +0,0 @@ -use std::convert::Infallible; - -use battery_service::controller::{Controller, ControllerEvent}; -use battery_service::device::{Device, DeviceId, DynamicBatteryMsgs, StaticBatteryMsgs}; -use battery_service::wrapper::Wrapper; -use embassy_executor::Spawner; -use embassy_sync::once_lock::OnceLock; -use embassy_time::{Duration, Timer}; -use embedded_batteries_async::charger::{MilliAmps, MilliVolts}; -use embedded_batteries_async::smart_battery::{ - self, BatteryModeFields, BatteryStatusFields, CapacityModeSignedValue, CapacityModeValue, Cycles, DeciKelvin, - ManufactureDate, MilliAmpsSigned, Minutes, Percent, SmartBattery, SpecificationInfoFields, -}; -use embedded_hal_mock::eh1::i2c::Mock; -use embedded_services::{error, info}; - -mod espi_service { - use battery_service::context::{BatteryEvent, BatteryEventInner}; - use battery_service::device::DeviceId; - use embassy_sync::once_lock::OnceLock; - use embassy_sync::signal::Signal; - use embassy_time::Timer; - use embedded_services::comms::{self, EndpointID, External}; - use embedded_services::ec_type::message::BatteryMessage; - use embedded_services::{GlobalRawMutex, error}; - use log::info; - - pub struct Service { - endpoint: comms::Endpoint, - _signal: Signal, - } - - impl Service { - pub fn new() -> Self { - Service { - endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), - _signal: Signal::new(), - } - } - } - - impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - let msg = message - .data - .get::() - .ok_or(comms::MailboxDelegateError::MessageNotFound)?; - - match msg { - BatteryMessage::CycleCount(cycles) => { - info!("Bat cycles: {cycles}"); - Ok(()) - } - _ => Err(comms::MailboxDelegateError::InvalidData), - } - } - } - - static ESPI_SERVICE: OnceLock = OnceLock::new(); - - pub async fn init() { - let espi_service = ESPI_SERVICE.get_or_init(Service::new); - - comms::register_endpoint(espi_service, &espi_service.endpoint) - .await - .unwrap(); - } - - #[embassy_executor::task] - pub async fn task(battery_service: &'static battery_service::Service) { - let espi_service = ESPI_SERVICE.get().await; - - espi_service - .endpoint - .send( - EndpointID::Internal(comms::Internal::Battery), - &BatteryEvent { - device_id: DeviceId(0), - event: BatteryEventInner::DoInit, - }, - ) - .await - .unwrap(); - - if let Err(e) = battery_service.wait_for_battery_response().await { - error!("Init request failed with {:?}", e); - } - Timer::after_secs(5).await; - - espi_service - .endpoint - .send( - EndpointID::Internal(comms::Internal::Battery), - &BatteryEvent { - device_id: DeviceId(0), - event: BatteryEventInner::PollStaticData, - }, - ) - .await - .unwrap(); - - if let Err(e) = battery_service.wait_for_battery_response().await { - error!("static data request failed with {:?}", e); - } - - loop { - espi_service - .endpoint - .send( - EndpointID::Internal(comms::Internal::Battery), - &BatteryEvent { - device_id: DeviceId(0), - event: BatteryEventInner::PollDynamicData, - }, - ) - .await - .unwrap(); - - if let Err(e) = battery_service.wait_for_battery_response().await { - error!("dynamic data request failed with {:?}", e); - } - Timer::after_secs(5).await; - } - } -} - -struct FuelGaugeController { - driver: MockFuelGaugeDriver, -} - -impl smart_battery::ErrorType for FuelGaugeController { - type Error = Infallible; -} - -impl SmartBattery for FuelGaugeController { - async fn absolute_state_of_charge(&mut self) -> Result { - self.driver.absolute_state_of_charge().await - } - async fn at_rate(&mut self) -> Result { - self.driver.at_rate().await - } - async fn at_rate_ok(&mut self) -> Result { - self.driver.at_rate_ok().await - } - async fn at_rate_time_to_empty(&mut self) -> Result { - self.driver.at_rate_time_to_empty().await - } - async fn at_rate_time_to_full(&mut self) -> Result { - self.driver.at_rate_time_to_full().await - } - async fn average_current(&mut self) -> Result { - self.driver.average_current().await - } - async fn average_time_to_empty(&mut self) -> Result { - self.driver.average_time_to_empty().await - } - async fn average_time_to_full(&mut self) -> Result { - self.driver.average_time_to_full().await - } - async fn battery_mode(&mut self) -> Result { - self.driver.battery_mode().await - } - async fn battery_status(&mut self) -> Result { - self.driver.battery_status().await - } - async fn charging_current(&mut self) -> Result { - self.driver.charging_current().await - } - async fn charging_voltage(&mut self) -> Result { - self.driver.charging_voltage().await - } - async fn current(&mut self) -> Result { - self.driver.current().await - } - async fn cycle_count(&mut self) -> Result { - self.driver.cycle_count().await - } - async fn design_capacity(&mut self) -> Result { - self.driver.design_capacity().await - } - async fn design_voltage(&mut self) -> Result { - self.driver.design_voltage().await - } - async fn device_chemistry(&mut self, chemistry: &mut [u8]) -> Result<(), Self::Error> { - self.driver.device_chemistry(chemistry).await - } - async fn device_name(&mut self, name: &mut [u8]) -> Result<(), Self::Error> { - self.driver.device_name(name).await - } - async fn full_charge_capacity(&mut self) -> Result { - self.driver.full_charge_capacity().await - } - async fn manufacture_date(&mut self) -> Result { - self.driver.manufacture_date().await - } - async fn manufacturer_name(&mut self, name: &mut [u8]) -> Result<(), Self::Error> { - self.driver.manufacturer_name(name).await - } - async fn max_error(&mut self) -> Result { - self.driver.max_error().await - } - async fn relative_state_of_charge(&mut self) -> Result { - self.driver.relative_state_of_charge().await - } - async fn remaining_capacity(&mut self) -> Result { - self.driver.remaining_capacity().await - } - async fn remaining_capacity_alarm(&mut self) -> Result { - self.driver.remaining_capacity_alarm().await - } - async fn remaining_time_alarm(&mut self) -> Result { - self.driver.remaining_time_alarm().await - } - async fn run_time_to_empty(&mut self) -> Result { - self.driver.run_time_to_empty().await - } - async fn serial_number(&mut self) -> Result { - self.driver.serial_number().await - } - async fn set_at_rate(&mut self, rate: CapacityModeSignedValue) -> Result<(), Self::Error> { - self.driver.set_at_rate(rate).await - } - async fn set_battery_mode(&mut self, flags: BatteryModeFields) -> Result<(), Self::Error> { - self.driver.set_battery_mode(flags).await - } - async fn set_remaining_capacity_alarm(&mut self, capacity: CapacityModeValue) -> Result<(), Self::Error> { - self.driver.set_remaining_capacity_alarm(capacity).await - } - async fn set_remaining_time_alarm(&mut self, time: Minutes) -> Result<(), Self::Error> { - self.driver.set_remaining_time_alarm(time).await - } - async fn specification_info(&mut self) -> Result { - self.driver.specification_info().await - } - async fn temperature(&mut self) -> Result { - self.driver.temperature().await - } - async fn voltage(&mut self) -> Result { - self.driver.voltage().await - } -} - -impl Controller for FuelGaugeController { - type ControllerError = Infallible; - - async fn initialize(&mut self) -> Result<(), Self::ControllerError> { - info!("Fuel gauge inited!"); - Ok(()) - } - - async fn get_static_data(&mut self) -> Result { - info!("Sending static data"); - - Ok(StaticBatteryMsgs { ..Default::default() }) - } - - async fn get_dynamic_data(&mut self) -> Result { - info!("Sending dynamic data"); - Ok(DynamicBatteryMsgs { ..Default::default() }) - } - - async fn get_device_event(&mut self) -> ControllerEvent { - loop { - Timer::after_secs(1000000).await; - } - } - - async fn ping(&mut self) -> Result<(), Self::ControllerError> { - info!("Ping!"); - Ok(()) - } - - fn get_timeout(&self) -> Duration { - Duration::from_secs(5) - } - - fn set_timeout(&mut self, _duration: Duration) { - unimplemented!() - } -} - -struct MockFuelGaugeDriver { - _mock_bus: I2c, -} - -impl MockFuelGaugeDriver { - pub fn new(i2c: I2c) -> Self { - MockFuelGaugeDriver { _mock_bus: i2c } - } -} - -impl embedded_batteries_async::smart_battery::ErrorType - for MockFuelGaugeDriver -{ - type Error = Infallible; -} - -impl embedded_batteries_async::smart_battery::SmartBattery - for MockFuelGaugeDriver -{ - async fn remaining_capacity_alarm(&mut self) -> Result { - Ok(CapacityModeValue::MilliAmpUnsigned(0)) - } - - async fn set_remaining_capacity_alarm(&mut self, _capacity: CapacityModeValue) -> Result<(), Self::Error> { - Ok(()) - } - - async fn remaining_time_alarm(&mut self) -> Result { - Ok(0) - } - - async fn set_remaining_time_alarm(&mut self, _time: Minutes) -> Result<(), Self::Error> { - Ok(()) - } - - async fn battery_mode(&mut self) -> Result { - Ok(BatteryModeFields::new()) - } - - async fn set_battery_mode(&mut self, _flags: BatteryModeFields) -> Result<(), Self::Error> { - Ok(()) - } - - async fn at_rate(&mut self) -> Result { - Ok(CapacityModeSignedValue::MilliAmpSigned(0)) - } - - async fn set_at_rate(&mut self, _rate: CapacityModeSignedValue) -> Result<(), Self::Error> { - Ok(()) - } - - async fn at_rate_time_to_full(&mut self) -> Result { - Ok(0) - } - - async fn at_rate_time_to_empty(&mut self) -> Result { - Ok(0) - } - - async fn at_rate_ok(&mut self) -> Result { - Ok(true) - } - - async fn temperature(&mut self) -> Result { - Ok(0) - } - - async fn voltage(&mut self) -> Result { - Ok(0) - } - - async fn charging_voltage(&mut self) -> Result { - Ok(0) - } - - async fn current(&mut self) -> Result { - Ok(0) - } - - async fn charging_current(&mut self) -> Result { - Ok(0) - } - - async fn average_current( - &mut self, - ) -> Result { - Ok(0) - } - - async fn max_error(&mut self) -> Result { - Ok(0) - } - - async fn relative_state_of_charge( - &mut self, - ) -> Result { - Ok(0) - } - - async fn absolute_state_of_charge( - &mut self, - ) -> Result { - Ok(0) - } - - async fn remaining_capacity( - &mut self, - ) -> Result { - Ok(CapacityModeValue::MilliAmpUnsigned(0)) - } - - async fn full_charge_capacity( - &mut self, - ) -> Result { - Ok(CapacityModeValue::MilliAmpUnsigned(0)) - } - - async fn run_time_to_empty(&mut self) -> Result { - Ok(0) - } - - async fn average_time_to_empty(&mut self) -> Result { - Ok(0) - } - - async fn average_time_to_full(&mut self) -> Result { - Ok(0) - } - - async fn battery_status( - &mut self, - ) -> Result { - Ok(BatteryStatusFields::new()) - } - - async fn cycle_count(&mut self) -> Result { - Ok(33) - } - - async fn design_capacity( - &mut self, - ) -> Result { - Ok(CapacityModeValue::MilliAmpUnsigned(0)) - } - - async fn design_voltage(&mut self) -> Result { - Ok(0) - } - - async fn specification_info(&mut self) -> Result { - Ok(SpecificationInfoFields::new()) - } - - async fn manufacture_date( - &mut self, - ) -> Result { - Ok(ManufactureDate::new()) - } - - async fn serial_number(&mut self) -> Result { - Ok(0) - } - - async fn manufacturer_name(&mut self, _name: &mut [u8]) -> Result<(), Self::Error> { - Ok(()) - } - - async fn device_name(&mut self, _name: &mut [u8]) -> Result<(), Self::Error> { - Ok(()) - } - - async fn device_chemistry(&mut self, _chemistry: &mut [u8]) -> Result<(), Self::Error> { - Ok(()) - } -} - -#[embassy_executor::task] -async fn wrapper_task(wrapper: Wrapper<'static, FuelGaugeController>) { - loop { - wrapper.process().await; - info!("Got new wrapper message"); - } -} - -#[embassy_executor::task] -async fn battery_service_task( - service: &'static battery_service::Service, - device: [&'static battery_service::device::Device; 1], -) { - if let Err(e) = battery_service::task::task(service, device).await { - error!("Failed to start battery service with error {:?}", e) - } -} - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - env_logger::builder().filter_level(log::LevelFilter::Info).init(); - - let expectations = vec![]; - - static DEV: OnceLock = OnceLock::new(); - - let dev = DEV.get_or_init(|| Device::new(DeviceId(0))); - - static SERVICE: battery_service::Service = battery_service::Service::new(); - - let wrap = Wrapper::new( - dev, - FuelGaugeController { - driver: MockFuelGaugeDriver::new(Mock::new(&expectations)), - }, - ); - - embedded_services::init().await; - info!("services init'd"); - - espi_service::init().await; - info!("espi service init'd"); - - spawner.must_spawn(espi_service::task(&SERVICE)); - spawner.must_spawn(wrapper_task(wrap)); - spawner.must_spawn(battery_service_task(&SERVICE, [dev])); -} From 0038225e35de1529d0eda38f3bb80505c1f99b8d Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Fri, 6 Feb 2026 21:15:33 -0800 Subject: [PATCH 16/79] Mark thermal service methods pub (#708) A few methods were mistakenly made `pub(crate)` during refactor that I didn't catch. This PR reverts them back to `pub`. --- thermal-service/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index 312f34137..4057d4012 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -95,7 +95,7 @@ impl Service { } /// Send a thermal event - pub(crate) async fn send_event(&self, event: Event) { + pub async fn send_event(&self, event: Event) { self.context.send_event(event).await } @@ -120,11 +120,7 @@ impl Service { } /// Send a request to a sensor through the thermal service instead of directly. - pub(crate) async fn execute_sensor_request( - &self, - id: sensor::DeviceId, - request: sensor::Request, - ) -> sensor::Response { + pub async fn execute_sensor_request(&self, id: sensor::DeviceId, request: sensor::Request) -> sensor::Response { self.context.execute_sensor_request(id, request).await } @@ -144,7 +140,7 @@ impl Service { } /// Send a request to a fan through the thermal service instead of directly. - pub(crate) async fn execute_fan_request(&self, id: fan::DeviceId, request: fan::Request) -> fan::Response { + pub async fn execute_fan_request(&self, id: fan::DeviceId, request: fan::Request) -> fan::Response { self.context.execute_fan_request(id, request).await } } From 930a97c7e3c67bf479a9cb0aae866826d419fdee Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Tue, 10 Feb 2026 08:46:30 -0800 Subject: [PATCH 17/79] CFU Service Refactor (#702) Refactor CFU service and move embedded_services::cfu module to cfu_service - Updated task function in cfu-service to accept a CfuClient reference directly. - Adjusted buffer and splitter tasks to utilize the new CfuClient for device registration and request handling. - Refactored examples As a consequence of moving CFU data structures out of embedded-service, type-c service needs to take a dependency on cfu-service. Chatted with @RobertZ2011 offline and verified that the eventual goal is to move CFU out of type-c service by abstracting out a firmware update trait that the type-c service will use and the CFU service will impl. --- Cargo.lock | 1 + Cargo.toml | 1 + cfu-service/src/buffer.rs | 88 +++++--- .../src/cfu => cfu-service/src}/component.rs | 13 +- cfu-service/src/lib.rs | 200 ++++++++++++++++-- cfu-service/src/splitter.rs | 109 ++++++---- cfu-service/src/task.rs | 15 +- embedded-service/src/cfu/mod.rs | 169 --------------- embedded-service/src/lib.rs | 2 - examples/rt685s-evk/Cargo.lock | 15 ++ examples/rt685s-evk/Cargo.toml | 2 + examples/rt685s-evk/src/bin/type_c.rs | 8 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 17 +- examples/std/Cargo.lock | 1 + examples/std/src/bin/cfu_buffer.rs | 62 +++--- examples/std/src/bin/cfu_client.rs | 37 ++-- examples/std/src/bin/cfu_splitter.rs | 62 +++--- examples/std/src/bin/type_c/external.rs | 8 +- examples/std/src/bin/type_c/service.rs | 7 +- examples/std/src/bin/type_c/ucsi.rs | 8 +- examples/std/src/bin/type_c/unconstrained.rs | 8 +- type-c-service/Cargo.toml | 3 + type-c-service/src/task.rs | 21 +- type-c-service/src/wrapper/backing.rs | 7 +- type-c-service/src/wrapper/cfu.rs | 2 +- type-c-service/src/wrapper/message.rs | 4 +- type-c-service/src/wrapper/mod.rs | 21 +- 27 files changed, 498 insertions(+), 393 deletions(-) rename {embedded-service/src/cfu => cfu-service/src}/component.rs (97%) delete mode 100644 embedded-service/src/cfu/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 538958eff..70d6b11bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2350,6 +2350,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "cfu-service", "critical-section", "defmt 0.3.100", "embassy-futures", diff --git a/Cargo.toml b/Cargo.toml index f9c4e6509..2e634339f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ embassy-sync = "0.7.2" embassy-time = "0.5.0" embassy-time-driver = "0.2.1" embedded-batteries-async = "0.3" +cfu-service = { path = "./cfu-service" } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } embedded-hal = "1.0" embedded-hal-async = "1.0" diff --git a/cfu-service/src/buffer.rs b/cfu-service/src/buffer.rs index 3494c87f3..d0300e00a 100644 --- a/cfu-service/src/buffer.rs +++ b/cfu-service/src/buffer.rs @@ -10,14 +10,9 @@ use embassy_sync::{ }; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::{ - GlobalRawMutex, - cfu::{ - self, - component::{CfuDevice, InternalResponseData, RequestData}, - }, - error, intrusive_list, trace, -}; +use embedded_services::{GlobalRawMutex, error, intrusive_list, trace}; + +use crate::component::{CfuDevice, InternalResponseData, RequestData}; /// Internal state for [`Buffer`] #[derive(Copy, Clone, Default)] @@ -115,9 +110,11 @@ impl<'a> Buffer<'a> { } /// Process a fw version request - async fn process_get_fw_version(&self) -> InternalResponseData { - if let Ok(InternalResponseData::FwVersionResponse(mut response)) = - cfu::route_request(self.buffered_id, RequestData::FwVersionRequest).await + async fn process_get_fw_version(&self, cfu_client: &crate::CfuClient) -> InternalResponseData { + if let Ok(InternalResponseData::FwVersionResponse(mut response)) = cfu_client + .context + .route_request(self.buffered_id, RequestData::FwVersionRequest) + .await { // Update the component ID in the response to match our external ID response.component_info[0].component_id = self.cfu_device.component_id(); @@ -128,8 +125,12 @@ impl<'a> Buffer<'a> { } } - async fn process_abort_update(&self) -> InternalResponseData { - match cfu::route_request(self.buffered_id, RequestData::AbortUpdate).await { + async fn process_abort_update(&self, cfu_client: &crate::CfuClient) -> InternalResponseData { + match cfu_client + .context + .route_request(self.buffered_id, RequestData::AbortUpdate) + .await + { Ok(response) => response, Err(e) => { error!("Failed to abort update for device {}: {:?}", self.buffered_id, e); @@ -139,11 +140,13 @@ impl<'a> Buffer<'a> { } /// Process a give offer request - async fn process_give_offer(&self, offer: &FwUpdateOffer) -> InternalResponseData { + async fn process_give_offer(&self, offer: &FwUpdateOffer, cfu_client: &crate::CfuClient) -> InternalResponseData { let mut offer = *offer; offer.component_info.component_id = self.buffered_id; - if let Ok(response @ InternalResponseData::OfferResponse(_)) = - cfu::route_request(self.buffered_id, RequestData::GiveOffer(offer)).await + if let Ok(response @ InternalResponseData::OfferResponse(_)) = cfu_client + .context + .route_request(self.buffered_id, RequestData::GiveOffer(offer)) + .await { response } else { @@ -157,7 +160,12 @@ impl<'a> Buffer<'a> { } /// Process update content - async fn process_give_content(&self, state: &mut State, content: &FwUpdateContentCommand) -> InternalResponseData { + async fn process_give_content( + &self, + state: &mut State, + content: &FwUpdateContentCommand, + cfu_client: &crate::CfuClient, + ) -> InternalResponseData { // Clear out any pending response if this is a new FW update if content.header.flags & FW_UPDATE_FLAG_FIRST_BLOCK != 0 { state.pending_response = None; @@ -171,7 +179,11 @@ impl<'a> Buffer<'a> { trace!("Content successfully buffered"); } else { // Buffered component can accept new content, send it - if let Err(e) = cfu::send_device_request(self.buffered_id, RequestData::GiveContent(*content)).await { + if let Err(e) = cfu_client + .context + .send_device_request(self.buffered_id, RequestData::GiveContent(*content)) + .await + { error!( "Failed to send content to buffered component {:?}: {:?}", self.buffered_id, e @@ -181,7 +193,12 @@ impl<'a> Buffer<'a> { } // Wait for a response from the buffered component - match with_timeout(self.config.buffer_timeout, cfu::wait_device_response(self.buffered_id)).await { + match with_timeout( + self.config.buffer_timeout, + cfu_client.context.wait_device_response(self.buffered_id), + ) + .await + { Err(TimeoutError) => { // Component didn't respond in time state.component_busy = true; @@ -242,7 +259,7 @@ impl<'a> Buffer<'a> { } /// Wait for an event - pub async fn wait_event(&self) -> Event { + pub async fn wait_event(&self, cfu_client: &crate::CfuClient) -> Event { let is_busy = self.state.lock().await.component_busy; match select3( // Wait for a buffered content request @@ -250,7 +267,7 @@ impl<'a> Buffer<'a> { // Wait for a request from the host self.cfu_device.wait_request(), // Wait for response from the buffered component - cfu::wait_device_response(self.buffered_id), + cfu_client.context.wait_device_response(self.buffered_id), ) .await { @@ -275,14 +292,18 @@ impl<'a> Buffer<'a> { } /// Top-level event processing function - pub async fn process(&self, event: Event) -> Option { + pub async fn process(&self, event: Event, cfu_client: &crate::CfuClient) -> Option { let mut state = self.state.lock().await; match event { - Event::CfuRequest(request) => Some(self.process_request(&mut state, request).await), + Event::CfuRequest(request) => Some(self.process_request(&mut state, request, cfu_client).await), Event::BufferedContent(content) => { // Send the buffered content to the component // Don't need to wait for a response here, the response will be caught later by either [`wait_event`] or [`process_give_content`] - if let Err(e) = cfu::send_device_request(self.buffered_id, RequestData::GiveContent(content)).await { + if let Err(e) = cfu_client + .context + .send_device_request(self.buffered_id, RequestData::GiveContent(content)) + .await + { error!( "Failed to send content to buffered component {:?}: {:?}", self.buffered_id, e @@ -303,23 +324,28 @@ impl<'a> Buffer<'a> { } /// Process a CFU message and produce a response - async fn process_request(&self, state: &mut State, request: RequestData) -> InternalResponseData { + async fn process_request( + &self, + state: &mut State, + request: RequestData, + cfu_client: &crate::CfuClient, + ) -> InternalResponseData { match request { RequestData::FwVersionRequest => { trace!("Got FwVersionRequest"); - self.process_get_fw_version().await + self.process_get_fw_version(cfu_client).await } RequestData::GiveOffer(offer) => { trace!("Got GiveOffer"); - self.process_give_offer(&offer).await + self.process_give_offer(&offer, cfu_client).await } RequestData::GiveContent(content) => { trace!("Got GiveContent"); - self.process_give_content(state, &content).await + self.process_give_content(state, &content, cfu_client).await } RequestData::AbortUpdate => { trace!("Got AbortUpdate"); - self.process_abort_update().await + self.process_abort_update(cfu_client).await } RequestData::FinalizeUpdate => { trace!("Got FinalizeUpdate"); @@ -356,7 +382,7 @@ impl<'a> Buffer<'a> { } /// Register the buffer with all relevant services - pub async fn register(&'static self) -> Result<(), intrusive_list::Error> { - cfu::register_device(&self.cfu_device).await + pub fn register(&'static self, cfu_client: &crate::CfuClient) -> Result<(), intrusive_list::Error> { + cfu_client.context.register_device(&self.cfu_device) } } diff --git a/embedded-service/src/cfu/component.rs b/cfu-service/src/component.rs similarity index 97% rename from embedded-service/src/cfu/component.rs rename to cfu-service/src/component.rs index d89773b35..6cac8120f 100644 --- a/embedded-service/src/cfu/component.rs +++ b/cfu-service/src/component.rs @@ -9,9 +9,8 @@ use embedded_cfu_protocol::writer::{CfuWriterAsync, CfuWriterError}; use heapless::Vec; use super::CfuError; -use crate::GlobalRawMutex; -use crate::cfu::route_request; -use crate::intrusive_list; +use embedded_services::GlobalRawMutex; +use embedded_services::intrusive_list; /// Component internal update state #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -222,7 +221,7 @@ impl CfuComponentDefault { } } /// wait for a request and process it - pub async fn process_request(&self) -> Result<(), CfuError> { + pub async fn process_request(&self, cfu_client: &'static crate::CfuClient) -> Result<(), CfuError> { match self.device.wait_request().await { RequestData::FwVersionRequest => { let fwv = self.get_fw_version().await.map_err(CfuError::ProtocolError)?; @@ -247,8 +246,10 @@ impl CfuComponentDefault { // panic safety: adding 1 here is safe because MAX_CMPT_COUNT is 1 more than MAX_SUBCMPT_COUNT for (index, id) in arr.iter().enumerate() { //info!("Forwarding GetFwVersion command to sub-component: {}", id); - if let InternalResponseData::FwVersionResponse(fwv) = - route_request(*id, RequestData::FwVersionRequest).await? + if let InternalResponseData::FwVersionResponse(fwv) = cfu_client + .context + .route_request(*id, RequestData::FwVersionRequest) + .await? { comp_info[index + 1] = fwv .component_info diff --git a/cfu-service/src/lib.rs b/cfu-service/src/lib.rs index 9e6b4948b..9e2ff4035 100644 --- a/cfu-service/src/lib.rs +++ b/cfu-service/src/lib.rs @@ -1,20 +1,20 @@ #![no_std] +use embassy_sync::channel::Channel; use embedded_cfu_protocol::client::CfuReceiveContent; use embedded_cfu_protocol::components::CfuComponentTraits; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::cfu::component::*; -use embedded_services::cfu::{CfuError, ContextToken}; -use embedded_services::{comms, error, info, trace}; +use embedded_services::{GlobalRawMutex, comms, error, info, intrusive_list, trace}; pub mod buffer; +pub mod component; pub mod host; pub mod splitter; pub mod task; pub struct CfuClient { /// Cfu Client context - context: ContextToken, + context: ClientContext, /// Comms endpoint tp: comms::Endpoint, } @@ -37,21 +37,32 @@ impl CfuReceiveContent for CfuClient { impl CfuClient { /// Create a new Cfu Client - pub fn create() -> Option { - Some(Self { - context: ContextToken::create()?, + pub async fn new(service_storage: &'static embassy_sync::once_lock::OnceLock) -> &'static Self { + let service_storage = service_storage.get_or_init(|| Self { + context: ClientContext::new(), tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Nonvol)), - }) + }); + + service_storage.init().await; + + service_storage } + + async fn init(&'static self) { + if comms::register_endpoint(self, &self.tp).await.is_err() { + error!("Failed to register cfu endpoint"); + } + } + pub async fn process_request(&self) -> Result<(), CfuError> { let request = self.context.wait_request().await; //let device = self.context.get_device(request.id).await?; let comp = request.id; match request.data { - RequestData::FwVersionRequest => { + component::RequestData::FwVersionRequest => { info!("Received FwVersionRequest, comp {}", comp); - if let Ok(device) = self.context.get_device(comp).await { + if let Ok(device) = self.context.get_device(comp) { let resp = device .execute_device_request(request.data) .await @@ -60,7 +71,7 @@ impl CfuClient { // TODO replace with signal to component to get its own fw version //cfu::send_request(comp, RequestData::FwVersionRequest).await?; match resp { - InternalResponseData::FwVersionResponse(r) => { + component::InternalResponseData::FwVersionResponse(r) => { let ver = r.component_info[0].fw_version; info!("got fw version {:?} for comp {}", ver, comp); } @@ -74,15 +85,15 @@ impl CfuClient { } Err(CfuError::InvalidComponent) } - RequestData::GiveContent(_content_cmd) => Ok(()), - RequestData::GiveOffer(_offer_cmd) => Ok(()), - RequestData::PrepareComponentForUpdate => Ok(()), - RequestData::AbortUpdate => Ok(()), - RequestData::FinalizeUpdate => Ok(()), - RequestData::GiveOfferExtended(_) => { + component::RequestData::GiveContent(_content_cmd) => Ok(()), + component::RequestData::GiveOffer(_offer_cmd) => Ok(()), + component::RequestData::PrepareComponentForUpdate => Ok(()), + component::RequestData::AbortUpdate => Ok(()), + component::RequestData::FinalizeUpdate => Ok(()), + component::RequestData::GiveOfferExtended(_) => { // Don't currently support extended offers self.context - .send_response(InternalResponseData::OfferResponse( + .send_response(component::InternalResponseData::OfferResponse( FwUpdateOfferResponse::new_with_failure( HostToken::Driver, OfferRejectReason::InvalidComponent, @@ -92,10 +103,10 @@ impl CfuClient { .await; Ok(()) } - RequestData::GiveOfferInformation(_) => { + component::RequestData::GiveOfferInformation(_) => { // Don't currently support information offers self.context - .send_response(InternalResponseData::OfferResponse( + .send_response(component::InternalResponseData::OfferResponse( FwUpdateOfferResponse::new_with_failure( HostToken::Driver, OfferRejectReason::InvalidComponent, @@ -107,6 +118,155 @@ impl CfuClient { } } } + + pub fn register_device( + &self, + device: &'static impl component::CfuDeviceContainer, + ) -> Result<(), intrusive_list::Error> { + self.context.register_device(device) + } + + pub async fn route_request( + &self, + to: ComponentId, + request: component::RequestData, + ) -> Result { + self.context.route_request(to, request).await + } } impl comms::MailboxDelegate for CfuClient {} + +/// Error type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CfuError { + /// Image did not pass validation + BadImage, + /// Component either doesn't exist + InvalidComponent, + /// Component is busy + ComponentBusy, + /// Component encountered a protocol error during execution + ProtocolError(CfuProtocolError), +} + +/// Request to the power policy service +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Request { + /// Component that sent this request + pub id: ComponentId, + /// Request data + pub data: component::RequestData, +} + +/// Cfu context +pub struct ClientContext { + /// Registered devices + devices: embedded_services::intrusive_list::IntrusiveList, + /// Request to components + request: Channel, + /// Response from components + response: Channel, +} + +impl Default for ClientContext { + fn default() -> Self { + Self::new() + } +} + +impl ClientContext { + pub fn new() -> Self { + Self { + devices: embedded_services::intrusive_list::IntrusiveList::new(), + request: Channel::new(), + response: Channel::new(), + } + } + + /// Register a device with the Cfu Client service + fn register_device( + &self, + device: &'static impl component::CfuDeviceContainer, + ) -> Result<(), intrusive_list::Error> { + let device = device.get_cfu_component_device(); + if self.get_device(device.component_id()).is_ok() { + return Err(intrusive_list::Error::NodeAlreadyInList); + } + + self.devices.push(device) + } + + /// Convenience function to send a request to the Cfu service + pub async fn send_request( + &self, + from: ComponentId, + request: component::RequestData, + ) -> Result { + self.request + .send(Request { + id: from, + data: request, + }) + .await; + Ok(self.response.receive().await) + } + + /// Convenience function to route a request to a specific component + pub async fn route_request( + &self, + to: ComponentId, + request: component::RequestData, + ) -> Result { + let device = self.get_device(to)?; + device + .execute_device_request(request) + .await + .map_err(CfuError::ProtocolError) + } + + /// Send a request to the specific CFU device, but don't wait for a response + pub async fn send_device_request(&self, to: ComponentId, request: component::RequestData) -> Result<(), CfuError> { + let device = self.get_device(to)?; + device.send_request(request).await; + Ok(()) + } + + /// Wait for a response from the specific CFU device + pub async fn wait_device_response(&self, to: ComponentId) -> Result { + let device = self.get_device(to)?; + Ok(device.wait_response().await) + } + + /// Wait for a cfu request + pub async fn wait_request(&self) -> Request { + self.request.receive().await + } + + /// Send a response to a cfu request + pub async fn send_response(&self, response: component::InternalResponseData) { + self.response.send(response).await + } + + /// Get a device by its ID + pub fn get_device(&self, id: ComponentId) -> Result<&'static component::CfuDevice, CfuError> { + for device in &self.devices { + if let Some(data) = device.data::() { + if data.component_id() == id { + return Ok(data); + } + } else { + error!("Non-device located in devices list"); + } + } + + Err(CfuError::InvalidComponent) + } + + /// Provides access to the device list + pub fn devices(&self) -> &intrusive_list::IntrusiveList { + &self.devices + } +} diff --git a/cfu-service/src/splitter.rs b/cfu-service/src/splitter.rs index 055d2936b..5210619da 100644 --- a/cfu-service/src/splitter.rs +++ b/cfu-service/src/splitter.rs @@ -3,15 +3,10 @@ use core::{future::Future, iter::zip}; +use crate::component; use embassy_futures::join::{join, join3, join4}; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::{ - cfu::{ - self, - component::{CfuDevice, InternalResponseData, RequestData}, - }, - error, intrusive_list, trace, -}; +use embedded_services::{error, intrusive_list, trace}; /// Trait containing customization functionality for [`Splitter`] pub trait Customization { @@ -28,7 +23,7 @@ pub trait Customization { /// Splitter struct pub struct Splitter<'a, C: Customization> { /// CFU device - cfu_device: CfuDevice, + cfu_device: component::CfuDevice, /// Component ID for each individual device devices: &'a [ComponentId], /// Customization for the Splitter @@ -45,7 +40,7 @@ impl<'a, C: Customization> Splitter<'a, C> { None } else { Some(Self { - cfu_device: CfuDevice::new(component_id), + cfu_device: component::CfuDevice::new(component_id), devices, customization, }) @@ -53,33 +48,35 @@ impl<'a, C: Customization> Splitter<'a, C> { } /// Create a new invalid FW version response - fn create_invalid_fw_version_response(&self) -> InternalResponseData { + fn create_invalid_fw_version_response(&self) -> component::InternalResponseData { let dev_inf = FwVerComponentInfo::new(FwVersion::new(0xffffffff), self.cfu_device.component_id()); let comp_info: [FwVerComponentInfo; MAX_CMPT_COUNT] = [dev_inf; MAX_CMPT_COUNT]; - InternalResponseData::FwVersionResponse(GetFwVersionResponse { + component::InternalResponseData::FwVersionResponse(GetFwVersionResponse { header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), component_info: comp_info, }) } /// Create a content rejection response - fn create_content_rejection(sequence: u16) -> InternalResponseData { - InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + fn create_content_rejection(sequence: u16) -> component::InternalResponseData { + component::InternalResponseData::ContentResponse(FwUpdateContentResponse::new( sequence, CfuUpdateContentResponseStatus::ErrorInvalid, )) } /// Process a fw version request - async fn process_get_fw_version(&self) -> InternalResponseData { + async fn process_get_fw_version(&self, cfu_client: &crate::CfuClient) -> component::InternalResponseData { let mut versions = [GetFwVersionResponse { header: Default::default(), component_info: Default::default(), }; MAX_SUPPORTED_DEVICES]; let success = map_slice_join(self.devices, &mut versions, |device_id| async move { - if let Ok(InternalResponseData::FwVersionResponse(version_info)) = - cfu::route_request(*device_id, RequestData::FwVersionRequest).await + if let Ok(component::InternalResponseData::FwVersionResponse(version_info)) = cfu_client + .context + .route_request(*device_id, component::RequestData::FwVersionRequest) + .await { Some(version_info) } else { @@ -94,14 +91,18 @@ impl<'a, C: Customization> Splitter<'a, C> { // The overall component version comes first overall_version.component_info[0].component_id = self.cfu_device.component_id(); - InternalResponseData::FwVersionResponse(overall_version) + component::InternalResponseData::FwVersionResponse(overall_version) } else { self.create_invalid_fw_version_response() } } /// Process a give offer request - async fn process_give_offer(&self, offer: &FwUpdateOffer) -> InternalResponseData { + async fn process_give_offer( + &self, + offer: &FwUpdateOffer, + cfu_client: &crate::CfuClient, + ) -> component::InternalResponseData { let mut offer_responses = [FwUpdateOfferResponse::default(); MAX_SUPPORTED_DEVICES]; let success = map_slice_join(self.devices, &mut offer_responses, |device_id| async move { @@ -109,8 +110,10 @@ impl<'a, C: Customization> Splitter<'a, C> { // Override with the correct component ID for the device offer.component_info.component_id = *device_id; - if let Ok(InternalResponseData::OfferResponse(response)) = - cfu::route_request(*device_id, RequestData::GiveOffer(offer)).await + if let Ok(component::InternalResponseData::OfferResponse(response)) = cfu_client + .context + .route_request(*device_id, component::RequestData::GiveOffer(offer)) + .await { Some(response) } else { @@ -121,19 +124,27 @@ impl<'a, C: Customization> Splitter<'a, C> { .await; if success && let Some(offer_responses_slice) = offer_responses.get(..self.devices.len()) { - InternalResponseData::OfferResponse(self.customization.resolve_offer_response(offer_responses_slice)) + component::InternalResponseData::OfferResponse( + self.customization.resolve_offer_response(offer_responses_slice), + ) } else { self.create_invalid_fw_version_response() } } /// Process update content - async fn process_give_content(&self, content: &FwUpdateContentCommand) -> InternalResponseData { + async fn process_give_content( + &self, + content: &FwUpdateContentCommand, + cfu_client: &crate::CfuClient, + ) -> component::InternalResponseData { let mut content_responses = [FwUpdateContentResponse::default(); MAX_SUPPORTED_DEVICES]; let success = map_slice_join(self.devices, &mut content_responses, |device_id| async move { - if let Ok(InternalResponseData::ContentResponse(response)) = - cfu::route_request(*device_id, RequestData::GiveContent(*content)).await + if let Ok(component::InternalResponseData::ContentResponse(response)) = cfu_client + .context + .route_request(*device_id, component::RequestData::GiveContent(*content)) + .await { Some(response) } else { @@ -144,57 +155,63 @@ impl<'a, C: Customization> Splitter<'a, C> { .await; if success && let Some(content_responses_slice) = content_responses.get(..self.devices.len()) { - InternalResponseData::ContentResponse(self.customization.resolve_content_response(content_responses_slice)) + component::InternalResponseData::ContentResponse( + self.customization.resolve_content_response(content_responses_slice), + ) } else { Self::create_content_rejection(content.header.sequence_num) } } /// Wait for a CFU message - pub async fn wait_request(&self) -> RequestData { + pub async fn wait_request(&self) -> component::RequestData { self.cfu_device.wait_request().await } /// Process a CFU message and produce a response - pub async fn process_request(&self, request: RequestData) -> InternalResponseData { + pub async fn process_request( + &self, + request: component::RequestData, + cfu_client: &crate::CfuClient, + ) -> component::InternalResponseData { match request { - RequestData::FwVersionRequest => { + component::RequestData::FwVersionRequest => { trace!("Got FwVersionRequest"); - self.process_get_fw_version().await + self.process_get_fw_version(cfu_client).await } - RequestData::GiveOffer(offer) => { + component::RequestData::GiveOffer(offer) => { trace!("Got GiveOffer"); - self.process_give_offer(&offer).await + self.process_give_offer(&offer, cfu_client).await } - RequestData::GiveContent(content) => { + component::RequestData::GiveContent(content) => { trace!("Got GiveContent"); - self.process_give_content(&content).await + self.process_give_content(&content, cfu_client).await } - RequestData::AbortUpdate => { + component::RequestData::AbortUpdate => { trace!("Got AbortUpdate"); - InternalResponseData::ComponentPrepared + component::InternalResponseData::ComponentPrepared } - RequestData::FinalizeUpdate => { + component::RequestData::FinalizeUpdate => { trace!("Got FinalizeUpdate"); - InternalResponseData::ComponentPrepared + component::InternalResponseData::ComponentPrepared } - RequestData::PrepareComponentForUpdate => { + component::RequestData::PrepareComponentForUpdate => { trace!("Got PrepareComponentForUpdate"); - InternalResponseData::ComponentPrepared + component::InternalResponseData::ComponentPrepared } - RequestData::GiveOfferExtended(_) => { + component::RequestData::GiveOfferExtended(_) => { trace!("Got GiveExtendedOffer"); // Extended offers are not currently supported - InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_with_failure( + component::InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_with_failure( HostToken::Driver, OfferRejectReason::InvalidComponent, OfferStatus::Reject, )) } - RequestData::GiveOfferInformation(_) => { + component::RequestData::GiveOfferInformation(_) => { trace!("Got GiveOfferInformation"); // Offer information is not currently supported - InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_with_failure( + component::InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_with_failure( HostToken::Driver, OfferRejectReason::InvalidComponent, OfferStatus::Reject, @@ -204,12 +221,12 @@ impl<'a, C: Customization> Splitter<'a, C> { } /// Send a response to the CFU message - pub async fn send_response(&self, response: InternalResponseData) { + pub async fn send_response(&self, response: component::InternalResponseData) { self.cfu_device.send_response(response).await; } - pub async fn register(&'static self) -> Result<(), intrusive_list::Error> { - cfu::register_device(&self.cfu_device).await + pub fn register(&'static self, cfu_client: &crate::CfuClient) -> Result<(), intrusive_list::Error> { + cfu_client.context.register_device(&self.cfu_device) } } diff --git a/cfu-service/src/task.rs b/cfu-service/src/task.rs index ef71d8cfa..f4dd11879 100644 --- a/cfu-service/src/task.rs +++ b/cfu-service/src/task.rs @@ -1,21 +1,12 @@ -use embassy_sync::once_lock::OnceLock; -use embedded_services::{comms, error, info}; +use embedded_services::{error, info}; use crate::CfuClient; -pub async fn task() { +pub async fn task(cfu_client: &'static CfuClient) { info!("Starting cfu client task"); - static CLIENT: OnceLock = OnceLock::new(); - #[allow(clippy::expect_used)] // panic safety: singleton panic on initialization - let cfuclient = CLIENT.get_or_init(|| CfuClient::create().expect("cfu client singleton already initialized")); - - if comms::register_endpoint(cfuclient, &cfuclient.tp).await.is_err() { - error!("Failed to register cfu endpoint"); - return; - } loop { - if let Err(e) = cfuclient.process_request().await { + if let Err(e) = cfu_client.process_request().await { error!("Error processing request: {:?}", e); } } diff --git a/embedded-service/src/cfu/mod.rs b/embedded-service/src/cfu/mod.rs deleted file mode 100644 index 3c3f97ead..000000000 --- a/embedded-service/src/cfu/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Cfu Service related data structures and messages -//pub mod action; -pub mod component; - -use core::sync::atomic::{AtomicBool, Ordering}; - -use crate::GlobalRawMutex; -use embassy_sync::channel::Channel; -use embassy_sync::once_lock::OnceLock; -use embedded_cfu_protocol::protocol_definitions::{CfuProtocolError, ComponentId}; - -use crate::cfu::component::{CfuDevice, CfuDeviceContainer, DEVICE_CHANNEL_SIZE, InternalResponseData, RequestData}; -use crate::{error, intrusive_list}; - -/// Error type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CfuError { - /// Image did not pass validation - BadImage, - /// Component either doesn't exist - InvalidComponent, - /// Component is busy - ComponentBusy, - /// Component encountered a protocol error during execution - ProtocolError(CfuProtocolError), -} - -/// Request to the power policy service -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Request { - /// Component that sent this request - pub id: ComponentId, - /// Request data - pub data: RequestData, -} - -/// Cfu context -struct ClientContext { - /// Registered devices - devices: intrusive_list::IntrusiveList, - /// Request to components - request: Channel, - /// Response from components - response: Channel, -} - -impl ClientContext { - fn new() -> Self { - Self { - devices: intrusive_list::IntrusiveList::new(), - request: Channel::new(), - response: Channel::new(), - } - } -} - -static CONTEXT: OnceLock = OnceLock::new(); - -/// Init Cfu Client service -pub fn init() { - CONTEXT.get_or_init(ClientContext::new); -} - -/// Register a device with the Cfu Client service -pub async fn register_device(device: &'static impl CfuDeviceContainer) -> Result<(), intrusive_list::Error> { - let device = device.get_cfu_component_device(); - if get_device(device.component_id()).await.is_some() { - return Err(intrusive_list::Error::NodeAlreadyInList); - } - - CONTEXT.get().await.devices.push(device) -} - -/// Find a device by its ID -async fn get_device(id: ComponentId) -> Option<&'static CfuDevice> { - for device in &CONTEXT.get().await.devices { - if let Some(data) = device.data::() { - if data.component_id() == id { - return Some(data); - } - } else { - error!("Non-device located in devices list"); - } - } - - None -} - -/// Convenience function to send a request to the Cfu service -pub async fn send_request(from: ComponentId, request: RequestData) -> Result { - let context = CONTEXT.get().await; - context - .request - .send(Request { - id: from, - data: request, - }) - .await; - Ok(context.response.receive().await) -} - -/// Convenience function to route a request to a specific component -pub async fn route_request(to: ComponentId, request: RequestData) -> Result { - if let Some(device) = get_device(to).await { - device - .execute_device_request(request) - .await - .map_err(CfuError::ProtocolError) - } else { - Err(CfuError::InvalidComponent) - } -} - -/// Send a request to the specific CFU device, but don't wait for a response -pub async fn send_device_request(to: ComponentId, request: RequestData) -> Result<(), CfuError> { - if let Some(device) = get_device(to).await { - device.send_request(request).await; - Ok(()) - } else { - Err(CfuError::InvalidComponent) - } -} - -/// Wait for a response from the specific CFU device -pub async fn wait_device_response(to: ComponentId) -> Result { - if let Some(device) = get_device(to).await { - Ok(device.wait_response().await) - } else { - Err(CfuError::InvalidComponent) - } -} - -/// Singleton struct to give access to the cfu client context -pub struct ContextToken(()); - -impl ContextToken { - /// Create a new context token, returning None if this function has been called before - pub fn create() -> Option { - static INIT: AtomicBool = AtomicBool::new(false); - if INIT.load(Ordering::SeqCst) { - return None; - } - - INIT.store(true, Ordering::SeqCst); - Some(ContextToken(())) - } - - /// Wait for a cfu request - pub async fn wait_request(&self) -> Request { - CONTEXT.get().await.request.receive().await - } - - /// Send a response to a cfu request - pub async fn send_response(&self, response: InternalResponseData) { - CONTEXT.get().await.response.send(response).await - } - - /// Get a device by its ID - pub async fn get_device(&self, id: ComponentId) -> Result<&'static CfuDevice, CfuError> { - get_device(id).await.ok_or(CfuError::InvalidComponent) - } - - /// Provides access to the device list - pub async fn devices(&self) -> &intrusive_list::IntrusiveList { - &CONTEXT.get().await.devices - } -} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 660aaf5af..223bca9af 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -14,7 +14,6 @@ pub mod thread_mode_cell; pub mod activity; pub mod broadcaster; pub mod buffer; -pub mod cfu; pub mod comms; pub mod fmt; pub mod hid; @@ -75,7 +74,6 @@ pub type Never = core::convert::Infallible; pub async fn init() { comms::init(); activity::init(); - cfu::init(); keyboard::init(); power::policy::init(); } diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 6a4f7f14c..d01c0dd68 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -216,6 +216,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "cfu-service" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-cfu-protocol", + "embedded-services", + "heapless", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -1356,6 +1369,7 @@ checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" name = "rt685s-evk-example" version = "0.1.0" dependencies = [ + "cfu-service", "cortex-m", "cortex-m-rt", "crc", @@ -1635,6 +1649,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "cfu-service", "defmt 0.3.100", "embassy-futures", "embassy-sync", diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index ca667b922..2f3cd6304 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -80,6 +80,8 @@ platform-service = { path = "../../platform-service", features = [ ] } embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } +cfu-service = { path = "../../cfu-service", features = ["defmt"] } + # Needed otherwise cargo will pull from git [patch."https://github.com/OpenDevicePartnership/embedded-services"] embedded-services = { path = "../../embedded-service" } diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 8264a9954..a287cab7e 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -2,6 +2,7 @@ #![no_main] use ::tps6699x::{ADDR1, TPS66994_NUM_PORTS}; +use cfu_service::CfuClient; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_executor::Spawner; use embassy_imxrt::gpio::{Input, Inverter, Pull}; @@ -9,6 +10,7 @@ use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::mutex::Mutex; +use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; @@ -88,6 +90,10 @@ async fn service_task( ) { info!("Starting type-c task"); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -107,7 +113,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context).await; + type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; } #[embassy_executor::main] diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 66612e85b..f91cd32e9 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -2,6 +2,8 @@ #![no_main] use ::tps6699x::{ADDR1, TPS66994_NUM_PORTS}; +use cfu_service::CfuClient; +use cfu_service::component::{InternalResponseData, RequestData}; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_executor::Spawner; use embassy_imxrt::gpio::{Input, Inverter, Pull}; @@ -9,17 +11,16 @@ use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::mutex::Mutex; +use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::cfu::component::InternalResponseData; -use embedded_services::cfu::component::RequestData; use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; -use embedded_services::{GlobalRawMutex, IntrusiveList, cfu}; +use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use static_cell::StaticCell; @@ -78,8 +79,8 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn fw_update_task() { Timer::after_millis(1000).await; - let context = cfu::ContextToken::create().unwrap(); - let device = context.get_device(CONTROLLER0_CFU_ID).await.unwrap(); + let context = cfu_service::ClientContext::new(); + let device = context.get_device(CONTROLLER0_CFU_ID).unwrap(); info!("Getting FW version"); let response = device @@ -175,6 +176,10 @@ async fn service_task( ) -> ! { info!("Starting type-c task"); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -194,7 +199,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context).await; + type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; unreachable!() } diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 7bd330cbe..9e83d88a6 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1777,6 +1777,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "cfu-service", "embassy-futures", "embassy-sync", "embassy-time", diff --git a/examples/std/src/bin/cfu_buffer.rs b/examples/std/src/bin/cfu_buffer.rs index c2c546b5c..4186ec84d 100644 --- a/examples/std/src/bin/cfu_buffer.rs +++ b/examples/std/src/bin/cfu_buffer.rs @@ -4,16 +4,13 @@ use embassy_time::{Duration, Timer}; use log::*; use static_cell::StaticCell; +use cfu_service::component::{InternalResponseData, RequestData}; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::{ - GlobalRawMutex, - cfu::{self, component::InternalResponseData, route_request}, -}; +use embedded_services::GlobalRawMutex; +use cfu_service::CfuClient; use cfu_service::buffer; -use crate::cfu::component::RequestData; - /// Component ID for the CFU buffer const CFU_BUFFER_ID: ComponentId = 0x06; @@ -23,7 +20,7 @@ const CFU_COMPONENT0_ID: ComponentId = 0x20; mod mock { use std::sync::atomic::AtomicBool; - use embedded_services::cfu::component::{CfuDevice, CfuDeviceContainer, InternalResponseData}; + use cfu_service::component::{CfuDevice, CfuDeviceContainer, InternalResponseData}; use super::*; @@ -147,10 +144,10 @@ async fn device_task(device: &'static mock::Device) { } #[embassy_executor::task] -async fn buffer_task(buffer: &'static buffer::Buffer<'static>) { +async fn buffer_task(buffer: &'static buffer::Buffer<'static>, cfu_client: &'static CfuClient) { loop { - let request = buffer.wait_event().await; - if let Some(response) = buffer.process(request).await { + let request = buffer.wait_event(cfu_client).await; + if let Some(response) = buffer.process(request, cfu_client).await { buffer.send_response(response).await; } } @@ -160,6 +157,11 @@ async fn buffer_task(buffer: &'static buffer::Buffer<'static>) { async fn run(spawner: Spawner) { embedded_services::init().await; + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + spawner.must_spawn(cfu_service_task(cfu_client)); + info!("Creating device 0"); static DEVICE0: OnceLock = OnceLock::new(); let device0 = DEVICE0.get_or_init(|| { @@ -172,7 +174,7 @@ async fn run(spawner: Spawner) { }, ) }); - cfu::register_device(device0).await.unwrap(); + cfu_client.register_device(device0).unwrap(); spawner.must_spawn(device_task(device0)); info!("Creating buffer"); @@ -189,11 +191,12 @@ async fn run(spawner: Spawner) { buffer::Config::with_timeout(Duration::from_millis(75)), ) }); - buffer.register().await.unwrap(); - spawner.must_spawn(buffer_task(buffer)); + buffer.register(cfu_client).unwrap(); + spawner.must_spawn(buffer_task(buffer, cfu_client)); info!("Getting FW version"); - let response = route_request(CFU_BUFFER_ID, RequestData::FwVersionRequest) + let response = cfu_client + .route_request(CFU_BUFFER_ID, RequestData::FwVersionRequest) .await .unwrap(); let prev_version = match response { @@ -206,18 +209,19 @@ async fn run(spawner: Spawner) { info!("Got version: {prev_version:#x}"); info!("Giving offer"); - let offer = route_request( - CFU_BUFFER_ID, - RequestData::GiveOffer(FwUpdateOffer::new( - HostToken::Driver, + let offer = cfu_client + .route_request( CFU_BUFFER_ID, - FwVersion::new(0x211), - 0, - 0, - )), - ) - .await - .unwrap(); + RequestData::GiveOffer(FwUpdateOffer::new( + HostToken::Driver, + CFU_BUFFER_ID, + FwVersion::new(0x211), + 0, + 0, + )), + ) + .await + .unwrap(); info!("Got response: {offer:?}"); for i in 0..10 { @@ -235,7 +239,8 @@ async fn run(spawner: Spawner) { info!("Giving content"); let now = embassy_time::Instant::now(); - let response = route_request(CFU_BUFFER_ID, RequestData::GiveContent(request)) + let response = cfu_client + .route_request(CFU_BUFFER_ID, RequestData::GiveContent(request)) .await .unwrap(); info!("Got response in {:?} ms: {:?}", now.elapsed().as_millis(), response); @@ -246,8 +251,8 @@ async fn run(spawner: Spawner) { } #[embassy_executor::task] -async fn cfu_service_task() -> ! { - cfu_service::task::task().await; +async fn cfu_service_task(cfu_client: &'static CfuClient) -> ! { + cfu_service::task::task(cfu_client).await; unreachable!() } @@ -256,7 +261,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(cfu_service_task()); spawner.must_spawn(run(spawner)); }); } diff --git a/examples/std/src/bin/cfu_client.rs b/examples/std/src/bin/cfu_client.rs index 1a7a4a9b5..1c51f2b4f 100644 --- a/examples/std/src/bin/cfu_client.rs +++ b/examples/std/src/bin/cfu_client.rs @@ -7,24 +7,25 @@ use static_cell::StaticCell; use embedded_cfu_protocol::protocol_definitions::{ ComponentId, FwUpdateOffer, FwVersion, HostToken, MAX_SUBCMPT_COUNT, }; -use embedded_services::cfu; -use embedded_services::cfu::component::CfuComponentDefault; -use crate::cfu::component::RequestData; +use cfu_service::{ + CfuClient, + component::{CfuComponentDefault, RequestData}, +}; #[embassy_executor::task] -async fn device_task0(component: &'static CfuComponentDefault) { +async fn device_task0(component: &'static CfuComponentDefault, cfu_client: &'static CfuClient) { loop { - if let Err(e) = component.process_request().await { + if let Err(e) = component.process_request(cfu_client).await { error!("Error processing request: {e:?}"); } } } #[embassy_executor::task] -async fn device_task1(component: &'static CfuComponentDefault) { +async fn device_task1(component: &'static CfuComponentDefault, cfu_client: &'static CfuClient) { loop { - if let Err(e) = component.process_request().await { + if let Err(e) = component.process_request(cfu_client).await { error!("Error processing request: {e:?}"); } } @@ -34,20 +35,25 @@ async fn device_task1(component: &'static CfuComponentDefault) { async fn run(spawner: Spawner) { embedded_services::init().await; + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + spawner.must_spawn(cfu_service_task(cfu_client)); + info!("Creating device 0"); static DEVICE0: OnceLock> = OnceLock::new(); let mut subs: [Option; MAX_SUBCMPT_COUNT] = [None; MAX_SUBCMPT_COUNT]; subs[0] = Some(2); let device0 = DEVICE0.get_or_init(|| CfuComponentDefault::new(1, true, subs, CfuWriterNop {})); - cfu::register_device(device0).await.unwrap(); - spawner.must_spawn(device_task0(device0)); + cfu_client.register_device(device0).unwrap(); + spawner.must_spawn(device_task0(device0, cfu_client)); info!("Creating device 1"); static DEVICE1: OnceLock> = OnceLock::new(); let device1 = DEVICE1.get_or_init(|| CfuComponentDefault::new(2, false, [None; MAX_SUBCMPT_COUNT], CfuWriterNop {})); - cfu::register_device(device1).await.unwrap(); - spawner.must_spawn(device_task1(device1)); + cfu_client.register_device(device1).unwrap(); + spawner.must_spawn(device_task1(device1, cfu_client)); let dummy_offer0 = FwUpdateOffer::new( HostToken::Driver, @@ -72,7 +78,7 @@ async fn run(spawner: Spawner) { 0, ); - match cfu::route_request(1, RequestData::GiveOffer(dummy_offer0)).await { + match cfu_client.route_request(1, RequestData::GiveOffer(dummy_offer0)).await { Ok(resp) => { info!("got okay response to device0 update {resp:?}"); } @@ -80,7 +86,7 @@ async fn run(spawner: Spawner) { error!("offer failed with error {e:?}"); } } - match cfu::route_request(2, RequestData::GiveOffer(dummy_offer1)).await { + match cfu_client.route_request(2, RequestData::GiveOffer(dummy_offer1)).await { Ok(resp) => { info!("got okay response to device1 update {resp:?}"); } @@ -91,8 +97,8 @@ async fn run(spawner: Spawner) { } #[embassy_executor::task] -async fn cfu_service_task() -> ! { - cfu_service::task::task().await; +async fn cfu_service_task(cfu_client: &'static CfuClient) -> ! { + cfu_service::task::task(cfu_client).await; unreachable!() } @@ -102,7 +108,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(cfu_service_task()); spawner.must_spawn(run(spawner)); }); } diff --git a/examples/std/src/bin/cfu_splitter.rs b/examples/std/src/bin/cfu_splitter.rs index b7122dacd..506e49426 100644 --- a/examples/std/src/bin/cfu_splitter.rs +++ b/examples/std/src/bin/cfu_splitter.rs @@ -3,12 +3,10 @@ use embassy_sync::once_lock::OnceLock; use log::*; use static_cell::StaticCell; +use cfu_service::component::{InternalResponseData, RequestData}; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::cfu::{self, component::InternalResponseData, route_request}; -use cfu_service::splitter; - -use crate::cfu::component::RequestData; +use cfu_service::{CfuClient, splitter}; /// Component ID for the CFU Splitter const CFU_SPLITTER_ID: ComponentId = 0x06; @@ -19,7 +17,7 @@ const CFU_COMPONENT0_ID: ComponentId = 0x20; const CFU_COMPONENT1_ID: ComponentId = 0x21; mod mock { - use embedded_services::cfu::component::{CfuDevice, CfuDeviceContainer, InternalResponseData}; + use cfu_service::component::{CfuDevice, CfuDeviceContainer, InternalResponseData}; use super::*; @@ -166,10 +164,13 @@ async fn device_task(device: &'static mock::Device) { } #[embassy_executor::task] -async fn splitter_task(splitter: &'static splitter::Splitter<'static, mock::Customization>) { +async fn splitter_task( + splitter: &'static splitter::Splitter<'static, mock::Customization>, + cfu_client: &'static CfuClient, +) { loop { let request = splitter.wait_request().await; - let response = splitter.process_request(request).await; + let response = splitter.process_request(request, cfu_client).await; splitter.send_response(response).await; } } @@ -178,6 +179,11 @@ async fn splitter_task(splitter: &'static splitter::Splitter<'static, mock::Cust async fn run(spawner: Spawner) { embedded_services::init().await; + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + spawner.must_spawn(cfu_service_task(cfu_client)); + info!("Creating device 0"); static DEVICE0: OnceLock = OnceLock::new(); let device0 = DEVICE0.get_or_init(|| { @@ -190,7 +196,7 @@ async fn run(spawner: Spawner) { }, ) }); - cfu::register_device(device0).await.unwrap(); + cfu_client.register_device(device0).unwrap(); spawner.must_spawn(device_task(device0)); info!("Creating device 1"); @@ -205,7 +211,7 @@ async fn run(spawner: Spawner) { }, ) }); - cfu::register_device(device1).await.unwrap(); + cfu_client.register_device(device1).unwrap(); spawner.must_spawn(device_task(device1)); info!("Creating splitter"); @@ -213,11 +219,12 @@ async fn run(spawner: Spawner) { static DEVICES: [ComponentId; 2] = [CFU_COMPONENT0_ID, CFU_COMPONENT1_ID]; let customization = mock::Customization {}; let splitter = SPLITTER.get_or_init(|| splitter::Splitter::new(CFU_SPLITTER_ID, &DEVICES, customization).unwrap()); - splitter.register().await.unwrap(); - spawner.must_spawn(splitter_task(splitter)); + splitter.register(cfu_client).unwrap(); + spawner.must_spawn(splitter_task(splitter, cfu_client)); info!("Getting FW version"); - let response = route_request(CFU_SPLITTER_ID, RequestData::FwVersionRequest) + let response = cfu_client + .route_request(CFU_SPLITTER_ID, RequestData::FwVersionRequest) .await .unwrap(); let prev_version = match response { @@ -230,18 +237,19 @@ async fn run(spawner: Spawner) { info!("Got version: {prev_version:#x}"); info!("Giving offer"); - let offer = route_request( - CFU_SPLITTER_ID, - RequestData::GiveOffer(FwUpdateOffer::new( - HostToken::Driver, + let offer = cfu_client + .route_request( CFU_SPLITTER_ID, - FwVersion::new(0x211), - 0, - 0, - )), - ) - .await - .unwrap(); + RequestData::GiveOffer(FwUpdateOffer::new( + HostToken::Driver, + CFU_SPLITTER_ID, + FwVersion::new(0x211), + 0, + 0, + )), + ) + .await + .unwrap(); info!("Got response: {offer:?}"); let header = FwUpdateContentHeader { @@ -256,15 +264,16 @@ async fn run(spawner: Spawner) { data: [0u8; DEFAULT_DATA_LENGTH], }; - let response = route_request(CFU_SPLITTER_ID, RequestData::GiveContent(request)) + let response = cfu_client + .route_request(CFU_SPLITTER_ID, RequestData::GiveContent(request)) .await .unwrap(); info!("Got response: {response:?}"); } #[embassy_executor::task] -async fn cfu_service_task() -> ! { - cfu_service::task::task().await; +async fn cfu_service_task(cfu_client: &'static CfuClient) -> ! { + cfu_service::task::task(cfu_client).await; unreachable!() } @@ -274,7 +283,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(cfu_service_task()); spawner.must_spawn(run(spawner)); }); } diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 1b4cbcc83..943d7b3f4 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -1,6 +1,8 @@ //! Low-level example of external messaging with a simple type-C service +use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; +use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embedded_services::power::policy::*; @@ -104,6 +106,10 @@ async fn service_task( ) { info!("Starting type-c task"); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -124,7 +130,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_context).await; + type_c_service::task::task(service, wrappers, power_context, cfu_client).await; } fn create_wrapper( diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index d47b6e2d8..1307c283c 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,3 +1,4 @@ +use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; @@ -151,6 +152,10 @@ async fn service_task( ) { info!("Starting type-c task"); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -171,7 +176,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context).await; + type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; } fn create_wrapper( diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 1600ae2b6..eb8f4ffc6 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,6 +1,8 @@ use crate::mock_controller::Wrapper; +use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; use embassy_sync::mutex::Mutex; +use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; @@ -193,6 +195,10 @@ async fn service_task( ) -> ! { info!("Starting type-c task"); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< PubSubChannel, @@ -214,7 +220,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context).await; + type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; unreachable!() } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 8b896bbb9..bb2039a75 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,6 +1,8 @@ use crate::mock_controller::Wrapper; +use cfu_service::CfuClient; use embassy_executor::Executor; use embassy_sync::mutex::Mutex; +use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embedded_services::power::policy::PowerCapability; @@ -119,6 +121,10 @@ async fn service_task( ) -> ! { info!("Starting type-c task"); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -139,7 +145,7 @@ async fn service_task( static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context).await; + type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; unreachable!() } diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index d52f82917..75f6e1ebd 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -26,6 +26,7 @@ embedded-usb-pd.workspace = true heapless.workspace = true log = { workspace = true, optional = true } tps6699x = { workspace = true, features = ["embassy"] } +cfu-service.workspace = true [dev-dependencies] embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } @@ -45,6 +46,7 @@ defmt = [ "embassy-sync/defmt", "tps6699x/defmt", "embedded-usb-pd/defmt", + "cfu-service/defmt", ] log = [ "dep:log", @@ -52,4 +54,5 @@ log = [ "embassy-time/log", "embassy-sync/log", "tps6699x/log", + "cfu-service/log", ] diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index b328003fb..e0afb8181 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -17,6 +17,7 @@ pub async fn task_closure< service: &'static Service<'a>, wrappers: [&'a ControllerWrapper<'a, M, C, V, POLICY_CHANNEL_SIZE>; N], power_policy_context: &'a embedded_services::power::policy::policy::Context, + cfu_client: &'a cfu_service::CfuClient, f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, @@ -33,8 +34,7 @@ pub async fn task_closure< for controller_wrapper in wrappers { if controller_wrapper - .register(service.controllers(), power_policy_context) - .await + .register(service.controllers(), power_policy_context, cfu_client) .is_err() { error!("Failed to register a controller"); @@ -52,16 +52,23 @@ pub async fn task<'a, M, C, V, const N: usize, const POLICY_CHANNEL_SIZE: usize> service: &'static Service<'a>, wrappers: [&'a ControllerWrapper<'a, M, C, V, POLICY_CHANNEL_SIZE>; N], power_policy_context: &'a embedded_services::power::policy::policy::Context, + cfu_client: &'a cfu_service::CfuClient, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, C: embedded_services::sync::Lockable, V: crate::wrapper::FwOfferValidator, ::Inner: embedded_services::type_c::controller::Controller, { - task_closure(service, wrappers, power_policy_context, |service: &Service| async { - if let Err(e) = service.process_next_event().await { - error!("Type-C service processing error: {:#?}", e); - } - }) + task_closure( + service, + wrappers, + power_policy_context, + cfu_client, + |service: &Service| async { + if let Err(e) = service.process_next_event().await { + error!("Type-C service processing error: {:#?}", e); + } + }, + ) .await; } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 6c5d2db60..ec076bf89 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -46,6 +46,7 @@ //! ``` use core::cell::{RefCell, RefMut}; +use cfu_service::component::CfuDevice; use embassy_sync::{ blocking_mutex::raw::RawMutex, pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}, @@ -168,7 +169,7 @@ pub trait DynPortState<'a> { pub struct Registration<'a, const POLICY_CHANNEL_SIZE: usize> { pub context: &'a embedded_services::type_c::controller::Context, pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, - pub cfu_device: &'a embedded_services::cfu::component::CfuDevice, + pub cfu_device: &'a CfuDevice, pub power_devices: &'a [embedded_services::power::policy::device::Device], } @@ -187,7 +188,7 @@ pub struct Storage<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: u context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], - cfu_device: embedded_services::cfu::component::CfuDevice, + cfu_device: CfuDevice, power_devices: [embedded_services::power::policy::device::Device; N], // State-related @@ -206,7 +207,7 @@ impl<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> Storage< context, controller_id, pd_ports: ports.map(|(port, _)| port), - cfu_device: embedded_services::cfu::component::CfuDevice::new(cfu_id), + cfu_device: CfuDevice::new(cfu_id), power_devices: ports .map(|(_, device)| embedded_services::power::policy::device::Device::new(device, power_policy_context)), pd_alerts: [const { PubSubChannel::new() }; N], diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index 209ffbe25..69464894c 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,8 +1,8 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation +use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::cfu::component::{InternalResponseData, RequestData}; use embedded_services::power; use embedded_services::type_c::controller::Controller; use embedded_services::{debug, error}; diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index f8416b3ae..d6efd4612 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -44,7 +44,7 @@ pub struct EventPowerPolicyCommand<'a> { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum EventCfu { /// CFU request - Request(embedded_services::cfu::component::RequestData), + Request(cfu_service::component::RequestData), /// Recovery tick /// /// Occurs when the FW update has timed out to abort the update and return hardware to its normal state @@ -164,7 +164,7 @@ pub enum Output<'a> { /// CFU recovery tick CfuRecovery, /// CFU response - CfuResponse(embedded_services::cfu::component::InternalResponseData), + CfuResponse(cfu_service::component::InternalResponseData), /// Dp status update DpStatusUpdate(OutputDpStatusChanged), } diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 9cb42a8d6..aef64ffb2 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -4,7 +4,7 @@ //! This struct current currently supports messages from the following services: //! * Type-C: [`embedded_services::type_c::controller::Command`] //! * Power policy: [`embedded_services::power::policy::device::Command`] -//! * CFU: [`embedded_services::cfu::Request`] +//! * CFU: [`cfu_service::Request`] //! # Event loop //! This struct follows a standard wait/process/finalize event loop. //! @@ -608,10 +608,11 @@ where } /// Register all devices with their respective services - pub async fn register( + pub fn register( &'static self, controllers: &intrusive_list::IntrusiveList, power_policy_context: &embedded_services::power::policy::policy::Context, + cfu_client: &cfu_service::CfuClient, ) -> Result<(), Error<::BusError>> { // TODO: Unify these devices? for device in self.registration.power_devices { @@ -634,15 +635,13 @@ where })?; //TODO: Remove when we have a more general framework in place - embedded_services::cfu::register_device(self.registration.cfu_device) - .await - .map_err(|_| { - error!( - "Controller{}: Failed to register CFU device", - self.registration.pd_controller.id().0 - ); - Error::Pd(PdError::Failed) - })?; + cfu_client.register_device(self.registration.cfu_device).map_err(|_| { + error!( + "Controller{}: Failed to register CFU device", + self.registration.pd_controller.id().0 + ); + Error::Pd(PdError::Failed) + })?; Ok(()) } } From 97db84a8b2bb051283f14e9c98a06d52373bac02 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Tue, 10 Feb 2026 09:05:34 -0800 Subject: [PATCH 18/79] Add mock battery to battery-service and example demoing use (#709) - Mock battery is feature gated --- battery-service/Cargo.toml | 1 + battery-service/src/lib.rs | 2 + battery-service/src/mock.rs | 385 ++++++++++++++++++++++++++++++++ examples/std/Cargo.toml | 2 +- examples/std/src/bin/battery.rs | 111 +++++++++ 5 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 battery-service/src/mock.rs create mode 100644 examples/std/src/bin/battery.rs diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 87d36baf8..3b3304407 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -42,3 +42,4 @@ log = [ "embassy-time/log", "embassy-sync/log", ] +mock = [] diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index 993f0ff86..d308fd103 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -14,6 +14,8 @@ mod acpi; pub mod context; pub mod controller; pub mod device; +#[cfg(feature = "mock")] +pub mod mock; pub mod task; pub mod wrapper; diff --git a/battery-service/src/mock.rs b/battery-service/src/mock.rs new file mode 100644 index 000000000..2fafe5e53 --- /dev/null +++ b/battery-service/src/mock.rs @@ -0,0 +1,385 @@ +use embassy_time::{Duration, Timer}; +use embedded_batteries_async::{ + acpi, charger, + smart_battery::{self, SmartBattery}, +}; +use embedded_services::{GlobalRawMutex, error, info}; + +// Convenience fns +pub async fn init_state_machine(battery_service: &'static crate::Service) -> Result<(), crate::context::ContextError> { + battery_service + .execute_event(crate::context::BatteryEvent { + event: crate::context::BatteryEventInner::DoInit, + device_id: crate::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge init error: {:?}", f))?; + + battery_service + .execute_event(crate::context::BatteryEvent { + event: crate::context::BatteryEventInner::PollStaticData, + device_id: crate::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge static data error: {:?}", f))?; + + battery_service + .execute_event(crate::context::BatteryEvent { + event: crate::context::BatteryEventInner::PollDynamicData, + device_id: crate::device::DeviceId(0), + }) + .await + .inspect_err(|f| embedded_services::debug!("Fuel gauge dynamic data error: {:?}", f))?; + + Ok(()) +} + +pub async fn recover_state_machine(battery_service: &'static crate::Service) -> Result<(), ()> { + loop { + match battery_service + .execute_event(crate::context::BatteryEvent { + event: crate::context::BatteryEventInner::Timeout, + device_id: crate::device::DeviceId(0), + }) + .await + { + Ok(_) => { + embedded_services::info!("FG recovered!"); + return Ok(()); + } + Err(e) => match e { + crate::context::ContextError::StateError(e) => match e { + crate::context::StateMachineError::DeviceTimeout => { + embedded_services::trace!("Recovery failed, trying again after a backoff period"); + Timer::after(Duration::from_secs(10)).await; + } + crate::context::StateMachineError::NoOpRecoveryFailed => { + embedded_services::error!("Couldn't recover, reinit needed"); + return Err(()); + } + _ => embedded_services::debug!("Unexpected error"), + }, + _ => embedded_services::debug!("Unexpected error"), + }, + } + } +} + +pub type MockBattery<'a> = crate::wrapper::Wrapper<'a, MockBatteryDriver>; + +#[derive(Default)] +pub struct MockBatteryDriver { + capacity_mode_bit: embassy_sync::mutex::Mutex, +} + +impl MockBatteryDriver { + pub fn new() -> Self { + MockBatteryDriver { + capacity_mode_bit: embassy_sync::mutex::Mutex::new(false), + } + } + + async fn set_capacity_bit(&mut self, mwh: bool) -> Result<(), MockBatteryError> { + let battery_mode = self.battery_mode().await?; + SmartBattery::set_battery_mode(self, battery_mode.with_capacity_mode(mwh)).await?; + *self.capacity_mode_bit.get_mut() = mwh; + + Ok(()) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct MockBatteryError; + +impl crate::controller::Controller for MockBatteryDriver { + type ControllerError = MockBatteryError; + + async fn initialize(&mut self) -> Result<(), Self::ControllerError> { + // Milliamps + let mwh = false; + self.set_capacity_bit(mwh) + .await + .inspect_err(|_| error!("FG: failed to initialize"))?; + + info!("FG: initialized"); + Ok(()) + } + + async fn ping(&mut self) -> Result<(), Self::ControllerError> { + if let Err(e) = self.charging_voltage().await { + error!("FG: failed to ping"); + Err(e) + } else { + info!("FG: ping success"); + Ok(()) + } + } + + async fn get_dynamic_data(&mut self) -> Result { + let new_msgs = crate::device::DynamicBatteryMsgs { + average_current_ma: self.average_current().await?, + battery_status: self.battery_status().await?.into(), + max_power_mw: 100, + battery_temp_dk: self.temperature().await?, + sus_power_mw: 42, + charging_current_ma: self.charging_current().await?, + charging_voltage_mv: self.charging_voltage().await?, + voltage_mv: self.voltage().await?, + current_ma: self.current().await?, + full_charge_capacity_mwh: match self.full_charge_capacity().await? { + smart_battery::CapacityModeValue::CentiWattUnsigned(_) => 0xDEADBEEF, + smart_battery::CapacityModeValue::MilliAmpUnsigned(capacity) => capacity.into(), + }, + remaining_capacity_mwh: match self.remaining_capacity().await? { + smart_battery::CapacityModeValue::CentiWattUnsigned(_) => 0xDEADBEEF, + smart_battery::CapacityModeValue::MilliAmpUnsigned(capacity) => capacity.into(), + }, + relative_soc_pct: self.relative_state_of_charge().await?.into(), + cycle_count: self.cycle_count().await?, + max_error_pct: self.max_error().await?.into(), + bmd_status: acpi::BmdStatusFlags::default(), + turbo_vload_mv: 0, + turbo_rhf_effective_mohm: 0, + }; + Ok(new_msgs) + } + + #[allow(clippy::indexing_slicing)] + async fn get_static_data(&mut self) -> Result { + let design_capacity: u32 = match self.design_capacity().await? { + smart_battery::CapacityModeValue::CentiWattUnsigned(design_capacity) => design_capacity.into(), + smart_battery::CapacityModeValue::MilliAmpUnsigned(design_capacity) => design_capacity.into(), + }; + + let mut new_msgs = crate::device::StaticBatteryMsgs { + manufacturer_name: Default::default(), + device_name: Default::default(), + device_chemistry: Default::default(), + design_capacity_mwh: match self.design_capacity().await? { + smart_battery::CapacityModeValue::CentiWattUnsigned(design_capacity) => design_capacity.into(), + smart_battery::CapacityModeValue::MilliAmpUnsigned(design_capacity) => design_capacity.into(), + }, + design_voltage_mv: self.design_voltage().await?, + device_chemistry_id: Default::default(), + serial_num: Default::default(), + battery_mode: self.battery_mode().await?, + design_cap_warning: design_capacity / 4, + design_cap_low: design_capacity / 10, + measurement_accuracy: self.max_error().await?.into(), + max_sample_time: Default::default(), + min_sample_time: Default::default(), + max_averaging_interval: Default::default(), + min_averaging_interval: Default::default(), + cap_granularity_1: Default::default(), + cap_granularity_2: Default::default(), + power_threshold_support: battery_service_messages::PowerThresholdSupport::empty(), + max_instant_pwr_threshold: Default::default(), + max_sus_pwr_threshold: Default::default(), + bmc_flags: battery_service_messages::BmcControlFlags::empty(), + bmd_capability: battery_service_messages::BmdCapabilityFlags::empty(), + bmd_recalibrate_count: Default::default(), + bmd_quick_recalibrate_time: Default::default(), + bmd_slow_recalibrate_time: Default::default(), + }; + let mut buf = [0u8; 21]; + + let buf_len = new_msgs.manufacturer_name.len(); + self.manufacturer_name(&mut buf[..buf_len]).await?; + new_msgs.manufacturer_name.copy_from_slice(&buf[..buf_len]); + + let buf_len = new_msgs.device_name.len(); + self.device_name(&mut buf[..buf_len]).await?; + new_msgs.device_name.copy_from_slice(&buf[..buf_len]); + + let buf_len = new_msgs.device_chemistry.len(); + self.device_chemistry(&mut buf[..buf_len]).await?; + new_msgs.device_chemistry.copy_from_slice(&buf[..buf_len]); + + let buf_len = new_msgs.device_chemistry_id.len(); + self.device_chemistry(&mut buf[..buf_len]).await?; + new_msgs.device_chemistry_id.copy_from_slice(&buf[..buf_len]); + + let serial = self.serial_number().await?; + let serial = serial.to_le_bytes(); + new_msgs.serial_num = [serial[0], serial[1], 0, 0]; + + Ok(new_msgs) + } + + async fn get_device_event(&mut self) -> crate::controller::ControllerEvent { + // TODO: Loop forever till we figure out what we want to do here + loop { + Timer::after_secs(1000000).await; + } + } + + fn set_timeout(&mut self, _duration: embassy_time::Duration) {} +} + +impl smart_battery::Error for MockBatteryError { + fn kind(&self) -> smart_battery::ErrorKind { + smart_battery::ErrorKind::Other + } +} + +impl smart_battery::ErrorType for MockBatteryDriver { + type Error = MockBatteryError; +} + +// Revisit: Have this generate realistic data dynamically (right now just static arbitrary values) +impl smart_battery::SmartBattery for MockBatteryDriver { + async fn absolute_state_of_charge(&mut self) -> Result { + Ok(77) + } + + async fn at_rate(&mut self) -> Result { + Ok(smart_battery::CapacityModeSignedValue::MilliAmpSigned(100)) + } + + async fn at_rate_ok(&mut self) -> Result { + Ok(true) + } + + async fn at_rate_time_to_empty(&mut self) -> Result { + Ok(2600) + } + + async fn at_rate_time_to_full(&mut self) -> Result { + Ok(1337) + } + + async fn average_current(&mut self) -> Result { + Ok(42) + } + + async fn average_time_to_empty(&mut self) -> Result { + Ok(100) + } + + async fn average_time_to_full(&mut self) -> Result { + Ok(120) + } + + async fn battery_mode(&mut self) -> Result { + Ok(smart_battery::BatteryModeFields::new()) + } + + async fn battery_status(&mut self) -> Result { + Ok(smart_battery::BatteryStatusFields::new()) + } + + async fn charging_current(&mut self) -> Result { + Ok(50) + } + + async fn charging_voltage(&mut self) -> Result { + Ok(4242) + } + + async fn current(&mut self) -> Result { + Ok(500) + } + + async fn cycle_count(&mut self) -> Result { + Ok(10000) + } + + async fn design_capacity(&mut self) -> Result { + Ok(smart_battery::CapacityModeValue::CentiWattUnsigned(0)) + } + + async fn design_voltage(&mut self) -> Result { + Ok(12000) + } + + #[allow(clippy::indexing_slicing)] + async fn device_chemistry(&mut self, chemistry: &mut [u8]) -> Result<(), Self::Error> { + let bytes = [b'L', b'i', b'P', b'o', 0]; + let bytes_to_copy = core::cmp::min(bytes.len(), chemistry.len()); + chemistry[..bytes_to_copy].copy_from_slice(&bytes[..bytes_to_copy]); + Ok(()) + } + + #[allow(clippy::indexing_slicing)] + async fn device_name(&mut self, name: &mut [u8]) -> Result<(), Self::Error> { + let bytes = [b'O', b'd', b'p', b'B', b'a', b't', b't', 0]; + let bytes_to_copy = core::cmp::min(bytes.len(), name.len()); + name[..bytes_to_copy].copy_from_slice(&bytes[..bytes_to_copy]); + Ok(()) + } + + async fn full_charge_capacity(&mut self) -> Result { + Ok(smart_battery::CapacityModeValue::CentiWattUnsigned(0)) + } + + async fn manufacture_date(&mut self) -> Result { + Ok(smart_battery::ManufactureDate::new()) + } + + #[allow(clippy::indexing_slicing)] + async fn manufacturer_name(&mut self, name: &mut [u8]) -> Result<(), Self::Error> { + let bytes = [b'B', b'a', b't', b'B', b'r', b'o', b's', 0]; + let bytes_to_copy = core::cmp::min(bytes.len(), name.len()); + name[..bytes_to_copy].copy_from_slice(&bytes[..bytes_to_copy]); + Ok(()) + } + + async fn max_error(&mut self) -> Result { + Ok(2) + } + + async fn relative_state_of_charge(&mut self) -> Result { + Ok(10) + } + + async fn remaining_capacity(&mut self) -> Result { + Ok(smart_battery::CapacityModeValue::CentiWattUnsigned(0)) + } + + async fn remaining_capacity_alarm(&mut self) -> Result { + Ok(smart_battery::CapacityModeValue::CentiWattUnsigned(0)) + } + + async fn remaining_time_alarm(&mut self) -> Result { + Ok(85) + } + + async fn run_time_to_empty(&mut self) -> Result { + Ok(110) + } + + async fn serial_number(&mut self) -> Result { + Ok(0x4544) + } + + async fn set_at_rate(&mut self, _rate: smart_battery::CapacityModeSignedValue) -> Result<(), Self::Error> { + Ok(()) + } + + async fn set_battery_mode(&mut self, _flags: smart_battery::BatteryModeFields) -> Result<(), Self::Error> { + Ok(()) + } + + async fn set_remaining_capacity_alarm( + &mut self, + _capacity: smart_battery::CapacityModeValue, + ) -> Result<(), Self::Error> { + Ok(()) + } + + async fn set_remaining_time_alarm(&mut self, _time: smart_battery::Minutes) -> Result<(), Self::Error> { + Ok(()) + } + + async fn specification_info(&mut self) -> Result { + Ok(smart_battery::SpecificationInfoFields::new()) + } + + async fn temperature(&mut self) -> Result { + Ok(2981) + } + + async fn voltage(&mut self) -> Result { + Ok(12600) + } +} diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index d1b298b00..bd245060a 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -33,7 +33,7 @@ cfu-service = { path = "../../cfu-service", features = ["log"] } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } embedded-batteries-async = "0.3" -battery-service = { path = "../../battery-service", features = ["log"] } +battery-service = { path = "../../battery-service", features = ["log", "mock"] } type-c-service = { path = "../../type-c-service", features = ["log"] } embedded-sensors-hal-async = "0.3.0" diff --git a/examples/std/src/bin/battery.rs b/examples/std/src/bin/battery.rs new file mode 100644 index 000000000..abd2d0df8 --- /dev/null +++ b/examples/std/src/bin/battery.rs @@ -0,0 +1,111 @@ +//! Standard battery example +//! +//! The example can be run simply by typing `cargo run --bin battery` + +use battery_service as bs; +use embassy_executor::{Executor, Spawner}; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; + +#[embassy_executor::task] +async fn battery_service_task( + service: &'static battery_service::Service, + devices: [&'static battery_service::device::Device; 1], +) { + battery_service::task::task(service, devices) + .await + .expect("Failed to init battery service"); +} + +#[embassy_executor::task] +async fn battery_wrapper_process(battery_wrapper: &'static battery_service::mock::MockBattery<'static>) { + battery_wrapper.process().await +} + +#[embassy_executor::task] +async fn init_and_run_service(spawner: Spawner, battery_service: &'static battery_service::Service) { + embedded_services::debug!("Initializing battery service"); + embedded_services::init().await; + + static BATTERY_DEVICE: StaticCell = StaticCell::new(); + static BATTERY_WRAPPER: StaticCell = StaticCell::new(); + let device = BATTERY_DEVICE.init(bs::device::Device::new(bs::device::DeviceId::default())); + + let wrapper = BATTERY_WRAPPER.init(bs::wrapper::Wrapper::new( + device, + battery_service::mock::MockBatteryDriver::new(), + )); + + // Run battery service + spawner.must_spawn(battery_service_task(battery_service, [device])); + spawner.must_spawn(battery_wrapper_process(wrapper)); +} + +#[embassy_executor::task] +pub async fn run_app(battery_service: &'static battery_service::Service) { + // Initialize battery state machine. + let mut retries = 5; + while let Err(e) = bs::mock::init_state_machine(battery_service).await { + retries -= 1; + if retries <= 0 { + embedded_services::error!("Failed to initialize Battery: {:?}", e); + return; + } + Timer::after(Duration::from_secs(1)).await; + } + + let mut failures: u32 = 0; + let mut count: usize = 1; + loop { + Timer::after(Duration::from_secs(1)).await; + if count.is_multiple_of(const { 60 * 60 * 60 }) { + if let Err(e) = battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollStaticData, + device_id: bs::device::DeviceId(0), + }) + .await + { + failures += 1; + embedded_services::error!("Fuel gauge static data error: {:#?}", e); + } + } + if let Err(e) = battery_service + .execute_event(battery_service::context::BatteryEvent { + event: battery_service::context::BatteryEventInner::PollDynamicData, + device_id: bs::device::DeviceId(0), + }) + .await + { + failures += 1; + embedded_services::error!("Fuel gauge dynamic data error: {:#?}", e); + } + + if failures > 10 { + failures = 0; + count = 0; + embedded_services::error!("FG: Too many errors, timing out and starting recovery..."); + if bs::mock::recover_state_machine(battery_service).await.is_err() { + embedded_services::error!("FG: Fatal error"); + return; + } + } + + count = count.wrapping_add(1); + } +} + +fn main() { + env_logger::builder().filter_level(log::LevelFilter::Debug).init(); + embedded_services::info!("battery example started"); + + static BATTERY_SERVICE: bs::Service = bs::Service::new(); + + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + // Run battery service + executor.run(|spawner| { + spawner.must_spawn(run_app(&BATTERY_SERVICE)); + spawner.must_spawn(init_and_run_service(spawner, &BATTERY_SERVICE)); + }); +} From f212195cbce75a9cd975004f70225ff758372a4d Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Tue, 10 Feb 2026 10:35:56 -0800 Subject: [PATCH 19/79] thermal-service: Add mocks (#710) Adds mock sensor and fan to thermal service and updates the thermal `std` example to make use of them (which simplifies the example quite a lot). Example can be tested with `cargo run --release --bin thermal`. Resolves #619. --- examples/std/Cargo.toml | 2 +- examples/std/src/bin/thermal.rs | 436 +++-------------------------- thermal-service/Cargo.toml | 3 +- thermal-service/src/lib.rs | 2 + thermal-service/src/mock/fan.rs | 59 ++++ thermal-service/src/mock/mod.rs | 57 ++++ thermal-service/src/mock/sensor.rs | 70 +++++ 7 files changed, 235 insertions(+), 394 deletions(-) create mode 100644 thermal-service/src/mock/fan.rs create mode 100644 thermal-service/src/mock/mod.rs create mode 100644 thermal-service/src/mock/sensor.rs diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index bd245060a..5f5375356 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -38,7 +38,7 @@ type-c-service = { path = "../../type-c-service", features = ["log"] } embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" -thermal-service = { path = "../../thermal-service", features = ["log"] } +thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } thermal-service-messages = { path = "../../thermal-service-messages" } env_logger = "0.9.0" diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 0f5a7b9d7..357ef97f2 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -1,399 +1,29 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; -use embedded_fans_async as fan; -use embedded_sensors_hal_async::sensor; -use embedded_sensors_hal_async::temperature::{DegreesCelsius, TemperatureSensor, TemperatureThresholdSet}; -use embedded_services::comms; -use log::{info, warn}; +use embedded_services::{error, info}; use static_cell::StaticCell; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; use thermal_service as ts; -use thermal_service_messages::ThermalRequest; -use ts::mptf; -const SAMPLE_BUF_LEN: usize = 16; - -// Mock host service -mod host { - use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; - use embedded_services::comms::{self, Endpoint, EndpointID, External, MailboxDelegate}; - use log::{info, warn}; - use thermal_service as ts; - use thermal_service_messages::{ThermalResponse, ThermalResult}; - use ts::mptf; - - pub struct Host { - pub tp: Endpoint, - pub alert: Signal, - } - - impl Host { - pub fn new() -> Self { - Self { - tp: Endpoint::uninit(EndpointID::External(External::Host)), - alert: Signal::new(), - } - } - - fn handle_response(&self, response: ThermalResponse) { - match response { - ThermalResponse::ThermalGetTmpResponse { temperature } => { - info!("Host received temperature: {} °C", ts::utils::dk_to_c(temperature)) - } - ThermalResponse::ThermalGetVarResponse { val } => { - info!("Host received fan RPM: {val}") - } - _ => info!("Received MPTF response: {response:?}"), - } - } - } - - impl MailboxDelegate for Host { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(&result) = message.data.get::() { - self.handle_response(result.map_err(|_| comms::MailboxDelegateError::Other)?); - Ok(()) - } else if let Some(¬ification) = message.data.get::() { - warn!("Received notification: {notification:?}"); - self.alert.signal(()); - Ok(()) - } else { - Err(comms::MailboxDelegateError::MessageNotFound) - } - } - } -} - -// A mock struct shared by MockSensor and MockAlertPin to sync on raw samples and thresholds -struct MockBus { - samples: [f32; 35], - idx: AtomicUsize, - threshold_low: Mutex, - threshold_high: Mutex, -} - -impl MockBus { - fn new() -> Self { - Self { - samples: [ - 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 55.0, 60.0, 65.0, 70.0, 75.0, 80.0, 85.0, 90.0, 95.0, 100.0, - 105.0, 100.0, 95.0, 90.0, 85.0, 80.0, 75.0, 70.0, 65.0, 60.0, 55.0, 50.0, 45.0, 40.0, 35.0, 30.0, 25.0, - 20.0, - ], - idx: AtomicUsize::new(0), - threshold_low: Mutex::new(0.0), - threshold_high: Mutex::new(0.0), - } - } - - // Return the current sample and move to next sample (wrapping around at end) - fn sample_and_next(&self) -> f32 { - self.samples[self - .idx - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |idx| { - Some((idx + 1) % self.samples.len()) - }) - .unwrap()] - } - - async fn set_threshold_low(&self, threshold: f32) { - *self.threshold_low.lock().await = threshold - } - - async fn set_threshold_high(&self, threshold: f32) { - *self.threshold_high.lock().await = threshold - } -} - -#[derive(Copy, Clone, Debug)] -struct MockSensorError; -impl sensor::Error for MockSensorError { - fn kind(&self) -> sensor::ErrorKind { - sensor::ErrorKind::Other - } -} - -// A mock temperature sensor -struct MockSensor { - bus: &'static MockBus, -} - -impl MockSensor { - fn new(bus: &'static MockBus) -> Self { - Self { bus } - } -} - -impl sensor::ErrorType for MockSensor { - type Error = MockSensorError; -} - -impl TemperatureSensor for MockSensor { - async fn temperature(&mut self) -> Result { - Ok(self.bus.sample_and_next()) - } -} - -impl TemperatureThresholdSet for MockSensor { - async fn set_temperature_threshold_low(&mut self, threshold: DegreesCelsius) -> Result<(), Self::Error> { - self.bus.set_threshold_low(threshold).await; - Ok(()) - } - - async fn set_temperature_threshold_high(&mut self, threshold: DegreesCelsius) -> Result<(), Self::Error> { - self.bus.set_threshold_high(threshold).await; - Ok(()) - } -} - -impl ts::sensor::CustomRequestHandler for MockSensor {} -impl ts::sensor::Controller for MockSensor {} - -#[derive(Copy, Clone, Debug)] -struct MockFanError; -impl fan::Error for MockFanError { - fn kind(&self) -> embedded_fans_async::ErrorKind { - fan::ErrorKind::Other - } -} - -// A mock fan -struct MockFan { - rpm: u16, -} - -impl MockFan { - fn new() -> Self { - Self { rpm: 0 } - } -} - -impl fan::ErrorType for MockFan { - type Error = MockFanError; -} - -impl fan::Fan for MockFan { - fn min_rpm(&self) -> u16 { - 1000 - } - - fn max_rpm(&self) -> u16 { - 5000 - } - - fn min_start_rpm(&self) -> u16 { - 1000 - } - - async fn set_speed_rpm(&mut self, rpm: u16) -> Result { - self.rpm = rpm; - Ok(rpm) - } -} - -impl fan::RpmSense for MockFan { - async fn rpm(&mut self) -> Result { - Ok(self.rpm) - } -} - -impl ts::fan::CustomRequestHandler for MockFan {} -impl ts::fan::RampResponseHandler for MockFan {} -impl ts::fan::Controller for MockFan {} - -// Simulates host receiving requests from OSPM and forwarding to thermal service #[embassy_executor::task] -async fn host() { - info!("Spawning host task"); - - static HOST: OnceLock = OnceLock::new(); - let host = HOST.get_or_init(host::Host::new); - info!("Registering host endpoint"); - comms::register_endpoint(host, &host.tp).await.unwrap(); - - let thermal_id = comms::EndpointID::Internal(comms::Internal::Thermal); - - // Set thresholds to 40 °C (3131 deciKelvin) - host.tp - .send( - thermal_id, - &ThermalRequest::ThermalSetThrsRequest { - instance_id: 0, - timeout: 0, - low: 0, - high: 3131, - }, - ) - .await - .unwrap(); - Timer::after_millis(100).await; - - // Set Fan ON temp to 40 °C (3131 deciKelvin) - host.tp - .send( - thermal_id, - &ThermalRequest::ThermalSetVarRequest { - instance_id: 0, - len: 4, - var_uuid: mptf::uuid_standard::FAN_ON_TEMP, - set_var: 3131, - }, - ) - .await - .unwrap(); - Timer::after_millis(100).await; - - // Set Fan RAMP temp to 50 °C (3231 deciKelvin) - host.tp - .send( - thermal_id, - &ThermalRequest::ThermalSetVarRequest { - instance_id: 0, - len: 4, - var_uuid: mptf::uuid_standard::FAN_RAMP_TEMP, - set_var: 3231, - }, - ) - .await - .unwrap(); - Timer::after_millis(100).await; - - // Set Fan MAX temp to 80 °C (3531 deciKelvin) - host.tp - .send( - thermal_id, - &ThermalRequest::ThermalSetVarRequest { - instance_id: 0, - len: 4, - var_uuid: mptf::uuid_standard::FAN_MAX_TEMP, - set_var: 3531, - }, - ) - .await - .unwrap(); - Timer::after_millis(100).await; - - // Wait to receive MPTF notification that threshold exceeded, then request temperature and RPM - loop { - host.alert.wait().await; - - info!("Host requesting temperature in response to threshold alert"); - host.tp - .send(thermal_id, &ThermalRequest::ThermalGetTmpRequest { instance_id: 0 }) - .await - .unwrap(); - - // Need to wait briefly before send is fixed to propagate errors and we can handle retries - Timer::after_millis(100).await; - - info!("Host requesting fan RPM in response to threshold alert"); - host.tp - .send( - thermal_id, - &ThermalRequest::ThermalGetVarRequest { - instance_id: 0, - len: 4, - var_uuid: mptf::uuid_standard::FAN_CURRENT_RPM, - }, - ) - .await - .unwrap(); - } -} - -async fn create_sensor() -> &'static ts::sensor::Sensor { - info!("Initializing mock bus"); - static BUS: OnceLock = OnceLock::new(); - let bus = BUS.get_or_init(MockBus::new); - - info!("Initializing mock sensor"); - let mock_sensor = MockSensor::new(bus); - static SENSOR: OnceLock> = OnceLock::new(); - - let profile = ts::sensor::Profile { - warn_high_threshold: 40.0, - prochot_threshold: 50.0, - crt_threshold: 80.0, - ..Default::default() - }; - SENSOR.get_or_init(|| ts::sensor::Sensor::new(ts::sensor::DeviceId(0), mock_sensor, profile)) -} +async fn run(spawner: Spawner) { + embedded_services::init().await; -async fn create_fan() -> &'static ts::fan::Fan { - info!("Initializing mock fan"); - let mock_fan = MockFan::new(); - static FAN: OnceLock> = OnceLock::new(); - FAN.get_or_init(|| ts::fan::Fan::new(ts::fan::DeviceId(0), mock_fan, ts::fan::Profile::default())) -} + static SENSOR: StaticCell = StaticCell::new(); + let sensor = SENSOR.init(ts::mock::new_sensor()); -async fn init_thermal(spawner: Spawner) { - info!("Initializing thermal service"); + static FAN: StaticCell = StaticCell::new(); + let fan = FAN.init(ts::mock::new_fan()); static SERVICE: OnceLock = OnceLock::new(); - let sensor = create_sensor().await; - let fan = create_fan().await; - - if let Ok(service) = ts::Service::new(&SERVICE, &[sensor.device()], &[fan.device()]).await { - spawner.must_spawn(mock_sensor_task(sensor, service)); - spawner.must_spawn(mock_fan_task(fan, service)); - spawner.must_spawn(handle_requests(service)); - spawner.must_spawn(handle_alerts(service)); - } else { - panic!("Failed to initialize thermal service!") - } -} - -#[embassy_executor::task] -async fn handle_alerts(service: &'static ts::Service) { - loop { - match service.wait_event().await { - ts::Event::ThresholdExceeded(ts::sensor::DeviceId(sensor_id), ts::sensor::ThresholdType::WarnHigh, _) => { - warn!("Sensor {sensor_id} exceeded WARN threshold"); - service - .send_service_msg(comms::EndpointID::External(comms::External::Host), &mptf::Notify::Warn) - .await - .unwrap() - } - ts::Event::ThresholdExceeded(ts::sensor::DeviceId(sensor_id), ts::sensor::ThresholdType::Prochot, _) => { - warn!("Sensor {sensor_id} exceeded PROCHOT threshold"); - service - .send_service_msg( - comms::EndpointID::External(comms::External::Host), - &mptf::Notify::ProcHot, - ) - .await - .unwrap() - } - ts::Event::ThresholdExceeded(ts::sensor::DeviceId(sensor_id), ts::sensor::ThresholdType::Critical, _) => { - warn!("Sensor {sensor_id} exceeded CRITICAL threshold"); - service - .send_service_msg( - comms::EndpointID::External(comms::External::Host), - &mptf::Notify::Critical, - ) - .await - .unwrap() - } - event => warn!("Event: {event:?}"), - } - } -} + let service = ts::Service::new(&SERVICE, &[sensor.device()], &[fan.device()]) + .await + .expect("Failed to initialize thermal service"); -#[embassy_executor::task] -async fn handle_requests(service: &'static ts::Service) -> ! { - ts::task::handle_requests(service).await; - unreachable!() -} - -#[embassy_executor::task] -async fn run(spawner: Spawner) { - embedded_services::init().await; - init_thermal(spawner).await; - spawner.must_spawn(host()); + spawner.must_spawn(sensor_task(service, sensor)); + spawner.must_spawn(fan_task(service, fan)); + spawner.must_spawn(handle_requests_task(service)); + spawner.must_spawn(monitor(service)); } fn main() { @@ -407,16 +37,38 @@ fn main() { } #[embassy_executor::task] -async fn mock_sensor_task( - sensor: &'static ts::sensor::Sensor, - service: &'static ts::Service, -) -> ! { - ts::task::sensor_task(sensor, service).await; - unreachable!() +async fn sensor_task(service: &'static ts::Service, sensor: &'static ts::mock::TsMockSensor) { + ts::task::sensor_task(sensor, service).await } #[embassy_executor::task] -async fn mock_fan_task(fan: &'static ts::fan::Fan, service: &'static ts::Service) -> ! { +async fn fan_task(service: &'static ts::Service, fan: &'static ts::mock::TsMockFan) { ts::task::fan_task(fan, service).await; - unreachable!() +} + +#[embassy_executor::task] +async fn handle_requests_task(service: &'static ts::Service) { + ts::task::handle_requests(service).await; +} + +#[embassy_executor::task] +async fn monitor(service: &'static ts::Service) { + loop { + match service + .execute_sensor_request(ts::mock::MOCK_SENSOR_ID, ts::sensor::Request::GetTemp) + .await + { + Ok(ts::sensor::ResponseData::Temp(temp)) => info!("Mock sensor temp: {} C", temp), + _ => error!("Failed to monitor mock sensor temp"), + } + match service + .execute_fan_request(ts::mock::MOCK_FAN_ID, ts::fan::Request::GetRpm) + .await + { + Ok(ts::fan::ResponseData::Rpm(rpm)) => info!("Mock fan RPM: {}", rpm), + _ => error!("Failed to monitor mock fan RPM"), + } + + Timer::after_secs(1).await; + } } diff --git a/thermal-service/Cargo.toml b/thermal-service/Cargo.toml index b54147689..97eacd2fc 100644 --- a/thermal-service/Cargo.toml +++ b/thermal-service/Cargo.toml @@ -41,6 +41,7 @@ log = [ "embassy-time/log", "embassy-sync/log", ] +mock = [] [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index 4057d4012..b49ac43c7 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -8,6 +8,8 @@ use embedded_services::{comms, error, info, intrusive_list}; mod context; pub mod fan; +#[cfg(feature = "mock")] +pub mod mock; pub mod mptf; pub mod sensor; pub mod task; diff --git a/thermal-service/src/mock/fan.rs b/thermal-service/src/mock/fan.rs new file mode 100644 index 000000000..c3fd5401e --- /dev/null +++ b/thermal-service/src/mock/fan.rs @@ -0,0 +1,59 @@ +use crate::fan; +use embedded_fans_async::{Error, ErrorKind, ErrorType, Fan, RpmSense}; + +/// `MockFan` error. +#[derive(Clone, Copy, Debug)] +pub struct MockFanError; +impl Error for MockFanError { + fn kind(&self) -> ErrorKind { + ErrorKind::Other + } +} + +/// Mock fan. +#[derive(Clone, Copy, Debug, Default)] +pub struct MockFan { + rpm: u16, +} + +impl MockFan { + /// Create a new `MockFan`. + pub fn new() -> Self { + Self::default() + } +} + +impl ErrorType for MockFan { + type Error = MockFanError; +} + +impl Fan for MockFan { + fn min_rpm(&self) -> u16 { + 0 + } + + fn max_rpm(&self) -> u16 { + 6000 + } + + fn min_start_rpm(&self) -> u16 { + 1000 + } + + async fn set_speed_rpm(&mut self, rpm: u16) -> Result { + self.rpm = rpm; + Ok(rpm) + } +} + +impl RpmSense for MockFan { + async fn rpm(&mut self) -> Result { + // The mock fan is simple, it just remembers the last RPM it was set to and reports that + // as its current RPM. + Ok(self.rpm) + } +} + +impl fan::CustomRequestHandler for MockFan {} +impl fan::RampResponseHandler for MockFan {} +impl fan::Controller for MockFan {} diff --git a/thermal-service/src/mock/mod.rs b/thermal-service/src/mock/mod.rs new file mode 100644 index 000000000..df78b3412 --- /dev/null +++ b/thermal-service/src/mock/mod.rs @@ -0,0 +1,57 @@ +pub mod fan; +pub mod sensor; + +const SAMPLE_BUF_LEN: usize = 16; + +// Represents the temperature ranges the mock thermal service will move through +pub(crate) const MIN_TEMP: f32 = 20.0; +pub(crate) const MAX_TEMP: f32 = 40.0; +pub(crate) const TEMP_RANGE: f32 = MAX_TEMP - MIN_TEMP; + +/// Default mock sensor ID. +pub const MOCK_SENSOR_ID: crate::sensor::DeviceId = crate::sensor::DeviceId(0); + +/// Default mock fan ID. +pub const MOCK_FAN_ID: crate::fan::DeviceId = crate::fan::DeviceId(0); + +/// A thermal-service wrapped [`sensor::MockSensor`]. +pub type TsMockSensor = crate::sensor::Sensor; + +/// A thermal-service wrapped [`fan::MockFan`]. +pub type TsMockFan = crate::fan::Fan; + +/// Creates a new mock sensor ready for use with the thermal service. +/// +/// This is a convenience wrapper, but for finer control a [`sensor::MockSensor`] can still be +/// constructed manually. +/// +/// This still needs to be wrapped in a static and registered with the thermal service, +/// and then a respective task spawned. +pub fn new_sensor() -> TsMockSensor { + let sensor = sensor::MockSensor::new(); + crate::sensor::Sensor::new(MOCK_SENSOR_ID, sensor, crate::sensor::Profile::default()) +} + +/// Creates a new mock fan ready for use with the thermal service. +/// +/// This is a convenience wrapper, but for finer control a [`fan::MockFan`] can still be +/// constructed manually. +/// +/// This still needs to be wrapped in a static and registered with the thermal service, +/// and then a respective task spawned. +pub fn new_fan() -> TsMockFan { + let fan = fan::MockFan::new(); + + // Attaches the mock sensor to the mock fan and set the fan state temps + // so that they are in range with the mock sensor + let profile = crate::fan::Profile { + sensor_id: MOCK_SENSOR_ID, + auto_control: true, + on_temp: MIN_TEMP + TEMP_RANGE / 4.0, + ramp_temp: MIN_TEMP + TEMP_RANGE / 2.0, + max_temp: MAX_TEMP - TEMP_RANGE / 4.0, + ..Default::default() + }; + + crate::fan::Fan::new(MOCK_FAN_ID, fan, profile) +} diff --git a/thermal-service/src/mock/sensor.rs b/thermal-service/src/mock/sensor.rs new file mode 100644 index 000000000..1dd2028b3 --- /dev/null +++ b/thermal-service/src/mock/sensor.rs @@ -0,0 +1,70 @@ +use crate::sensor; +use embedded_sensors_hal_async::sensor as sensor_traits; +use embedded_sensors_hal_async::temperature::{DegreesCelsius, TemperatureSensor, TemperatureThresholdSet}; + +/// `MockSensor` error. +#[derive(Clone, Copy, Debug)] +pub struct MockSensorError; +impl sensor_traits::Error for MockSensorError { + fn kind(&self) -> sensor_traits::ErrorKind { + sensor_traits::ErrorKind::Other + } +} + +impl sensor_traits::ErrorType for MockSensor { + type Error = MockSensorError; +} + +/// Mock sensor. +#[derive(Clone, Copy, Debug, Default)] +pub struct MockSensor { + temp: DegreesCelsius, + falling: bool, +} + +impl MockSensor { + /// Create a new `MockSensor`. + pub fn new() -> Self { + Self { + temp: super::MIN_TEMP, + falling: false, + } + } +} + +impl TemperatureSensor for MockSensor { + async fn temperature(&mut self) -> Result { + let t = self.temp; + + // Creates a sawtooth pattern + if self.falling { + self.temp -= 1.0; + if self.temp <= super::MIN_TEMP { + self.temp = super::MIN_TEMP; + self.falling = false; + } + } else { + self.temp += 1.0; + if self.temp >= super::MAX_TEMP { + self.temp = super::MAX_TEMP; + self.falling = true; + } + } + + Ok(t) + } +} + +// Setting a threshold for `MockSensor` doesn't make sense so immediately return error +impl TemperatureThresholdSet for MockSensor { + async fn set_temperature_threshold_low(&mut self, _threshold: DegreesCelsius) -> Result<(), Self::Error> { + Err(MockSensorError) + } + + async fn set_temperature_threshold_high(&mut self, _threshold: DegreesCelsius) -> Result<(), Self::Error> { + Err(MockSensorError) + } +} + +impl sensor::CustomRequestHandler for MockSensor {} +impl sensor::Controller for MockSensor {} From 16c4364c8c8c6b61c9c01661c8d1488cb55fd6aa Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Wed, 11 Feb 2026 10:30:25 -0800 Subject: [PATCH 20/79] battery-service: Fix ACPI response (#713) During refactor, the battery-service was changed to only respond with a `AcpiBatteryResponse`. However, the comms service expects a `Result` so this PR just makes that fix. --- battery-service/src/lib.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index d308fd103..7b5d21f64 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -66,18 +66,18 @@ impl Service { } Event::AcpiRequest(acpi_msg) => { trace!("Battery service: ACPI cmd recvd"); - match self.context.process_acpi_cmd(&acpi_msg).await { - Ok(response) => { - // TODO We should probably be responding to the requestor rather than just assuming the request came from the host - self.comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &response, - ) - .await - .expect("comms_send is infallible") - } - Err(e) => error!("Battery service command failed: {:?}", e), + let response = self.context.process_acpi_cmd(&acpi_msg).await; + if let Err(e) = response { + error!("Battery service command failed: {:?}", e) } + + // TODO We should probably be responding to the requestor rather than just assuming the request came from the host + self.comms_send( + crate::EndpointID::External(embedded_services::comms::External::Host), + &response, + ) + .await + .expect("comms_send is infallible") } } } From ef2ed88d70ae8f1e45ca5c327378664d1dccdf30 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:44:19 -0800 Subject: [PATCH 21/79] Refactor power policy to use direct async calls with power devices (#553) * Instead of IPC between the power policy task and power devices, direct async function calls are used through the new `DeviceTrait` trait. * Power policy now holds references to types that `impl DeviceTrait` instead of using channels * There's now a per-device channel for sending events to the power policy instead of a single shared channel * Remove type-stated state machines, these just ended up in a shared enum so there wasn't much benefit while resulting in duplication of common logic. * Introduce first power policy integration test for default consumer switching logic * Update examples * Introduce temporary bridge code until type-C code is similarly refactored (types in `type_c_service::wrapper::proxy`). --- Cargo.lock | 181 ++++++++- embedded-service/Cargo.toml | 2 +- embedded-service/src/event.rs | 43 ++ embedded-service/src/lib.rs | 1 + .../src/power/policy/action/device.rs | 184 --------- .../src/power/policy/action/mod.rs | 51 --- .../src/power/policy/action/policy.rs | 238 ----------- embedded-service/src/power/policy/device.rs | 326 +++++++++------ embedded-service/src/power/policy/mod.rs | 3 +- embedded-service/src/power/policy/policy.rs | 124 +++--- examples/pico-de-gallo/Cargo.lock | 254 ++++-------- examples/rt633/Cargo.lock | 1 + examples/rt685s-evk/Cargo.lock | 1 + examples/rt685s-evk/src/bin/type_c.rs | 182 ++++++--- examples/rt685s-evk/src/bin/type_c_cfu.rs | 191 +++++---- examples/rt685s-evk/src/lib.rs | 18 - examples/std/Cargo.lock | 214 ++++++---- examples/std/Cargo.toml | 2 +- examples/std/src/bin/power_policy.rs | 368 ++++++++++------- examples/std/src/bin/type_c/basic.rs | 33 +- examples/std/src/bin/type_c/external.rs | 72 ++-- examples/std/src/bin/type_c/service.rs | 226 +++++------ examples/std/src/bin/type_c/ucsi.rs | 212 ++++++---- examples/std/src/bin/type_c/unconstrained.rs | 382 ++++++++++-------- .../std/src/lib/type_c/mock_controller.rs | 30 +- examples/std/src/lib/type_c/mod.rs | 18 - power-policy-service/Cargo.toml | 8 + power-policy-service/src/consumer.rs | 68 ++-- power-policy-service/src/lib.rs | 125 ++++-- power-policy-service/src/provider.rs | 85 ++-- power-policy-service/src/task.rs | 66 +-- power-policy-service/tests/common/mock.rs | 82 ++++ power-policy-service/tests/common/mod.rs | 134 ++++++ power-policy-service/tests/consumer.rs | 135 +++++++ supply-chain/audits.toml | 77 ++++ supply-chain/imports.lock | 88 +++- type-c-service/src/service/mod.rs | 15 +- type-c-service/src/task.rs | 41 +- type-c-service/src/wrapper/backing.rs | 235 ++++++++--- type-c-service/src/wrapper/cfu.rs | 70 +--- type-c-service/src/wrapper/dp.rs | 18 +- type-c-service/src/wrapper/message.rs | 14 +- type-c-service/src/wrapper/mod.rs | 180 +++++---- type-c-service/src/wrapper/pd.rs | 65 ++- type-c-service/src/wrapper/power.rs | 216 +++------- type-c-service/src/wrapper/proxy.rs | 105 +++++ type-c-service/src/wrapper/vdm.rs | 24 +- 47 files changed, 2911 insertions(+), 2297 deletions(-) create mode 100644 embedded-service/src/event.rs delete mode 100644 embedded-service/src/power/policy/action/device.rs delete mode 100644 embedded-service/src/power/policy/action/mod.rs delete mode 100644 embedded-service/src/power/policy/action/policy.rs create mode 100644 power-policy-service/tests/common/mock.rs create mode 100644 power-policy-service/tests/common/mod.rs create mode 100644 power-policy-service/tests/consumer.rs create mode 100644 type-c-service/src/wrapper/proxy.rs diff --git a/Cargo.lock b/Cargo.lock index 70d6b11bd..e1c633b9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,56 @@ dependencies = [ "as-slice", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -365,6 +415,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "const-init" version = "1.0.0" @@ -1000,6 +1056,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1221,6 +1300,12 @@ dependencies = [ "libc", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1254,6 +1339,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "kdl" version = "6.3.4" @@ -1609,6 +1718,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "partition-manager" version = "0.1.0" @@ -1703,6 +1818,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "power-button-service" version = "0.1.0" @@ -1718,13 +1842,17 @@ dependencies = [ name = "power-policy-service" version = "0.1.0" dependencies = [ + "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", "embedded-services", + "env_logger", "heapless", "log", + "static_cell", + "tokio", ] [[package]] @@ -1946,18 +2074,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2436,6 +2574,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.17.0" @@ -2496,7 +2640,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -2517,7 +2661,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -2529,7 +2673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -2561,6 +2705,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -2568,7 +2718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2577,7 +2727,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2586,7 +2736,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -2607,6 +2757,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2629,7 +2788,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index 2570775c5..4199d9ec8 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -21,6 +21,7 @@ defmt = { workspace = true, optional = true } document-features.workspace = true embassy-sync.workspace = true embassy-time.workspace = true +embassy-futures.workspace = true embedded-batteries-async.workspace = true embedded-cfu-protocol.workspace = true embedded-hal-async.workspace = true @@ -49,7 +50,6 @@ cortex-m.workspace = true [dev-dependencies] critical-section = { workspace = true, features = ["std"] } -embassy-futures.workspace = true embassy-sync = { workspace = true, features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } embassy-time-driver = { workspace = true } diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs new file mode 100644 index 000000000..416a92c50 --- /dev/null +++ b/embedded-service/src/event.rs @@ -0,0 +1,43 @@ +//! Common traits for event senders and receivers + +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; + +/// Common event sender trait +pub trait Sender { + /// Attempt to send an event + /// + /// Return none if the event cannot currently be sent + fn try_send(&mut self, event: E) -> Option<()>; + /// Send an event + fn send(&mut self, event: E) -> impl Future; +} + +/// Common event receiver trait +pub trait Receiver { + /// Attempt to receive an event + /// + /// Return none if there are no pending events + fn try_next(&mut self) -> Option; + /// Receive an event + fn wait_next(&mut self) -> impl Future; +} + +impl Sender for DynamicSender<'_, E> { + fn try_send(&mut self, event: E) -> Option<()> { + DynamicSender::try_send(self, event).ok() + } + + fn send(&mut self, event: E) -> impl Future { + DynamicSender::send(self, event) + } +} + +impl Receiver for DynamicReceiver<'_, E> { + fn try_next(&mut self) -> Option { + self.try_receive().ok() + } + + fn wait_next(&mut self) -> impl Future { + self.receive() + } +} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 223bca9af..18c4cd932 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -15,6 +15,7 @@ pub mod activity; pub mod broadcaster; pub mod buffer; pub mod comms; +pub mod event; pub mod fmt; pub mod hid; pub mod init; diff --git a/embedded-service/src/power/policy/action/device.rs b/embedded-service/src/power/policy/action/device.rs deleted file mode 100644 index b08a7681f..000000000 --- a/embedded-service/src/power/policy/action/device.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Device state machine actions -use super::*; -use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device, policy}; -use crate::{info, trace}; - -/// Device state machine control -pub struct Device<'a, S: Kind, const N: usize> { - device: &'a device::Device, - _state: core::marker::PhantomData, -} - -/// Enum to contain any state -pub enum AnyState<'a, const N: usize> { - /// Detached - Detached(Device<'a, Detached, N>), - /// Idle - Idle(Device<'a, Idle, N>), - /// Connected Consumer - ConnectedConsumer(Device<'a, ConnectedConsumer, N>), - /// Connected Provider - ConnectedProvider(Device<'a, ConnectedProvider, N>), -} - -impl AnyState<'_, N> { - /// Return the kind of the contained state - pub fn kind(&self) -> StateKind { - match self { - AnyState::Detached(_) => StateKind::Detached, - AnyState::Idle(_) => StateKind::Idle, - AnyState::ConnectedConsumer(_) => StateKind::ConnectedConsumer, - AnyState::ConnectedProvider(_) => StateKind::ConnectedProvider, - } - } -} - -impl<'a, S: Kind, const N: usize> Device<'a, S, N> { - /// Create a new state machine - pub(crate) fn new(device: &'a device::Device) -> Self { - Self { - device, - _state: core::marker::PhantomData, - } - } - - /// Detach the device - pub async fn detach(self) -> Result, Error> { - info!("Received detach from device {}", self.device.id().0); - self.device.set_state(device::State::Detached).await; - self.device.update_consumer_capability(None).await; - self.device.update_requested_provider_capability(None).await; - self.device - .context_ref - .send_request(self.device.id(), policy::RequestData::NotifyDetached) - .await? - .complete_or_err()?; - Ok(Device::new(self.device)) - } - - /// Disconnect this device - async fn disconnect_internal(&self) -> Result<(), Error> { - info!("Device {} disconnecting", self.device.id().0); - self.device.update_consumer_capability(None).await; - self.device.update_requested_provider_capability(None).await; - self.device.set_state(device::State::Idle).await; - self.device - .context_ref - .send_request(self.device.id(), policy::RequestData::NotifyDisconnect) - .await? - .complete_or_err() - } - - /// Notify the power policy service of an updated consumer power capability - async fn notify_consumer_power_capability_internal( - &self, - capability: Option, - ) -> Result<(), Error> { - info!( - "Device {} consume capability updated: {:#?}", - self.device.id().0, - capability - ); - self.device.update_consumer_capability(capability).await; - self.device - .context_ref - .send_request( - self.device.id(), - policy::RequestData::NotifyConsumerCapability(capability), - ) - .await? - .complete_or_err() - } - - /// Request the given power from the power policy service - async fn request_provider_power_capability_internal( - &self, - capability: ProviderPowerCapability, - ) -> Result<(), Error> { - if self.device.provider_capability().await == Some(capability) { - // Already operating at this capability, power policy is already aware, don't need to do anything - trace!("Device {} already requested: {:#?}", self.device.id().0, capability); - return Ok(()); - } - - info!("Request provide from device {}, {:#?}", self.device.id().0, capability); - self.device.update_requested_provider_capability(Some(capability)).await; - self.device - .context_ref - .send_request( - self.device.id(), - policy::RequestData::RequestProviderCapability(capability), - ) - .await? - .complete_or_err()?; - Ok(()) - } -} - -impl<'a, const N: usize> Device<'a, Detached, N> { - /// Attach the device - pub async fn attach(self) -> Result, Error> { - info!("Received attach from device {}", self.device.id().0); - self.device.set_state(device::State::Idle).await; - self.device - .context_ref - .send_request(self.device.id(), policy::RequestData::NotifyAttached) - .await? - .complete_or_err()?; - Ok(Device::new(self.device)) - } -} - -impl Device<'_, Idle, N> { - /// Notify the power policy service of an updated consumer power capability - pub async fn notify_consumer_power_capability( - &self, - capability: Option, - ) -> Result<(), Error> { - self.notify_consumer_power_capability_internal(capability).await - } - - /// Request the given power from the power policy service - pub async fn request_provider_power_capability(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - self.request_provider_power_capability_internal(capability).await - } -} - -impl<'a, const N: usize> Device<'a, ConnectedConsumer, N> { - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - self.disconnect_internal().await?; - Ok(Device::new(self.device)) - } - - /// Notify the power policy service of an updated consumer power capability - pub async fn notify_consumer_power_capability( - &self, - - capability: Option, - ) -> Result<(), Error> { - self.notify_consumer_power_capability_internal(capability).await - } -} - -impl<'a, const N: usize> Device<'a, ConnectedProvider, N> { - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - self.disconnect_internal().await?; - Ok(Device::new(self.device)) - } - - /// Request the given power from the power policy service - pub async fn request_provider_power_capability(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - self.request_provider_power_capability_internal(capability).await - } - - /// Notify the power policy service of an updated consumer power capability - pub async fn notify_consumer_power_capability( - &self, - - capability: Option, - ) -> Result<(), Error> { - self.notify_consumer_power_capability_internal(capability).await - } -} diff --git a/embedded-service/src/power/policy/action/mod.rs b/embedded-service/src/power/policy/action/mod.rs deleted file mode 100644 index 889dccf70..000000000 --- a/embedded-service/src/power/policy/action/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Power policy actions -//! This modules contains wrapper structs that use type states to enforce the valid actions for each device state -use super::device::StateKind; - -pub mod device; -pub mod policy; - -trait Sealed {} - -/// Trait to provide the kind of a state type -#[allow(private_bounds)] -pub trait Kind: Sealed { - /// Return the kind of a state type - fn kind() -> StateKind; -} - -/// State type for a detached device -pub struct Detached; -impl Sealed for Detached {} -impl Kind for Detached { - fn kind() -> StateKind { - StateKind::Detached - } -} - -/// State type for an attached device -pub struct Idle; -impl Sealed for Idle {} -impl Kind for Idle { - fn kind() -> StateKind { - StateKind::Idle - } -} - -/// State type for a device that is providing power -pub struct ConnectedProvider; -impl Sealed for ConnectedProvider {} -impl Kind for ConnectedProvider { - fn kind() -> StateKind { - StateKind::ConnectedProvider - } -} - -/// State type for a device that is consuming power -pub struct ConnectedConsumer; -impl Sealed for ConnectedConsumer {} -impl Kind for ConnectedConsumer { - fn kind() -> StateKind { - StateKind::ConnectedConsumer - } -} diff --git a/embedded-service/src/power/policy/action/policy.rs b/embedded-service/src/power/policy/action/policy.rs deleted file mode 100644 index 540ff762d..000000000 --- a/embedded-service/src/power/policy/action/policy.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! Policy state machine -use embassy_time::{Duration, TimeoutError, with_timeout}; - -use super::*; -use crate::power::policy::{ConsumerPowerCapability, Error, ProviderPowerCapability, device}; -use crate::{error, info}; - -/// Default timeout for device commands to prevent the policy from getting stuck -const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); - -/// Policy state machine control -pub struct Policy<'a, S: Kind, const N: usize> { - device: &'a device::Device, - _state: core::marker::PhantomData, -} - -/// Enum to contain any state -pub enum AnyState<'a, const N: usize> { - /// Detached - Detached(Policy<'a, Detached, N>), - /// Idle - Idle(Policy<'a, Idle, N>), - /// Connected Consumer - ConnectedConsumer(Policy<'a, ConnectedConsumer, N>), - /// Connected Provider - ConnectedProvider(Policy<'a, ConnectedProvider, N>), -} - -impl AnyState<'_, N> { - /// Return the kind of the contained state - pub fn kind(&self) -> StateKind { - match self { - AnyState::Detached(_) => StateKind::Detached, - AnyState::Idle(_) => StateKind::Idle, - AnyState::ConnectedConsumer(_) => StateKind::ConnectedConsumer, - AnyState::ConnectedProvider(_) => StateKind::ConnectedProvider, - } - } -} - -impl<'a, S: Kind, const N: usize> Policy<'a, S, N> { - /// Create a new state machine - pub(crate) fn new(device: &'a device::Device) -> Self { - Self { - device, - _state: core::marker::PhantomData, - } - } - - /// Common disconnect function used by multiple states - async fn disconnect_internal_no_timeout(&self) -> Result<(), Error> { - info!("Device {} got disconnect request", self.device.id().0); - self.device - .execute_device_command(device::CommandData::Disconnect) - .await? - .complete_or_err()?; - self.device.set_state(device::State::Idle).await; - Ok(()) - } - - /// Common disconnect function used by multiple states - async fn disconnect_internal(&self) -> Result<(), Error> { - match with_timeout(DEFAULT_TIMEOUT, self.disconnect_internal_no_timeout()).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Common connect as provider function used by multiple states - async fn connect_as_provider_internal_no_timeout(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - info!("Device {} connecting provider", self.device.id().0); - - self.device - .execute_device_command(device::CommandData::ConnectAsProvider(capability)) - .await? - .complete_or_err()?; - - self.device - .set_state(device::State::ConnectedProvider(capability)) - .await; - - Ok(()) - } - - /// Common connect provider function used by multiple states - async fn connect_provider_internal(&self, capability: ProviderPowerCapability) -> Result<(), Error> { - match with_timeout( - DEFAULT_TIMEOUT, - self.connect_as_provider_internal_no_timeout(capability), - ) - .await - { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } -} - -// The policy can do nothing when no device is attached -impl Policy<'_, Detached, N> {} - -impl<'a, const N: usize> Policy<'a, Idle, N> { - /// Connect this device as a consumer - pub async fn connect_as_consumer_no_timeout( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - info!("Device {} connecting as consumer", self.device.id().0); - - self.device - .execute_device_command(device::CommandData::ConnectAsConsumer(capability)) - .await? - .complete_or_err()?; - - self.device - .set_state(device::State::ConnectedConsumer(capability)) - .await; - Ok(Policy::new(self.device)) - } - - /// Connect this device as a consumer - pub async fn connect_consumer( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Connect this device as a provider - pub async fn connect_provider_no_timeout( - self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_as_provider_internal_no_timeout(capability) - .await - .map(|_| Policy::new(self.device)) - } - - /// Connect this device as a provider - pub async fn connect_provider( - self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_provider_internal(capability) - .await - .map(|_| Policy::new(self.device)) - } -} - -impl<'a, const N: usize> Policy<'a, ConnectedConsumer, N> { - /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { - self.disconnect_internal_no_timeout() - .await - .map(|_| Policy::new(self.device)) - } - - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - self.disconnect_internal().await.map(|_| Policy::new(self.device)) - } -} - -impl<'a, const N: usize> Policy<'a, ConnectedProvider, N> { - /// Disconnect this device - pub async fn disconnect_no_timeout(self) -> Result, Error> { - if let Err(e) = self.disconnect_internal_no_timeout().await { - error!("Error disconnecting device {}: {:?}", self.device.id().0, e); - return Err(e); - } - Ok(Policy::new(self.device)) - } - - /// Disconnect this device - pub async fn disconnect(self) -> Result, Error> { - match with_timeout(DEFAULT_TIMEOUT, self.disconnect_no_timeout()).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Connect this device as a consumer - pub async fn connect_as_consumer_no_timeout( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - info!("Device {} connecting as consumer", self.device.id().0); - - self.device - .execute_device_command(device::CommandData::ConnectAsConsumer(capability)) - .await? - .complete_or_err()?; - - self.device - .set_state(device::State::ConnectedConsumer(capability)) - .await; - Ok(Policy::new(self.device)) - } - - /// Connect this device as a consumer - pub async fn connect_consumer( - self, - capability: ConsumerPowerCapability, - ) -> Result, Error> { - match with_timeout(DEFAULT_TIMEOUT, self.connect_as_consumer_no_timeout(capability)).await { - Ok(r) => r, - Err(TimeoutError) => Err(Error::Timeout), - } - } - - /// Connect this device as a provider - pub async fn connect_provider_no_timeout( - &self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_as_provider_internal_no_timeout(capability) - .await - .map(|_| Policy::new(self.device)) - } - - /// Connect this device as a provider - pub async fn connect_provider( - &self, - capability: ProviderPowerCapability, - ) -> Result, Error> { - self.connect_provider_internal(capability) - .await - .map(|_| Policy::new(self.device)) - } - - /// Get the provider power capability of this device - pub async fn power_capability(&self) -> Option { - self.device.provider_capability().await - } -} diff --git a/embedded-service/src/power/policy/device.rs b/embedded-service/src/power/policy/device.rs index 45fe2fcdd..b93d5a393 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/embedded-service/src/power/policy/device.rs @@ -1,12 +1,11 @@ //! Device struct and methods -use core::ops::DerefMut; - use embassy_sync::mutex::Mutex; -use super::{DeviceId, Error, action}; -use crate::ipc::deferred; -use crate::power::policy::policy::Context; +use super::{DeviceId, Error}; +use crate::event::Receiver; +use crate::power::policy::policy::RequestData; use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; +use crate::sync::Lockable; use crate::{GlobalRawMutex, intrusive_list}; /// Most basic device states @@ -49,16 +48,159 @@ impl State { } } -/// Internal device state for power policy +/// Per-device state for power policy implementation +/// +/// This struct implements the state machine outlined in the docs directory. +/// The various state transition functions always succeed in the sense that +/// the desired state is always entered, but some still return a result. +/// This is because a the device that is driving this state machine is the +/// ultimate source of truth and the recovery procedure would ultimately +/// end up catching up to this state anyway. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct InternalState { +pub struct InternalState { /// Current state of the device - pub state: State, + state: State, /// Current consumer capability - pub consumer_capability: Option, + consumer_capability: Option, /// Current requested provider capability - pub requested_provider_capability: Option, + requested_provider_capability: Option, +} + +impl Default for InternalState { + fn default() -> Self { + Self { + state: State::Detached, + consumer_capability: None, + requested_provider_capability: None, + } + } +} + +impl InternalState { + /// Attach the device + pub fn attach(&mut self) -> Result<(), Error> { + let result = if self.state == State::Detached { + Ok(()) + } else { + Err(Error::InvalidState(&[StateKind::Detached], self.state.kind())) + }; + self.state = State::Idle; + result + } + + /// Detach the device + /// + /// Detach is always a valid transition + pub fn detach(&mut self) { + self.state = State::Detached; + self.consumer_capability = None; + self.requested_provider_capability = None; + } + + /// Disconnect this device + pub fn disconnect(&mut self, clear_caps: bool) -> Result<(), Error> { + let result = if matches!(self.state, State::ConnectedConsumer(_) | State::ConnectedProvider(_)) { + Ok(()) + } else { + Err(Error::InvalidState( + &[StateKind::ConnectedConsumer, StateKind::ConnectedProvider], + self.state.kind(), + )) + }; + self.state = State::Idle; + if clear_caps { + self.consumer_capability = None; + self.requested_provider_capability = None; + } + result + } + + /// Update the available consumer capability + pub fn update_consumer_power_capability( + &mut self, + capability: Option, + ) -> Result<(), Error> { + let result = match self.state { + State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => Ok(()), + _ => Err(Error::InvalidState( + &[ + StateKind::Idle, + StateKind::ConnectedConsumer, + StateKind::ConnectedProvider, + ], + self.state.kind(), + )), + }; + self.consumer_capability = capability; + result + } + + /// Update the requested provider capability + pub fn update_requested_provider_power_capability( + &mut self, + capability: Option, + ) -> Result<(), Error> { + if self.requested_provider_capability == capability { + // Already operating at this capability, power policy is already aware, don't need to do anything + return Ok(()); + } + + let result = match self.state { + State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => Ok(()), + _ => Err(Error::InvalidState( + &[ + StateKind::Idle, + StateKind::ConnectedProvider, + StateKind::ConnectedConsumer, + ], + self.state.kind(), + )), + }; + + self.requested_provider_capability = capability; + result + } + + /// Handle a request to connect as a consumer from the policy + pub fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { + let result = if self.state == State::Idle { + Ok(()) + } else { + Err(Error::InvalidState(&[StateKind::Idle], self.state.kind())) + }; + self.state = State::ConnectedConsumer(capability); + result + } + + /// Handle a request to connect as a provider from the policy + pub fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + let result = if matches!(self.state, State::Idle | State::ConnectedProvider(_)) { + Ok(()) + } else { + Err(Error::InvalidState( + &[StateKind::Idle, StateKind::ConnectedProvider], + self.state.kind(), + )) + }; + self.state = State::ConnectedProvider(capability); + result + } + + /// Returns the current state machine state + pub fn state(&self) -> State { + self.state + } + + /// Returns the current consumer capability + pub fn consumer_capability(&self) -> Option { + self.consumer_capability + } + + /// Returns the requested provider capability + pub fn requested_provider_capability(&self) -> Option { + self.requested_provider_capability + } } /// Data for a device request @@ -111,23 +253,39 @@ pub struct Response { pub data: ResponseData, } -/// Device struct -pub struct Device { +/// Trait for PSU devices +pub trait DeviceTrait { + /// Disconnect power from this device + fn disconnect(&mut self) -> impl Future>; + /// Connect this device to provide power to an external connection + fn connect_provider(&mut self, capability: ProviderPowerCapability) -> impl Future>; + /// Connect this device to consume power from an external connection + fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> impl Future>; +} + +/// PSU registration struct +pub struct Device<'a, D: Lockable, R: Receiver> +where + D::Inner: DeviceTrait, +{ /// Intrusive list node node: intrusive_list::Node, /// Device ID id: DeviceId, /// Current state of the device - state: Mutex, - /// Command channel - command: deferred::Channel, - /// Reference back to the power policy context to send messages - pub(crate) context_ref: &'static Context, + pub state: Mutex, + /// Reference to hardware + pub device: &'a D, + /// Event receiver + pub receiver: Mutex, } -impl Device { +impl<'a, D: Lockable, R: Receiver> Device<'a, D, R> +where + D::Inner: DeviceTrait, +{ /// Create a new device - pub fn new(id: DeviceId, context_ref: &'static Context) -> Self { + pub fn new(id: DeviceId, device: &'a D, receiver: R) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -136,8 +294,8 @@ impl Device { consumer_capability: None, requested_provider_capability: None, }), - command: deferred::Channel::new(), - context_ref, + device, + receiver: Mutex::new(receiver), } } @@ -146,11 +304,6 @@ impl Device { self.id } - /// Returns the current state of the device - pub async fn state(&self) -> State { - self.state.lock().await.state - } - /// Returns the current consumer capability of the device pub async fn consumer_capability(&self) -> Option { self.state.lock().await.consumer_capability @@ -158,12 +311,12 @@ impl Device { /// Returns true if the device is currently consuming power pub async fn is_consumer(&self) -> bool { - self.state().await.kind() == StateKind::ConnectedConsumer + self.state.lock().await.state.kind() == StateKind::ConnectedConsumer } /// Returns current provider power capability pub async fn provider_capability(&self) -> Option { - match self.state().await { + match self.state.lock().await.state { State::ConnectedProvider(capability) => Some(capability), _ => None, } @@ -176,120 +329,33 @@ impl Device { /// Returns true if the device is currently providing power pub async fn is_provider(&self) -> bool { - self.state().await.kind() == StateKind::ConnectedProvider - } - - /// Execute a command on the device - pub(super) async fn execute_device_command(&self, command: CommandData) -> Result { - self.command.execute(command).await - } - - /// Create a handler for the command channel - /// - /// DROP SAFETY: Direct call to deferred channel primitive - pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, CommandData, InternalResponseData> { - self.command.receive().await - } - - /// Internal function to set device state - pub(super) async fn set_state(&self, new_state: State) { - let mut lock = self.state.lock().await; - let state = lock.deref_mut(); - state.state = new_state; - } - - /// Internal function to set consumer capability - pub(super) async fn update_consumer_capability(&self, capability: Option) { - let mut lock = self.state.lock().await; - let state = lock.deref_mut(); - state.consumer_capability = capability; - } - - /// Internal function to set requested provider capability - pub(super) async fn update_requested_provider_capability(&self, capability: Option) { - let mut lock = self.state.lock().await; - let state = lock.deref_mut(); - state.requested_provider_capability = capability; - } - - /// Try to provide access to the device actions for the given state - pub async fn try_device_action( - &self, - ) -> Result, Error> { - let state = self.state().await.kind(); - if S::kind() != state { - return Err(Error::InvalidState(S::kind(), state)); - } - Ok(action::device::Device::new(self)) - } - - /// Provide access to the current device state - pub async fn device_action(&self) -> action::device::AnyState<'_, CHANNEL_SIZE> { - match self.state().await.kind() { - StateKind::Detached => action::device::AnyState::Detached(action::device::Device::new(self)), - StateKind::Idle => action::device::AnyState::Idle(action::device::Device::new(self)), - StateKind::ConnectedProvider => { - action::device::AnyState::ConnectedProvider(action::device::Device::new(self)) - } - StateKind::ConnectedConsumer => { - action::device::AnyState::ConnectedConsumer(action::device::Device::new(self)) - } - } - } - - /// Try to provide access to the policy actions for the given state - /// Implemented here for lifetime reasons - pub(super) async fn try_policy_action( - &self, - ) -> Result, Error> { - let state = self.state().await.kind(); - if S::kind() != state { - return Err(Error::InvalidState(S::kind(), state)); - } - Ok(action::policy::Policy::new(self)) - } - - /// Provide access to the current policy actions - /// Implemented here for lifetime reasons - pub(super) async fn policy_action(&self) -> action::policy::AnyState<'_, CHANNEL_SIZE> { - match self.state().await.kind() { - StateKind::Detached => action::policy::AnyState::Detached(action::policy::Policy::new(self)), - StateKind::Idle => action::policy::AnyState::Idle(action::policy::Policy::new(self)), - StateKind::ConnectedProvider => { - action::policy::AnyState::ConnectedProvider(action::policy::Policy::new(self)) - } - StateKind::ConnectedConsumer => { - action::policy::AnyState::ConnectedConsumer(action::policy::Policy::new(self)) - } - } - } - - /// Detach the device, this action is available in all states - pub async fn detach(&self) -> Result, Error> { - match self.device_action().await { - action::device::AnyState::Detached(state) => Ok(state), - action::device::AnyState::Idle(state) => state.detach().await, - action::device::AnyState::ConnectedProvider(state) => state.detach().await, - action::device::AnyState::ConnectedConsumer(state) => state.detach().await, - } + self.state.lock().await.state.kind() == StateKind::ConnectedProvider } } -// This must be static due to a bound on IntrusiveNode needing a static reference to Any -impl intrusive_list::NodeContainer for Device { +impl + 'static> intrusive_list::NodeContainer for Device<'static, D, R> +where + D::Inner: DeviceTrait, +{ fn get_node(&self) -> &crate::Node { &self.node } } /// Trait for any container that holds a device -pub trait DeviceContainer { +pub trait DeviceContainer> +where + D::Inner: DeviceTrait, +{ /// Get the underlying device struct - fn get_power_policy_device(&self) -> &Device; + fn get_power_policy_device(&self) -> &Device<'_, D, R>; } -impl DeviceContainer for Device { - fn get_power_policy_device(&self) -> &Device { +impl> DeviceContainer for Device<'_, D, R> +where + D::Inner: DeviceTrait, +{ + fn get_power_policy_device(&self) -> &Device<'_, D, R> { self } } diff --git a/embedded-service/src/power/policy/mod.rs b/embedded-service/src/power/policy/mod.rs index 3b1d4eb76..1539d4dd7 100644 --- a/embedded-service/src/power/policy/mod.rs +++ b/embedded-service/src/power/policy/mod.rs @@ -1,5 +1,4 @@ //! Power policy related data structures and messages -pub mod action; pub mod charger; pub mod device; pub mod flags; @@ -20,7 +19,7 @@ pub enum Error { /// The consume request was denied, contains maximum available power CannotConsume(Option), /// The device is not in the correct state (expected, actual) - InvalidState(device::StateKind, device::StateKind), + InvalidState(&'static [device::StateKind], device::StateKind), /// Invalid response InvalidResponse, /// Busy, the device cannot respond to the request at this time diff --git a/embedded-service/src/power/policy/policy.rs b/embedded-service/src/power/policy/policy.rs index 25cf25976..e000630df 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/embedded-service/src/power/policy/policy.rs @@ -1,13 +1,17 @@ //! Context for any power policy implementations +use core::marker::PhantomData; +use core::pin::pin; -use crate::GlobalRawMutex; use crate::broadcaster::immediate as broadcaster; +use crate::event::Receiver; +use crate::power::policy::device::DeviceTrait; use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; -use embassy_sync::channel::Channel; +use crate::sync::Lockable; +use embassy_futures::select::select_slice; use super::charger::ChargerResponse; use super::device::{self}; -use super::{DeviceId, Error, action, charger}; +use super::{DeviceId, Error, charger}; use crate::power::policy::charger::ChargerResponseData::Ack; use crate::{error, intrusive_list}; @@ -16,15 +20,15 @@ use crate::{error, intrusive_list}; #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RequestData { /// Notify that a device has attached - NotifyAttached, + Attached, /// Notify that available power for consumption has changed - NotifyConsumerCapability(Option), + UpdatedConsumerCapability(Option), /// Request the given amount of power to provider - RequestProviderCapability(ProviderPowerCapability), + RequestedProviderCapability(Option), /// Notify that a device cannot consume or provide power anymore - NotifyDisconnect, + Disconnected, /// Notify that a device has detached - NotifyDetached, + Detached, } /// Request to the power policy service @@ -64,51 +68,52 @@ pub struct Response { pub data: ResponseData, } -/// Wrapper type to make code cleaner -type InternalResponseData = Result; - /// Power policy context -pub struct Context { +pub struct Context> +where + D::Inner: DeviceTrait, +{ /// Registered devices power_devices: intrusive_list::IntrusiveList, - /// Policy request - policy_request: Channel, - /// Policy response - policy_response: Channel, /// Registered chargers charger_devices: intrusive_list::IntrusiveList, /// Message broadcaster broadcaster: broadcaster::Immediate, + _phantom: PhantomData<(D, R)>, } -impl Default for Context { +impl + 'static> Default for Context +where + D::Inner: DeviceTrait, +{ fn default() -> Self { Self::new() } } -impl Context { +impl + 'static> Context +where + D::Inner: DeviceTrait, +{ /// Construct a new power policy Context pub const fn new() -> Self { Self { power_devices: intrusive_list::IntrusiveList::new(), charger_devices: intrusive_list::IntrusiveList::new(), - policy_request: Channel::new(), - policy_response: Channel::new(), broadcaster: broadcaster::Immediate::new(), + _phantom: PhantomData, } } - /// Register a device with the power policy service + /// Register a power device with the service pub fn register_device( &self, - device: &'static impl device::DeviceContainer, + device: &'static impl device::DeviceContainer, ) -> Result<(), intrusive_list::Error> { let device = device.get_power_policy_device(); if self.get_device(device.id()).is_ok() { return Err(intrusive_list::Error::NodeAlreadyInList); } - self.power_devices.push(device) } @@ -126,9 +131,9 @@ impl Context { } /// Get a device by its ID - pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device, Error> { + pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device<'static, D, R>, Error> { for device in &self.power_devices { - if let Some(data) = device.data::>() { + if let Some(data) = device.data::>() { if data.id() == id { return Ok(data); } @@ -143,13 +148,14 @@ impl Context { /// Returns the total amount of power that is being supplied to external devices pub async fn compute_total_provider_power_mw(&self) -> u32 { let mut total = 0; - for device in self.power_devices.iter_only::>() { + for device in self.power_devices.iter_only::>() { if let Some(capability) = device.provider_capability().await { if device.is_provider().await { total += capability.capability.max_power_mw(); } } } + total } @@ -164,21 +170,9 @@ impl Context { error!("Non-device located in charger list"); } } - Err(Error::InvalidDevice) } - /// Convenience function to send a request to the power policy service - pub(super) async fn send_request(&self, from: DeviceId, request: RequestData) -> Result { - self.policy_request - .send(Request { - id: from, - data: request, - }) - .await; - self.policy_response.receive().await - } - /// Initialize chargers in hardware pub async fn init_chargers(&self) -> ChargerResponse { for charger in &self.charger_devices { @@ -188,6 +182,7 @@ impl Context { .inspect_err(|e| error!("Charger {:?} failed InitRequest: {:?}", data.id(), e))?; } } + Ok(Ack) } @@ -221,16 +216,6 @@ impl Context { Ok(()) } - /// Wait for a power policy request - pub async fn wait_request(&self) -> Request { - self.policy_request.receive().await - } - - /// Send a response to a power policy request - pub async fn send_response(&self, response: Result) { - self.policy_response.send(response).await - } - /// Provides access to the device list pub fn devices(&self) -> &intrusive_list::IntrusiveList { &self.power_devices @@ -241,26 +226,37 @@ impl Context { &self.charger_devices } - /// Try to provide access to the actions available to the policy for the given state and device - pub async fn try_policy_action( - &self, - id: DeviceId, - ) -> Result, Error> { - self.get_device(id)?.try_policy_action().await - } - - /// Provide access to current policy actions - pub async fn policy_action( - &self, - id: DeviceId, - ) -> Result, Error> { - Ok(self.get_device(id)?.policy_action().await) - } - /// Broadcast a power policy message to all subscribers pub async fn broadcast_message(&self, message: CommsMessage) { self.broadcaster.broadcast(message).await; } + + /// Get the next pending device event + pub async fn wait_request(&self) -> Request { + let mut futures = heapless::Vec::<_, 16>::new(); + for device in self.devices().iter_only::>() { + // TODO: Validate Vec size at compile time + if futures + .push(async { device.receiver.lock().await.wait_next().await }) + .is_err() + { + error!("Futures vec overflow"); + } + } + + let (event, index) = select_slice(pin!(&mut futures)).await; + // Panic safety: The index is guaranteed to be within bounds since it comes from the select_slice result + #[allow(clippy::unwrap_used)] + let device = self + .devices() + .iter_only::>() + .nth(index) + .unwrap(); + Request { + id: device.id(), + data: event, + } + } } /// Init power policy service diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 6bfd10296..c8aa49b95 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -133,7 +133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" dependencies = [ "memchr", - "winnow 0.7.13", + "winnow 0.7.14", ] [[package]] @@ -202,14 +202,6 @@ dependencies = [ "unty", ] -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238#2f79d238149049d458199a9a9129b54be7893aee" -dependencies = [ - "num-traits", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -307,9 +299,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.46" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "shlex", @@ -563,7 +555,7 @@ dependencies = [ [[package]] name = "embedded-cfu-protocol" version = "0.2.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" +source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" dependencies = [ "embedded-io-async", "log", @@ -633,6 +625,7 @@ dependencies = [ "cortex-m-rt", "critical-section", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async", @@ -653,7 +646,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#7b618f5689cef191171d81d33a2fa6b5af46d33f" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce99a6d91032d7c9792fd87d4b4f49b6f" dependencies = [ "aquamarine", "bincode", @@ -712,9 +705,9 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#8cdd61095471903b1b438dbb0eee142676cc3d74" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#9805f13c044b0e314d415410c57a8a59a40eabeb" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities?rev=2f79d238)", + "bit-register", "bitflags", "num-traits", "num_enum", @@ -724,9 +717,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "funty" @@ -748,16 +741,17 @@ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "generator" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" dependencies = [ "cc", "cfg-if", "libc", "log", "rustversion", - "windows", + "windows-link", + "windows-result", ] [[package]] @@ -881,15 +875,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -900,9 +894,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", @@ -928,9 +922,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" @@ -955,9 +949,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "loom" @@ -1006,9 +1000,9 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#4456c65131366acd5e605c7d88a707881fa9e9f0" +source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ - "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", + "bit-register", "espi-device", "num_enum", "smbus-pec", @@ -1214,9 +1208,9 @@ dependencies = [ [[package]] name = "pico-de-gallo-hal" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc711fd7d8c333ac344e434a61a90a9e9076cc7c9fdb9d7d790a4425a9187c0a" +checksum = "bc9fc0623409958ece1636dec2e6f60b3c4843461de8a7bae08ac70b118d21f8" dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", @@ -1226,9 +1220,9 @@ dependencies = [ [[package]] name = "pico-de-gallo-internal" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5f5d62ccfc4edc551e404b9c7eb3a6cbeeca1aadb893983667b549ad76a7a0" +checksum = "de34f7394e7208ca19cc20291cc9e25843190be1a4a8b3f1127bb29eacef02c5" dependencies = [ "postcard-rpc", "postcard-schema", @@ -1237,9 +1231,9 @@ dependencies = [ [[package]] name = "pico-de-gallo-lib" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e874d5446bd937d6f45cd34711f8e179c122c7eb1e5768e09d2575e6cac09da0" +checksum = "f9b50787d5272f2d58658deae15f13f362486e4eeeac4eaa9348d88f1487a27c" dependencies = [ "embedded-hal 1.0.0", "nusb", @@ -1276,9 +1270,9 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -1366,18 +1360,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1460,12 +1454,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "scoped-tls" version = "1.0.1" @@ -1531,15 +1519,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1632,9 +1620,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1649,18 +1637,18 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1678,9 +1666,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "pin-project-lite", @@ -1700,9 +1688,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1711,9 +1699,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1722,9 +1710,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1743,9 +1731,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -1839,112 +1827,19 @@ dependencies = [ "vcell", ] -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - [[package]] name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -1971,7 +1866,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2005,15 +1900,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2115,9 +2001,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -2144,20 +2030,26 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 59a783c81..36196f2b8 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -774,6 +774,7 @@ dependencies = [ "critical-section", "defmt 0.3.100", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async 0.3.4", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index d01c0dd68..12c8dc171 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -732,6 +732,7 @@ dependencies = [ "critical-section", "defmt 0.3.100", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index a287cab7e..637021f32 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -9,22 +9,25 @@ use embassy_imxrt::gpio::{Input, Inverter, Pull}; use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; -use embassy_time::{self as _, Delay}; +use embassy_time::{self as _, Delay, Timer}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; -use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; +use embedded_services::power::policy::{DeviceId as PowerId, policy}; use embedded_services::type_c::{Cached, ControllerId}; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -34,7 +37,6 @@ const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); const PORT0_PWR_ID: PowerId = PowerId(0); const PORT1_PWR_ID: PowerId = PowerId(1); -const POLICY_CHANNEL_SIZE: usize = 1; bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; @@ -52,7 +54,14 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator, POLICY_CHANNEL_SIZE>; +type Wrapper<'a> = ControllerWrapper< + 'a, + GlobalRawMutex, + Tps6699xMutex<'a>, + DynamicSender<'a, policy::RequestData>, + DynamicReceiver<'a, policy::RequestData>, + Validator, +>; type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; @@ -71,48 +80,30 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st } #[embassy_executor::task] -async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { - power_policy_service::task::task( - policy, - None::<[&rt685s_evk_example::DummyPowerDevice; 0]>, - None::<[&rt685s_evk_example::DummyCharger; 0]>, - ) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task( + service: &'static PowerPolicy< + 'static, + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, +) { + Timer::after_millis(100).await; // Give some time for other tasks to start + power_policy_service::task::task(service) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] -async fn service_task( - controller_context: &'static embedded_services::type_c::controller::Context, - controllers: &'static IntrusiveList, +async fn type_c_service_task( + service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static embedded_services::power::policy::policy::Context, + power_policy_context: &'static policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - let service = Service::create( - type_c_service::service::config::Config::default(), - controller_context, - controllers, - power_policy_publisher, - power_policy_subscriber, - ); - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; } @@ -123,19 +114,6 @@ async fn main(spawner: Spawner) { info!("Embedded service init"); embedded_services::init().await; - static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); - let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( - power_policy_service::Config::default(), - )); - - info!("Spawining power policy task"); - spawner.must_spawn(power_policy_service_task(power_service)); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - let int_in = Input::new(p.PIO1_7, Pull::Up, Inverter::Disabled); static BUS: StaticCell>> = StaticCell::new(); let bus = BUS.init(Mutex::new( @@ -169,20 +147,71 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static STORAGE: StaticCell> = StaticCell::new(); + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell< + policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + + static POWER_SERVICE: StaticCell< + power_policy_service::PowerPolicy< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + power_service_context, + power_policy_service::config::Config::default(), + )); + + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, 0, // CFU component ID - [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], - &power_service.context, + [PORT0_ID, PORT1_ID], )); - static REFERENCED: StaticCell> = - StaticCell::new(); - let referenced = REFERENCED.init( + static INTERMEDIATE: StaticCell> = StaticCell::new(); + let intermediate = INTERMEDIATE.init( storage - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED: StaticCell< + ReferencedStorage< + TPS66994_NUM_PORTS, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced([ + (PORT0_PWR_ID, policy_sender0, policy_receiver0), + (PORT1_PWR_ID, policy_sender1, policy_receiver1), + ]) .expect("Failed to create referenced storage"), ); @@ -194,14 +223,35 @@ async fn main(spawner: Spawner) { let wrapper = WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); - info!("Spawining type-c service task"); - spawner.must_spawn(service_task( + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Service::create( + Default::default(), controller_context, - controllers, - [wrapper], - &power_service.context, + controller_list, + power_policy_publisher, + power_policy_subscriber, )); + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + info!("Spawining type-c service task"); + spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], power_service_context, cfu_client)); + + info!("Spawining power policy task"); + spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(pd_controller_task(wrapper)); // Sync our internal state with the hardware diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index f91cd32e9..8a4d143d0 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -10,6 +10,7 @@ use embassy_imxrt::gpio::{Input, Inverter, Pull}; use embassy_imxrt::i2c::Async; use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; @@ -17,18 +18,20 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::power::policy::{CommsMessage, DeviceId as PowerId}; +use embedded_services::power::policy::DeviceId as PowerId; +use embedded_services::power::policy::policy; use embedded_services::type_c::ControllerId; -use embedded_services::type_c::controller::Context; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -48,7 +51,14 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper<'a, GlobalRawMutex, Tps6699xMutex<'a>, Validator, POLICY_CHANNEL_SIZE>; +type Wrapper<'a> = ControllerWrapper< + 'a, + GlobalRawMutex, + Tps6699xMutex<'a>, + DynamicSender<'a, policy::RequestData>, + DynamicReceiver<'a, policy::RequestData>, + Validator, +>; type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; @@ -60,8 +70,6 @@ const PORT1_ID: GlobalPortId = GlobalPortId(1); const PORT0_PWR_ID: PowerId = PowerId(0); const PORT1_PWR_ID: PowerId = PowerId(1); -const POLICY_CHANNEL_SIZE: usize = 1; - #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { loop { @@ -157,50 +165,31 @@ async fn fw_update_task() { } #[embassy_executor::task] -async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { - power_policy_service::task::task( - policy, - None::<[&rt685s_evk_example::DummyPowerDevice; 0]>, - None::<[&rt685s_evk_example::DummyCharger; 0]>, - ) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task( + service: &'static PowerPolicy< + 'static, + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, +) { + Timer::after_millis(100).await; // Give some time for other tasks to start + power_policy_service::task::task(service) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] -async fn service_task( - controller_context: &'static Context, - controllers: &'static IntrusiveList, +async fn type_c_service_task( + service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static embedded_services::power::policy::policy::Context, -) -> ! { + power_policy_context: &'static policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + cfu_client: &'static CfuClient, +) { info!("Starting type-c task"); - - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - let service = Service::create( - type_c_service::service::config::Config::default(), - controller_context, - controllers, - power_policy_publisher, - power_policy_subscriber, - ); - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; - unreachable!() } #[embassy_executor::main] @@ -210,20 +199,6 @@ async fn main(spawner: Spawner) { info!("Embedded service init"); embedded_services::init().await; - info!("Spawining power policy task"); - - static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); - let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( - power_policy_service::Config::default(), - )); - - spawner.must_spawn(power_policy_service_task(power_service)); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controllers = CONTROLLER_LIST.init(IntrusiveList::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - let int_in = Input::new(p.PIO1_7, Pull::Up, Inverter::Disabled); static BUS: StaticCell>> = StaticCell::new(); let bus = BUS.init(Mutex::new( @@ -251,25 +226,77 @@ async fn main(spawner: Spawner) { mask.set_intel_vid_status_updated(true); mask.set_usb_status_updated(true); mask.set_power_path_switch_changed(true); + mask.set_sink_ready(true); *mask }) .await .unwrap(); - static STORAGE: StaticCell> = StaticCell::new(); + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell< + policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + + static POWER_SERVICE: StaticCell< + power_policy_service::PowerPolicy< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + power_service_context, + power_policy_service::config::Config::default(), + )); + + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, CONTROLLER0_CFU_ID, - [(PORT0_ID, PORT0_PWR_ID), (PORT1_ID, PORT1_PWR_ID)], - &power_service.context, + [PORT0_ID, PORT1_ID], )); - static REFERENCED: StaticCell> = - StaticCell::new(); - let referenced = REFERENCED.init( + static INTERMEDIATE: StaticCell> = StaticCell::new(); + let intermediate = INTERMEDIATE.init( storage - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED: StaticCell< + ReferencedStorage< + TPS66994_NUM_PORTS, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced([ + (PORT0_PWR_ID, policy_sender0, policy_receiver0), + (PORT1_PWR_ID, policy_sender1, policy_receiver1), + ]) .expect("Failed to create referenced storage"), ); @@ -281,14 +308,40 @@ async fn main(spawner: Spawner) { let wrapper = WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); - info!("Spawning type-c service task"); - spawner.must_spawn(service_task( + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Service::create( + Default::default(), controller_context, - controllers, + controller_list, + power_policy_publisher, + power_policy_subscriber, + )); + + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + info!("Spawining type-c service task"); + spawner.must_spawn(type_c_service_task( + type_c_service, [wrapper], - &power_service.context, + power_service_context, + cfu_client, )); + info!("Spawining power policy task"); + spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(pd_controller_task(wrapper)); spawner.must_spawn(fw_update_task()); diff --git a/examples/rt685s-evk/src/lib.rs b/examples/rt685s-evk/src/lib.rs index 82fbd0ff3..b40be19ff 100644 --- a/examples/rt685s-evk/src/lib.rs +++ b/examples/rt685s-evk/src/lib.rs @@ -18,21 +18,3 @@ static BOOT_IMAGE_VERSION: u32 = 0x01000000; #[unsafe(link_section = ".keystore")] #[used] static KEYSTORE: [u8; 2048] = [0; 2048]; - -pub struct DummyCharger(embedded_services::power::policy::charger::Device); -impl embedded_services::power::policy::charger::ChargerContainer for DummyCharger { - fn get_charger(&self) -> &embedded_services::power::policy::charger::Device { - &self.0 - } -} - -pub struct DummyPowerDevice( - embedded_services::power::policy::device::Device, -); -impl embedded_services::power::policy::device::DeviceContainer - for DummyPowerDevice -{ - fn get_power_policy_device(&self) -> &embedded_services::power::policy::device::Device { - &self.0 - } -} diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 9e83d88a6..63e2911ec 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -23,6 +23,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -86,17 +136,6 @@ dependencies = [ "winnow 0.7.13", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -287,6 +326,12 @@ dependencies = [ "log", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "const-init" version = "1.0.0" @@ -748,6 +793,7 @@ dependencies = [ "cortex-m-rt", "critical-section", "document-features", + "embassy-futures", "embassy-sync", "embassy-time", "embedded-batteries-async", @@ -795,16 +841,26 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.9.3" +name = "env_filter" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ - "atty", - "humantime", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] @@ -907,21 +963,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "humantime" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" - [[package]] name = "ident_case" version = "1.0.1" @@ -947,6 +988,12 @@ dependencies = [ "quote", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -971,6 +1018,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "kdl" version = "6.3.4" @@ -1253,6 +1324,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "paste" version = "1.0.15" @@ -1297,6 +1374,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "power-policy-service" version = "0.1.0" @@ -1445,18 +1531,28 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1605,15 +1701,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thermal-service" version = "0.1.0" @@ -1822,6 +1909,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.17.0" @@ -1867,37 +1960,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" -dependencies = [ - "windows-sys 0.60.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.61.3" diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 5f5375356..ef102cee3 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -41,7 +41,7 @@ embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } thermal-service-messages = { path = "../../thermal-service-messages" } -env_logger = "0.9.0" +env_logger = "0.11.8" log = "0.4.14" heapless = "0.8.0" static_cell = "2" diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 99d1c2053..50d0030a1 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -1,11 +1,23 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, once_lock::OnceLock, pubsub::PubSubChannel}; +use embassy_sync::{ + blocking_mutex::raw::NoopRawMutex, + channel::{self, Channel}, + mutex::Mutex, + pubsub::PubSubChannel, +}; use embassy_time::{self as _, Timer}; use embedded_services::{ + GlobalRawMutex, broadcaster::immediate as broadcaster, - power::policy::{self, ConsumerPowerCapability, PowerCapability, device, flags, policy::Context}, + power::{ + self, + policy::{ + self, ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability, device::DeviceTrait, flags, + }, + }, }; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; const LOW_POWER: PowerCapability = PowerCapability { @@ -18,204 +30,254 @@ const HIGH_POWER: PowerCapability = PowerCapability { current_ma: 3000, }; -const POWER_POLICY_CHANNEL_SIZE: usize = 1; -const NUM_POWER_DEVICES: usize = 2; +const DEVICE0_ID: policy::DeviceId = policy::DeviceId(0); +const DEVICE1_ID: policy::DeviceId = policy::DeviceId(1); + +const PER_CALL_DELAY_MS: u64 = 1000; -struct ExampleDevice { - device: policy::device::Device, +struct ExampleDevice<'a> { + sender: channel::DynamicSender<'a, policy::policy::RequestData>, } -impl ExampleDevice { - fn new(id: policy::DeviceId, context_ref: &'static Context) -> Self { - Self { - device: policy::device::Device::new(id, context_ref), - } +impl<'a> ExampleDevice<'a> { + fn new(sender: channel::DynamicSender<'a, policy::policy::RequestData>) -> Self { + Self { sender } } - async fn process_request(&self) -> Result<(), policy::Error> { - let request = self.device.receive().await; - match request.command { - device::CommandData::ConnectAsConsumer(capability) => { - info!( - "Device {} received connect consumer at {:#?}", - self.device.id().0, - capability - ); - } - device::CommandData::ConnectAsProvider(capability) => { - info!( - "Device {} received connect provider at {:#?}", - self.device.id().0, - capability - ); - } - device::CommandData::Disconnect => { - info!("Device {} received disconnect", self.device.id().0); - } - } + pub async fn simulate_attach(&mut self) { + self.sender.send(policy::policy::RequestData::Attached).await; + } - request.respond(Ok(policy::device::ResponseData::Complete)); - Ok(()) + pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option) { + self.sender + .send(policy::policy::RequestData::UpdatedConsumerCapability(capability)) + .await; } -} -impl policy::device::DeviceContainer for ExampleDevice { - fn get_power_policy_device(&self) -> &policy::device::Device { - &self.device + pub async fn simulate_detach(&mut self) { + self.sender.send(policy::policy::RequestData::Detached).await; } -} -#[embassy_executor::task] -async fn device_task0(device: &'static ExampleDevice) { - loop { - if let Err(e) = device.process_request().await { - error!("Error processing request: {e:?}"); - } + pub async fn simulate_update_requested_provider_power_capability( + &mut self, + capability: Option, + ) { + self.sender + .send(policy::policy::RequestData::RequestedProviderCapability(capability)) + .await } } -#[embassy_executor::task] -async fn device_task1(device: &'static ExampleDevice) { - loop { - if let Err(e) = device.process_request().await { - error!("Error processing request: {e:?}"); - } +impl DeviceTrait for ExampleDevice<'_> { + async fn disconnect(&mut self) -> Result<(), Error> { + debug!("ExampleDevice disconnect"); + Ok(()) + } + + async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + debug!("ExampleDevice connect_provider with {capability:?}"); + Ok(()) + } + + async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { + debug!("ExampleDevice connect_consumer with {capability:?}"); + Ok(()) } } #[embassy_executor::task] -async fn run(spawner: Spawner, service: &'static power_policy_service::PowerPolicy) { +async fn run(spawner: Spawner) { embedded_services::init().await; - spawner.must_spawn(receiver_task(service)); - info!("Creating device 0"); - static DEVICE0: OnceLock = OnceLock::new(); - let device0_mock = DEVICE0.get_or_init(|| ExampleDevice::new(policy::DeviceId(0), &service.context)); - spawner.must_spawn(device_task0(device0_mock)); - let device0 = device0_mock.device.try_device_action().await.unwrap(); + static DEVICE0_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); + static DEVICE0: StaticCell> = StaticCell::new(); + let device0 = DEVICE0.init(Mutex::new(ExampleDevice::new(device0_event_channel.dyn_sender()))); + static DEVICE0_REGISTRATION: StaticCell< + policy::device::Device< + 'static, + Mutex, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let device0_registration = DEVICE0_REGISTRATION.init(policy::device::Device::new( + DEVICE0_ID, + device0, + device0_event_channel.dyn_receiver(), + )); info!("Creating device 1"); - static DEVICE1: OnceLock = OnceLock::new(); - let device1_mock = DEVICE1.get_or_init(|| ExampleDevice::new(policy::DeviceId(1), &service.context)); - spawner.must_spawn(device_task1(device1_mock)); - let device1 = device1_mock.device.try_device_action().await.unwrap(); + static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); + static DEVICE1: StaticCell> = StaticCell::new(); + let device1 = DEVICE1.init(Mutex::new(ExampleDevice::new(device1_event_channel.dyn_sender()))); + static DEVICE1_REGISTRATION: StaticCell< + policy::device::Device< + 'static, + Mutex, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let device1_registration = DEVICE1_REGISTRATION.init(policy::device::Device::new( + DEVICE1_ID, + device1, + device1_event_channel.dyn_receiver(), + )); - spawner.must_spawn(power_policy_service_task(service, [device0_mock, device1_mock])); + static SERVICE_CONTEXT: StaticCell< + power::policy::policy::Context< + Mutex>, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let service_context = SERVICE_CONTEXT.init(power::policy::policy::Context::new()); + + service_context.register_device(device0_registration).unwrap(); + service_context.register_device(device1_registration).unwrap(); + + static SERVICE: StaticCell< + power_policy_service::PowerPolicy< + Mutex>, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, + > = StaticCell::new(); + let service = SERVICE.init(power_policy_service::PowerPolicy::new( + service_context, + power_policy_service::config::Config::default(), + )); + + spawner.must_spawn(power_policy_task(service)); + spawner.must_spawn(receiver_task(service)); // Plug in device 0, should become current consumer info!("Connecting device 0"); - let device0 = device0.attach().await.unwrap(); - device0 - .notify_consumer_power_capability(Some(ConsumerPowerCapability { + { + let mut dev0 = device0.lock().await; + dev0.simulate_attach().await; + dev0.simulate_update_consumer_power_capability(Some(ConsumerPowerCapability { capability: LOW_POWER, flags: flags::Consumer::none().with_unconstrained_power(), })) - .await - .unwrap(); + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Plug in device 1, should become current consumer info!("Connecting device 1"); - let device1 = device1.attach().await.unwrap(); - device1 - .notify_consumer_power_capability(Some(HIGH_POWER.into())) - .await - .unwrap(); + { + let mut dev1 = device1.lock().await; + dev1.simulate_attach().await; + dev1.simulate_update_consumer_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Unplug device 0, device 1 should remain current consumer - info!("Unpluging device 0"); - let device0 = device0.detach().await.unwrap(); + info!("Unplugging device 0"); + { + let mut dev0 = device0.lock().await; + dev0.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Plug in device 0, device 1 should remain current consumer info!("Connecting device 0"); - let device0 = device0.attach().await.unwrap(); - device0 - .notify_consumer_power_capability(Some(LOW_POWER.into())) - .await - .unwrap(); + { + let mut dev0 = device0.lock().await; + dev0.simulate_attach().await; + dev0.simulate_update_consumer_power_capability(Some(LOW_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Unplug device 1, device 0 should become current consumer info!("Unplugging device 1"); - let device1 = device1.detach().await.unwrap(); + { + let mut dev1 = device1.lock().await; + dev1.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Replug device 1, device 1 becomes current consumer info!("Connecting device 1"); - let device1 = device1.attach().await.unwrap(); - device1 - .notify_consumer_power_capability(Some(HIGH_POWER.into())) - .await - .unwrap(); + { + let mut dev1 = device1.lock().await; + dev1.simulate_attach().await; + dev1.simulate_update_consumer_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; - // Disconnect consumer device 0, device 1 should remain current consumer + // Detach consumer device 0, device 1 should remain current consumer // Device 0 should not be able to consume after device 1 is unplugged - info!("Disconnecting device 0"); - device0.notify_consumer_power_capability(None).await.unwrap(); - let device1 = device1.detach().await.unwrap(); - - // Switch to provider on device0 - info!("Device 0 requesting provider"); - device0 - .request_provider_power_capability(LOW_POWER.into()) - .await - .unwrap(); - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - service.context.compute_total_provider_power_mw().await - ); + info!("Detach device 0"); + { + let mut dev0 = device0.lock().await; + dev0.simulate_update_consumer_power_capability(None).await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + { + let mut dev1 = device1.lock().await; + dev1.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Switch device 0 to provider + info!("Device 0 switch to provider"); + { + let mut dev0 = device0.lock().await; + dev0.simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Attach device 1 and request provider info!("Device 1 attach and requesting provider"); - let device1 = device1.attach().await.unwrap(); - device1 - .request_provider_power_capability(LOW_POWER.into()) - .await - .unwrap(); - // Wait for the provider to be connected - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - service.context.compute_total_provider_power_mw().await - ); - - // Provider upgrade should fail because device 0 is already connected + { + let mut dev1 = device1.lock().await; + dev1.simulate_attach().await; + dev1.simulate_update_requested_provider_power_capability(Some(LOW_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Provider upgrade should fail because device 0 is already connected at high power info!("Device 1 attempting provider upgrade"); - device1 - .request_provider_power_capability(HIGH_POWER.into()) - .await - .unwrap(); - // Wait for the upgrade flow to complete - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - service.context.compute_total_provider_power_mw().await - ); + { + let mut dev1 = device1.lock().await; + dev1.simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Disconnect device 0 info!("Device 0 disconnecting"); - device0.detach().await.unwrap(); - // Wait for the detach flow to complete - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - service.context.compute_total_provider_power_mw().await - ); + { + let mut dev0 = device0.lock().await; + dev0.simulate_detach().await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; // Provider upgrade should succeed now info!("Device 1 attempting provider upgrade"); - device1 - .request_provider_power_capability(HIGH_POWER.into()) - .await - .unwrap(); - // Wait for the upgrade flow to complete - Timer::after_millis(250).await; - info!( - "Total provider power: {} mW", - service.context.compute_total_provider_power_mw().await - ); + { + let mut dev1 = device1.lock().await; + dev1.simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + } + Timer::after_millis(PER_CALL_DELAY_MS).await; } #[embassy_executor::task] -async fn receiver_task(service: &'static power_policy_service::PowerPolicy) { +async fn receiver_task( + service: &'static power_policy_service::PowerPolicy< + 'static, + Mutex>, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, +) { static CHANNEL: StaticCell> = StaticCell::new(); let channel = CHANNEL.init(PubSubChannel::new()); @@ -240,13 +302,14 @@ async fn receiver_task(service: &'static power_policy_service::PowerPolicy, - devices: [&'static ExampleDevice; NUM_POWER_DEVICES], +async fn power_policy_task( + power_policy: &'static PowerPolicy< + 'static, + Mutex>, + channel::DynamicReceiver<'static, policy::policy::RequestData>, + >, ) { - power_policy_service::task::task(service, Some(devices), None::<[&std_examples::type_c::DummyCharger; 0]>) - .await - .expect("Failed to start power policy service task"); + power_policy_service::task::task(power_policy).await.unwrap(); } fn main() { @@ -255,12 +318,7 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); - static SERVICE: StaticCell> = StaticCell::new(); - let service = SERVICE.init(power_policy_service::PowerPolicy::new( - power_policy_service::config::Config::default(), - )); - executor.run(|spawner| { - spawner.must_spawn(run(spawner, service)); + spawner.must_spawn(run(spawner)); }); } diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 85ec9adc2..c1d32842d 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,9 +1,8 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; -use embedded_services::power::policy::*; +use embedded_services::IntrusiveList; use embedded_services::type_c::{Cached, ControllerId, controller}; -use embedded_services::{IntrusiveList, power}; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; @@ -12,8 +11,6 @@ use static_cell::StaticCell; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); -const POWER_POLICY_CHANNEL_SIZE: usize = 1; mod test_controller { use embedded_services::type_c::controller::{ControllerStatus, PortStatus}; @@ -23,7 +20,6 @@ mod test_controller { pub struct Controller<'a> { pub controller: controller::Device<'a>, - pub power_policy: power::policy::device::Device, } impl controller::DeviceContainer for Controller<'_> { @@ -32,22 +28,10 @@ mod test_controller { } } - impl power::policy::device::DeviceContainer for Controller<'_> { - fn get_power_policy_device(&self) -> &power::policy::device::Device { - &self.power_policy - } - } - impl<'a> Controller<'a> { - pub fn new( - id: ControllerId, - power_id: power::policy::DeviceId, - ports: &'a [GlobalPortId], - power_context: &'static crate::power::policy::policy::Context, - ) -> Self { + pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { Self { controller: controller::Device::new(id, ports), - power_policy: power::policy::device::Device::new(power_id, power_context), } } @@ -131,16 +115,12 @@ mod test_controller { } #[embassy_executor::task] -async fn controller_task( - controller_list: &'static IntrusiveList, - power_context: &'static policy::Context, -) { +async fn controller_task(controller_list: &'static IntrusiveList) { static CONTROLLER: OnceLock = OnceLock::new(); static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; - let controller = - CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, POWER0_ID, &PORTS, power_context)); + let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, &PORTS)); controller::register_controller(controller_list, controller).unwrap(); loop { @@ -152,14 +132,11 @@ async fn controller_task( async fn task(spawner: Spawner) { embedded_services::init().await; - static POWER_CONTEXT: StaticCell> = StaticCell::new(); static CONTROLLER_LIST: StaticCell = StaticCell::new(); - - let power_context = POWER_CONTEXT.init(policy::Context::new()); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); info!("Starting controller task"); - spawner.must_spawn(controller_task(controller_list, power_context)); + spawner.must_spawn(controller_task(controller_list)); // Wait for controller to be registered Timer::after_secs(1).await; diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 943d7b3f4..7ea0f5eac 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -1,11 +1,11 @@ //! Low-level example of external messaging with a simple type-C service -use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; -use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::power::policy::*; +use embedded_services::power::policy::policy; +use embedded_services::type_c::controller; use embedded_services::{ GlobalRawMutex, IntrusiveList, power, type_c::{Cached, ControllerId, controller::Context}, @@ -21,7 +21,6 @@ const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); -const POLICY_CHANNEL_SIZE: usize = 1; #[embassy_executor::task] async fn controller_task(wrapper: &'static Wrapper<'static>) { @@ -102,14 +101,9 @@ async fn service_task( controller_context: &'static Context, controllers: &'static IntrusiveList, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_context: &'static policy::Context, ) { info!("Starting type-c task"); - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell> = StaticCell::new(); @@ -119,41 +113,63 @@ async fn service_task( // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - let service = Service::create( + for controller_wrapper in wrappers { + controller::register_controller(controllers, controller_wrapper.registration.pd_controller).unwrap(); + } + + static SERVICE: StaticCell = StaticCell::new(); + let service = SERVICE.init(Service::create( Config::default(), controller_context, controllers, power_policy_publisher, power_policy_subscriber, - ); - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); + )); - type_c_service::task::task(service, wrappers, power_context, cfu_client).await; + loop { + if let Err(e) = service.process_next_event().await { + error!("Type-C service processing error: {e:#?}"); + } + } } -fn create_wrapper( - controller_context: &'static Context, - power_context: &'static policy::Context, -) -> &'static mut Wrapper<'static> { +fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper<'static> { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); - static STORAGE: StaticCell> = StaticCell::new(); + static STORAGE: StaticCell> = StaticCell::new(); let backing_storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, 0, // CFU component ID (unused) - [(PORT0_ID, POWER0_ID)], - power_context, + [PORT0_ID], )); + + static INTERMEDIATE: StaticCell> = + StaticCell::new(); + let intermediate = INTERMEDIATE.init( + backing_storage + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + let policy_channel = POLICY_CHANNEL.init(Channel::new()); + + let policy_sender = policy_channel.dyn_sender(); + let policy_receiver = policy_channel.dyn_receiver(); + static REFERENCED: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage<1, GlobalRawMutex, POLICY_CHANNEL_SIZE>, + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, > = StaticCell::new(); let referenced = REFERENCED.init( - backing_storage - .create_referenced() + intermediate + .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) .expect("Failed to create referenced storage"), ); @@ -179,15 +195,13 @@ fn main() { let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); static CONTEXT: StaticCell = StaticCell::new(); let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - static POWER_CONTEXT: StaticCell> = StaticCell::new(); - let power_context = POWER_CONTEXT.init(policy::Context::new()); - let wrapper = create_wrapper(context, power_context); + let wrapper = create_wrapper(context); static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(service_task(context, controller_list, [wrapper], power_context)); + spawner.must_spawn(service_task(context, controller_list, [wrapper])); spawner.must_spawn(task(spawner, context)); spawner.must_spawn(controller_task(wrapper)); }); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 1307c283c..e6647778a 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,5 +1,6 @@ use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; @@ -8,18 +9,20 @@ use embedded_services::power::policy::policy; use embedded_services::power::{self}; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; -use embedded_services::{GlobalRawMutex, IntrusiveList, comms}; +use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::message::*; +use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -27,42 +30,6 @@ const PORT0_ID: GlobalPortId = GlobalPortId(0); const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); const DELAY_MS: u64 = 1000; -const POLICY_CHANNEL_SIZE: usize = 1; - -mod debug { - use embedded_services::{ - comms::{self, Endpoint, EndpointID, Internal}, - info, - type_c::comms::DebugAccessoryMessage, - }; - - pub struct Listener { - pub tp: Endpoint, - } - - impl Listener { - pub fn new() -> Self { - Self { - tp: Endpoint::uninit(EndpointID::Internal(Internal::Usbc)), - } - } - } - - impl comms::MailboxDelegate for Listener { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(message) = message.data.get::() { - if message.connected { - info!("Port{}: Debug accessory connected", message.port.0); - } else { - info!("Port{}: Debug accessory disconnected", message.port.0); - } - } - - Ok(()) - } - } -} - #[embassy_executor::task] async fn controller_task( wrapper: &'static Wrapper<'static>, @@ -93,24 +60,70 @@ async fn controller_task( } #[embassy_executor::task] -async fn task( - spawner: Spawner, - wrapper: &'static Wrapper<'static>, - controller: &'static Mutex>, - state: &'static mock_controller::ControllerState, -) { +async fn task(spawner: Spawner) { embedded_services::init().await; - // Register debug accessory listener - static LISTENER: OnceLock = OnceLock::new(); - let listener = LISTENER.get_or_init(debug::Listener::new); - comms::register_endpoint(listener, &listener.tp).await.unwrap(); + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell< + power::policy::policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(power::policy::policy::Context::new()); + + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + let (wrapper, controller, state) = create_wrapper(controller_context); + + static POWER_SERVICE: StaticCell< + power_policy_service::PowerPolicy< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + power_service_context, + power_policy_service::config::Config::default(), + )); + + // Create type-c service + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell> = + StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + static CONTROLLER_LIST: StaticCell = StaticCell::new(); + let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Service::create( + Config::default(), + controller_context, + controller_list, + power_policy_publisher, + power_policy_subscriber, + )); + + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; - info!("Starting controller task"); + spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(type_c_service_task( + type_c_service, + [wrapper], + power_service_context, + cfu_client, + )); spawner.must_spawn(controller_task(wrapper, controller)); - // Wait for controller to be registered - Timer::after_secs(1).await; + Timer::after_millis(1000).await; info!("Simulating connection"); state.connect_sink(Current::UsbDefault.into(), false).await; Timer::after_millis(DELAY_MS).await; @@ -133,75 +146,75 @@ async fn task( } #[embassy_executor::task] -async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { - power_policy_service::task::task( - policy, - None::<[&std_examples::type_c::DummyPowerDevice; 0]>, - None::<[&std_examples::type_c::DummyCharger; 0]>, - ) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task( + service: &'static PowerPolicy< + 'static, + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, +) { + power_policy_service::task::task(service) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] -async fn service_task( - controller_context: &'static Context, - controllers: &'static IntrusiveList, +async fn type_c_service_task( + service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static policy::Context, + power_policy_context: &'static policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell> = - StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - let service = Service::create( - Config::default(), - controller_context, - controllers, - power_policy_publisher, - power_policy_subscriber, - ); - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; } fn create_wrapper( context: &'static Context, - power_policy_context: &'static policy::Context, ) -> ( - &'static mut Wrapper<'static>, + &'static Wrapper<'static>, &'static Mutex>, &'static mock_controller::ControllerState, ) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); - static STORAGE: StaticCell> = StaticCell::new(); + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( context, CONTROLLER0_ID, 0, // CFU component ID (unused) - [(PORT0_ID, POWER0_ID)], - power_policy_context, + [PORT0_ID], )); - static REFERENCED: StaticCell> = StaticCell::new(); - let referenced = REFERENCED.init( + + static INTERMEDIATE: StaticCell> = + StaticCell::new(); + let intermediate = INTERMEDIATE.init( storage - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + let policy_channel = POLICY_CHANNEL.init(Channel::new()); + + let policy_sender = policy_channel.dyn_sender(); + let policy_receiver = policy_channel.dyn_receiver(); + + static REFERENCED: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) .expect("Failed to create referenced storage"), ); @@ -230,26 +243,7 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - - static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); - let power_policy_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( - power_policy_service::Config::default(), - )); - - let (wrapper, controller, state) = create_wrapper(controller_context, &power_policy_service.context); - executor.run(|spawner| { - spawner.must_spawn(power_policy_service_task(power_policy_service)); - spawner.must_spawn(service_task( - controller_context, - controller_list, - [wrapper], - &power_policy_service.context, - )); - spawner.must_spawn(task(spawner, wrapper, controller, state)); + spawner.must_spawn(task(spawner)); }); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index eb8f4ffc6..475940609 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,12 +1,14 @@ use crate::mock_controller::Wrapper; use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; -use embedded_services::power::policy::{self, PowerCapability}; +use embedded_services::power::policy::PowerCapability; +use embedded_services::power::policy::policy; use embedded_services::type_c::ControllerId; use embedded_services::type_c::controller::Context; use embedded_services::type_c::external::UcsiResponseResult; @@ -17,24 +19,24 @@ use embedded_usb_pd::ucsi::ppm::get_capability::ResponseData as UcsiCapabilities use embedded_usb_pd::ucsi::ppm::set_notification_enable::NotificationEnable; use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; +use type_c_service::wrapper::backing::Storage; +use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 2; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: policy::DeviceId = policy::DeviceId(0); +const POWER0_ID: embedded_services::power::policy::DeviceId = embedded_services::power::policy::DeviceId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER1_ID: policy::DeviceId = policy::DeviceId(1); +const POWER1_ID: embedded_services::power::policy::DeviceId = embedded_services::power::policy::DeviceId(1); const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; -const POLICY_CHANNEL_SIZE: usize = 1; - #[embassy_executor::task] async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { @@ -175,83 +177,91 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { } #[embassy_executor::task] -async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { - power_policy_service::task::task( - policy, - None::<[&std_examples::type_c::DummyPowerDevice; 0]>, - None::<[&std_examples::type_c::DummyCharger; 0]>, - ) - .await - .expect("Failed to start power policy service task"); +async fn power_policy_service_task( + service: &'static PowerPolicy< + 'static, + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, +) { + power_policy_service::task::task(service) + .await + .expect("Failed to start power policy service task"); } #[embassy_executor::task] -async fn service_task( - config: Config, - controller_context: &'static Context, - controllers: &'static IntrusiveList, +async fn type_c_service_task( + service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static embedded_services::power::policy::policy::Context, -) -> ! { + power_policy_context: &'static policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + cfu_client: &'static CfuClient, +) { info!("Starting type-c task"); - - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - let service = Service::create( - config, - controller_context, - controllers, - power_policy_publisher, - power_policy_subscriber, - ); - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; - unreachable!() } #[embassy_executor::task] -async fn type_c_service_task(spawner: Spawner) { +async fn task(spawner: Spawner) { info!("Starting main task"); embedded_services::init().await; + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell< + policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + + static POWER_SERVICE: StaticCell< + power_policy_service::PowerPolicy< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + power_service_context, + power_policy_service::config::Config::default(), + )); + + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); - static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); - let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( - power_policy_service::Config::default(), - )); + static STORAGE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); - static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new( - context, - CONTROLLER0_ID, - CFU0_ID, - [(PORT0_ID, POWER0_ID)], - &power_service.context, - )); - static REFERENCED0: StaticCell> = StaticCell::new(); - let referenced0 = REFERENCED0.init( + static INTERMEDIATE0: StaticCell> = + StaticCell::new(); + let intermediate0 = INTERMEDIATE0.init( storage0 - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static REFERENCED0: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced0 = REFERENCED0.init( + intermediate0 + .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) .expect("Failed to create referenced storage"), ); @@ -265,18 +275,32 @@ async fn type_c_service_task(spawner: Spawner) { .expect("Failed to create wrapper"), ); - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new( - context, - CONTROLLER1_ID, - CFU1_ID, - [(PORT1_ID, POWER1_ID)], - &power_service.context, - )); - static REFERENCED1: StaticCell> = StaticCell::new(); - let referenced1 = REFERENCED1.init( + static STORAGE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new(controller_context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + static INTERMEDIATE1: StaticCell> = + StaticCell::new(); + let intermediate1 = INTERMEDIATE1.init( storage1 - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED1: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced1 = REFERENCED1.init( + intermediate1 + .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) .expect("Failed to create referenced storage"), ); @@ -290,8 +314,19 @@ async fn type_c_service_task(spawner: Spawner) { .expect("Failed to create wrapper"), ); - spawner.must_spawn(power_policy_service_task(power_service)); - spawner.must_spawn(service_task( + // Create type-c service + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Service::create( Config { ucsi_capabilities: UcsiCapabilities { num_connectors: 2, @@ -316,14 +351,26 @@ async fn type_c_service_task(spawner: Spawner) { ), ..Default::default() }, - context, + controller_context, controller_list, + power_policy_publisher, + power_policy_subscriber, + )); + + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(type_c_service_task( + type_c_service, [wrapper0, wrapper1], - &power_service.context, + power_service_context, + cfu_client, )); spawner.must_spawn(wrapper_task(wrapper0)); spawner.must_spawn(wrapper_task(wrapper1)); - spawner.must_spawn(opm_task(context, [state0, state1])); + spawner.must_spawn(opm_task(controller_context, [state0, state1])); } fn main() { @@ -331,7 +378,8 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { - spawner.must_spawn(type_c_service_task(spawner)); + spawner.must_spawn(task(spawner)); }); } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index bb2039a75..c8ccf0bb6 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,24 +1,28 @@ use crate::mock_controller::Wrapper; use cfu_service::CfuClient; -use embassy_executor::Executor; +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; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; use embedded_services::power::policy::PowerCapability; +use embedded_services::power::policy::policy; use embedded_services::power::{self}; use embedded_services::type_c::ControllerId; -use embedded_services::type_c::controller::Context; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; +use power_policy_service::PowerPolicy; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; -use type_c_service::service::config::Config; -use type_c_service::wrapper::backing::{ReferencedStorage, Storage}; const NUM_PD_CONTROLLERS: usize = 3; +use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::proxy::PowerProxyDevice; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -37,8 +41,6 @@ const CFU2_ID: u8 = 0x02; const DELAY_MS: u64 = 1000; -const POLICY_CHANNEL_SIZE: usize = 1; - #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -49,134 +51,62 @@ async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { } #[embassy_executor::task] -async fn task(state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { +async fn task(spawner: Spawner) { embedded_services::init().await; - const CAPABILITY: PowerCapability = PowerCapability { - voltage_mv: 20000, - current_ma: 5000, - }; - - // Wait for controller to be registered - Timer::after_secs(1).await; - - info!("Connecting port 0, unconstrained"); - state[0].connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 1, constrained"); - state[1].connect_sink(CAPABILITY, false).await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 0"); - state[0].disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 1"); - state[1].disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 0, unconstrained"); - state[0].connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 1, unconstrained"); - state[1].connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Connecting port 2, unconstrained"); - state[2].connect_sink(CAPABILITY, true).await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 0"); - state[0].disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 1"); - state[1].disconnect().await; - Timer::after_millis(DELAY_MS).await; - - info!("Disconnecting port 2"); - state[2].disconnect().await; - Timer::after_millis(DELAY_MS).await; -} - -#[embassy_executor::task] -async fn power_policy_service_task(policy: &'static power_policy_service::PowerPolicy) { - power_policy_service::task::task( - policy, - None::<[&std_examples::type_c::DummyPowerDevice; 0]>, - None::<[&std_examples::type_c::DummyCharger; 0]>, - ) - .await - .expect("Failed to start power policy service task"); -} - -#[embassy_executor::task] -async fn service_task( - controller_context: &'static Context, - controllers: &'static IntrusiveList, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static embedded_services::power::policy::policy::Context, -) -> ! { - info!("Starting type-c task"); - - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell> = - StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - let service = Service::create( - Config::default(), - controller_context, - controllers, - power_policy_publisher, - power_policy_subscriber, - ); - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(service); - - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; - unreachable!() -} - -fn main() { - env_logger::builder().filter_level(log::LevelFilter::Trace).init(); - - static EXECUTOR: StaticCell = StaticCell::new(); - let executor = EXECUTOR.init(Executor::new()); + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell< + policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + + static POWER_SERVICE: StaticCell< + power_policy_service::PowerPolicy< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + > = StaticCell::new(); + let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + power_service_context, + power_policy_service::config::Config::default(), + )); static CONTEXT: StaticCell = StaticCell::new(); let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static POWER_POLICY_SERVICE: StaticCell> = StaticCell::new(); - let power_service = POWER_POLICY_SERVICE.init(power_policy_service::PowerPolicy::new( - power_policy_service::Config::default(), - )); + static STORAGE0: StaticCell> = StaticCell::new(); + let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + static INTERMEDIATE0: StaticCell> = StaticCell::new(); + let intermediate0 = INTERMEDIATE0.init( + storage0 + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - context, - CONTROLLER0_ID, - CFU0_ID, - [(PORT0_ID, POWER0_ID)], - &power_service.context, - )); - static REFERENCED: StaticCell> = StaticCell::new(); - let referenced = REFERENCED.init( - storage - .create_referenced() + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static REFERENCED0: StaticCell< + ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced0 = REFERENCED0.init( + intermediate0 + .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) .expect("Failed to create referenced storage"), ); @@ -189,24 +119,37 @@ fn main() { mock_controller::Wrapper::try_new( controller0, Default::default(), - referenced, + referenced0, crate::mock_controller::Validator, ) .expect("Failed to create wrapper"), ); - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new( - context, - CONTROLLER1_ID, - CFU1_ID, - [(PORT1_ID, POWER1_ID)], - &power_service.context, - )); - static REFERENCED1: StaticCell> = StaticCell::new(); - let referenced1 = REFERENCED1.init( + static STORAGE1: StaticCell> = StaticCell::new(); + let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + static INTERMEDIATE1: StaticCell> = StaticCell::new(); + let intermediate1 = INTERMEDIATE1.init( storage1 - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); + + static REFERENCED1: StaticCell< + ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced1 = REFERENCED1.init( + intermediate1 + .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) .expect("Failed to create referenced storage"), ); @@ -225,18 +168,31 @@ fn main() { .expect("Failed to create wrapper"), ); - static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new( - context, - CONTROLLER2_ID, - CFU2_ID, - [(PORT2_ID, POWER2_ID)], - &power_service.context, - )); - static REFERENCED2: StaticCell> = StaticCell::new(); - let referenced2 = REFERENCED2.init( + static STORAGE2: StaticCell> = StaticCell::new(); + let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); + static INTERMEDIATE2: StaticCell> = StaticCell::new(); + let intermediate2 = INTERMEDIATE2.init( storage2 - .create_referenced() + .try_create_intermediate() + .expect("Failed to create intermediate storage"), + ); + + static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); + let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); + let policy_sender2 = policy_channel2.dyn_sender(); + let policy_receiver2 = policy_channel2.dyn_receiver(); + + static REFERENCED2: StaticCell< + ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, policy::RequestData>, + DynamicReceiver<'_, policy::RequestData>, + >, + > = StaticCell::new(); + let referenced2 = REFERENCED2.init( + intermediate2 + .try_create_referenced([(POWER2_ID, policy_sender2, policy_receiver2)]) .expect("Failed to create referenced storage"), ); @@ -255,18 +211,124 @@ fn main() { .expect("Failed to create wrapper"), ); + // Create type-c service + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Service::create( + Default::default(), + controller_context, + controller_list, + power_policy_publisher, + power_policy_subscriber, + )); + + // Spin up CFU service + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + + spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(type_c_service_task( + type_c_service, + [wrapper0, wrapper1, wrapper2], + power_service_context, + cfu_client, + )); + + spawner.must_spawn(controller_task(wrapper0)); + spawner.must_spawn(controller_task(wrapper1)); + spawner.must_spawn(controller_task(wrapper2)); + + const CAPABILITY: PowerCapability = PowerCapability { + voltage_mv: 20000, + current_ma: 5000, + }; + + // Wait for controller to be registered + Timer::after_secs(1).await; + + info!("Connecting port 0, unconstrained"); + state0.connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 1, constrained"); + state1.connect_sink(CAPABILITY, false).await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 0"); + state0.disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 1"); + state1.disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 0, unconstrained"); + state0.connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 1, unconstrained"); + state1.connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Connecting port 2, unconstrained"); + state2.connect_sink(CAPABILITY, true).await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 0"); + state0.disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 1"); + state1.disconnect().await; + Timer::after_millis(DELAY_MS).await; + + info!("Disconnecting port 2"); + state2.disconnect().await; + Timer::after_millis(DELAY_MS).await; +} + +#[embassy_executor::task] +async fn power_policy_service_task( + service: &'static PowerPolicy< + 'static, + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, +) { + power_policy_service::task::task(service) + .await + .expect("Failed to start power policy service task"); +} + +#[embassy_executor::task] +async fn type_c_service_task( + service: &'static Service<'static>, + wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + power_policy_context: &'static policy::Context< + Mutex>, + DynamicReceiver<'static, policy::RequestData>, + >, + cfu_client: &'static CfuClient, +) { + info!("Starting type-c task"); + type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; +} + +fn main() { + env_logger::builder().filter_level(log::LevelFilter::Trace).init(); + + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(power_policy_service_task(power_service)); - spawner.must_spawn(service_task( - context, - controller_list, - [wrapper0, wrapper1, wrapper2], - &power_service.context, - )); - spawner.must_spawn(task([state0, state1, state2])); - info!("Starting controller tasks"); - spawner.must_spawn(controller_task(wrapper0)); - spawner.must_spawn(controller_task(wrapper1)); - spawner.must_spawn(controller_task(wrapper2)); + spawner.must_spawn(task(spawner)); }); } diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 810362c5b..a3cda41be 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -1,8 +1,8 @@ -use embassy_sync::{mutex::Mutex, signal::Signal}; +use embassy_sync::{channel, mutex::Mutex, signal::Signal}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOfferResponse, HostToken}; use embedded_services::{ GlobalRawMutex, - power::policy::PowerCapability, + power::policy::{PowerCapability, policy}, type_c::{ controller::{ AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, @@ -16,9 +16,6 @@ use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; use log::{debug, info, trace}; -use std::cell::Cell; - -const POWER_POLICY_CHANNEL_SIZE: usize = 1; pub struct ControllerState { events: Signal, @@ -43,22 +40,24 @@ impl ControllerState { } else { ConnectionState::Attached }); + + let mut events = PortEvent::none(); match role { PowerRole::Source => { status.available_source_contract = Some(capability); status.unconstrained_power = unconstrained; + events.status.set_new_power_contract_as_provider(true); } PowerRole::Sink => { status.available_sink_contract = Some(capability); status.unconstrained_power = unconstrained; + events.status.set_new_power_contract_as_consumer(true); + events.status.set_sink_ready(true); } } *self.status.lock().await = status; - let mut events = PortEvent::none(); events.status.set_plug_inserted_or_removed(true); - events.status.set_new_power_contract_as_consumer(true); - events.status.set_sink_ready(true); self.events.signal(events); } @@ -99,19 +98,19 @@ impl Default for ControllerState { pub struct Controller<'a> { state: &'a ControllerState, - events: Cell, + events: PortEvent, } impl<'a> Controller<'a> { pub fn new(state: &'a ControllerState) -> Self { Self { state, - events: Cell::new(PortEvent::none()), + events: PortEvent::none(), } } /// Function to demonstrate calling functions directly on the controller - pub fn custom_function(&self) { + pub fn custom_function(&mut self) { info!("Custom function called on controller"); } } @@ -122,14 +121,14 @@ impl embedded_services::type_c::controller::Controller for Controller<'_> { async fn wait_port_event(&mut self) -> Result<(), Error> { let events = self.state.events.wait().await; trace!("Port event: {events:#?}"); - self.events.set(events); + self.events = self.events.union(events); Ok(()) } async fn clear_port_events(&mut self, _port: LocalPortId) -> Result> { - let events = self.events.get(); + let events = self.events; debug!("Clear port events: {events:#?}"); - self.events.set(PortEvent::none()); + self.events = PortEvent::none(); Ok(events) } @@ -342,6 +341,7 @@ pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< 'a, GlobalRawMutex, Mutex>, + channel::DynamicSender<'a, policy::RequestData>, + channel::DynamicReceiver<'a, policy::RequestData>, Validator, - POWER_POLICY_CHANNEL_SIZE, >; diff --git a/examples/std/src/lib/type_c/mod.rs b/examples/std/src/lib/type_c/mod.rs index bf900859f..4fc4ccf55 100644 --- a/examples/std/src/lib/type_c/mod.rs +++ b/examples/std/src/lib/type_c/mod.rs @@ -1,19 +1 @@ pub mod mock_controller; - -pub struct DummyCharger(embedded_services::power::policy::charger::Device); -impl embedded_services::power::policy::charger::ChargerContainer for DummyCharger { - fn get_charger(&self) -> &embedded_services::power::policy::charger::Device { - &self.0 - } -} - -pub struct DummyPowerDevice( - embedded_services::power::policy::device::Device, -); -impl embedded_services::power::policy::device::DeviceContainer - for DummyPowerDevice -{ - fn get_power_policy_device(&self) -> &embedded_services::power::policy::device::Device { - &self.0 - } -} diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 8e661bb0d..9be53aed5 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -19,6 +19,14 @@ embedded-services.workspace = true log = { workspace = true, optional = true } heapless.workspace = true +[dev-dependencies] +static_cell.workspace = true +critical-section = { workspace = true, features = ["std"] } +embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } +tokio = { workspace = true, features = ["rt", "macros", "time"] } +env_logger = "0.11.8" +log = { workspace = true } + [features] default = [] defmt = [ diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/consumer.rs index 47bdb8403..58dd8a20b 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/consumer.rs @@ -29,14 +29,17 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl PowerPolicy { +impl + 'static> PowerPolicy<'_, D, R> +where + D::Inner: DeviceTrait, +{ /// Iterate over all devices to determine what is best power port provides the highest power async fn find_best_consumer(&self, state: &InternalState) -> Result, Error> { let mut best_consumer = None; let current_consumer_id = state.current_consumer_state.map(|f| f.device_id); for node in self.context.devices() { - let device = node.data::>().ok_or(Error::InvalidDevice)?; + let device = node.data::>().ok_or(Error::InvalidDevice)?; let consumer_capability = device.consumer_capability().await; // Don't consider consumers below minimum threshold @@ -90,7 +93,7 @@ impl PowerPolicy { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); for node in self.context.devices() { - let device = node.data::>().ok_or(Error::InvalidDevice)?; + let device = node.data::>().ok_or(Error::InvalidDevice)?; if let Some(capability) = device.consumer_capability().await { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; @@ -195,18 +198,22 @@ impl PowerPolicy { } state.current_consumer_state = None; - // Disconnect the current consumer if needed - if let Ok(consumer) = self - .context - .try_policy_action::(current_consumer.device_id) - .await - { - info!( - "Device {}, disconnecting current consumer", - current_consumer.device_id.0 - ); + let consumer_device = self.context.get_device(current_consumer.device_id)?; + let mut locked_state = consumer_device.state.lock().await; + let mut locked_device = consumer_device.device.lock().await; + + if matches!(locked_state.state(), State::ConnectedConsumer(_)) { + // Disconnect the current consumer if needed + info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); // disconnect current consumer and set idle - consumer.disconnect().await?; + locked_device.disconnect().await?; + if let Err(e) = locked_state.disconnect(false) { + // This should never happen because we check the state above, log an error instead of a panic + error!( + "Device{}: Disconnect transition failed: {:#?}", + current_consumer.device_id.0, e + ); + } } // If no chargers are registered, they won't receive the new power capability. @@ -224,28 +231,23 @@ impl PowerPolicy { } info!("Device {}, connecting new consumer", new_consumer.device_id.0); - if let Ok(idle) = self - .context - .try_policy_action::(new_consumer.device_id) - .await - { - idle.connect_consumer(new_consumer.consumer_power_capability).await?; - self.post_consumer_connected(state, new_consumer).await?; - } else if let Ok(provider) = self - .context - .try_policy_action::(new_consumer.device_id) - .await - { - provider + let device = self.context.get_device(new_consumer.device_id)?; + let mut locked_device = device.device.lock().await; + let mut locked_state = device.state.lock().await; + + if let e @ Err(_) = locked_state.connect_consumer(new_consumer.consumer_power_capability) { + error!( + "Device{}: Not ready to connect consumer, state: {:#?}", + device.id().0, + locked_state.state() + ); + e + } else { + locked_device .connect_consumer(new_consumer.consumer_power_capability) .await?; - state.current_consumer_state = Some(new_consumer); - self.post_consumer_connected(state, new_consumer).await?; - } else { - error!("Error obtaining device in idle state"); + self.post_consumer_connected(state, new_consumer).await } - - Ok(()) } /// Determines and connects the best external power diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index a267ea43f..f583f8aff 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -2,8 +2,11 @@ use core::ops::DerefMut; use embassy_sync::mutex::Mutex; use embedded_services::GlobalRawMutex; -use embedded_services::power::policy::device::Device; -use embedded_services::power::policy::{action, policy, *}; +use embedded_services::event::Receiver; +use embedded_services::power::policy::device::{Device, DeviceTrait, State}; +use embedded_services::power::policy::policy::RequestData; +use embedded_services::power::policy::{policy, *}; +use embedded_services::sync::Lockable; use embedded_services::{comms, error, info}; pub mod config; @@ -29,9 +32,12 @@ struct InternalState { } /// Power policy state -pub struct PowerPolicy { +pub struct PowerPolicy<'a, D: Lockable, R: Receiver> +where + D::Inner: DeviceTrait, +{ /// Power policy context - pub context: policy::Context, + pub context: &'a policy::Context, /// State state: Mutex, /// Comms endpoint @@ -40,49 +46,89 @@ pub struct PowerPolicy { config: config::Config, } -impl PowerPolicy { +impl<'a, D: Lockable + 'static, R: Receiver + 'static> PowerPolicy<'a, D, R> +where + D::Inner: DeviceTrait, +{ /// Create a new power policy - pub fn new(config: config::Config) -> Self { + pub fn new(context: &'a policy::Context, config: config::Config) -> Self { Self { - context: policy::Context::new(), + context, state: Mutex::new(InternalState::default()), tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Power)), config, } } - async fn process_notify_attach(&self) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; - Ok(()) + async fn process_notify_attach(&self, device: &Device<'_, D, R>) { + if let Err(e) = device.state.lock().await.attach() { + error!("Device{}: Invalid state for attach: {:#?}", device.id().0, e); + } } - async fn process_notify_detach(&self, device: &device::Device) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; - self.remove_connected_provider(device.id()).await; - self.update_current_consumer().await?; - Ok(()) + async fn process_notify_detach(&self, device: &Device<'_, D, R>) -> Result<(), Error> { + device.state.lock().await.detach(); + self.update_current_consumer().await } - async fn process_notify_consumer_power_capability(&self) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; - self.update_current_consumer().await?; - Ok(()) + async fn process_notify_consumer_power_capability( + &self, + device: &Device<'_, D, R>, + capability: Option, + ) -> Result<(), Error> { + if let Err(e) = device.state.lock().await.update_consumer_power_capability(capability) { + error!( + "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", + device.id().0, + e, + ); + } + + self.update_current_consumer().await } - async fn process_request_provider_power_capabilities(&self, device: DeviceId) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; - self.connect_provider(device).await; - Ok(()) + async fn process_request_provider_power_capabilities( + &self, + device: &Device<'_, D, R>, + capability: Option, + ) -> Result<(), Error> { + if let Err(e) = device + .state + .lock() + .await + .update_requested_provider_power_capability(capability) + { + error!( + "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", + device.id().0, + e, + ); + } + + self.connect_provider(device.id()).await } - async fn process_notify_disconnect(&self, device: &device::Device) -> Result<(), Error> { - self.context.send_response(Ok(policy::ResponseData::Complete)).await; - if let Some(consumer) = self.state.lock().await.current_consumer_state.take() { - info!("Device{}: Connected consumer disconnected", consumer.device_id.0); + async fn process_notify_disconnect(&self, device: &Device<'_, D, R>) -> Result<(), Error> { + if let Err(e) = device.state.lock().await.disconnect(true) { + error!( + "Device{}: Invalid state for notify disconnect, catching up: {:#?}", + device.id().0, + e, + ); + } + + if self + .state + .lock() + .await + .current_consumer_state + .is_some_and(|current| current.device_id == device.id()) + { + info!("Device{}: Connected consumer disconnected", device.id().0); self.disconnect_chargers().await?; self.comms_notify(CommsMessage { - data: CommsData::ConsumerDisconnected(consumer.device_id), + data: CommsData::ConsumerDisconnected(device.id()), }) .await; } @@ -124,31 +170,33 @@ impl PowerPolicy { let device = self.context.get_device(request.id)?; match request.data { - policy::RequestData::NotifyAttached => { + policy::RequestData::Attached => { info!("Received notify attached from device {}", device.id().0); - self.process_notify_attach().await + self.process_notify_attach(device).await; + Ok(()) } - policy::RequestData::NotifyDetached => { + policy::RequestData::Detached => { info!("Received notify detached from device {}", device.id().0); self.process_notify_detach(device).await } - policy::RequestData::NotifyConsumerCapability(capability) => { + policy::RequestData::UpdatedConsumerCapability(capability) => { info!( "Device{}: Received notify consumer capability: {:#?}", device.id().0, capability, ); - self.process_notify_consumer_power_capability().await + self.process_notify_consumer_power_capability(device, capability).await } - policy::RequestData::RequestProviderCapability(capability) => { + policy::RequestData::RequestedProviderCapability(capability) => { info!( "Device{}: Received request provider capability: {:#?}", device.id().0, capability, ); - self.process_request_provider_power_capabilities(device.id()).await + self.process_request_provider_power_capabilities(device, capability) + .await } - policy::RequestData::NotifyDisconnect => { + policy::RequestData::Disconnected => { info!("Received notify disconnect from device {}", device.id().0); self.process_notify_disconnect(device).await } @@ -162,4 +210,7 @@ impl PowerPolicy { } } -impl comms::MailboxDelegate for PowerPolicy {} +impl + 'static> comms::MailboxDelegate for PowerPolicy<'_, D, R> where + D::Inner: DeviceTrait +{ +} diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/provider.rs index 0f134bcb1..7187f011c 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/provider.rs @@ -3,7 +3,7 @@ //! the system is in unlimited power state. In this mode up to [provider_unlimited](super::Config::provider_unlimited) //! is provided to each device. Above this threshold, the system is in limited power state. //! In this mode [provider_limited](super::Config::provider_limited) is provided to each device -use embedded_services::{debug, trace}; +use embedded_services::{debug, event::Receiver, power::policy::policy::RequestData, trace}; use super::*; @@ -25,34 +25,27 @@ pub(super) struct State { state: PowerState, } -impl PowerPolicy { +impl + 'static> PowerPolicy<'_, D, R> +where + D::Inner: DeviceTrait, +{ /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&self, requester_id: DeviceId) { + pub(super) async fn connect_provider(&self, requester_id: DeviceId) -> Result<(), Error> { trace!("Device{}: Attempting to connect as provider", requester_id.0); - let requester = match self.context.get_device(requester_id) { - Ok(device) => device, - Err(_) => { - error!("Device{}: Invalid device", requester_id.0); - return; - } - }; + let requester = self.context.get_device(requester_id)?; let requested_power_capability = match requester.requested_provider_capability().await { Some(cap) => cap, // Requester is no longer requesting power _ => { info!("Device{}: No-longer requesting power", requester.id().0); - return; + return Ok(()); } }; - let mut state = self.state.lock().await; + let mut policy_state = self.state.lock().await; let mut total_power_mw = 0; // Determine total requested power draw - for device in self - .context - .devices() - .iter_only::>() - { + for device in self.context.devices().iter_only::>() { let target_provider_cap = if device.id() == requester_id { // Use the requester's requested power capability // this handles both new connections and upgrade requests @@ -62,17 +55,17 @@ impl PowerPolicy { device.provider_capability().await }; total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); + } - if total_power_mw > self.config.limited_power_threshold_mw { - state.current_provider_state.state = PowerState::Limited; - } else { - state.current_provider_state.state = PowerState::Unlimited; - } + if total_power_mw > self.config.limited_power_threshold_mw { + policy_state.current_provider_state.state = PowerState::Limited; + } else { + policy_state.current_provider_state.state = PowerState::Unlimited; } - debug!("New power state: {:?}", state.current_provider_state.state); + debug!("New power state: {:?}", policy_state.current_provider_state.state); - let target_power = match state.current_provider_state.state { + let target_power = match policy_state.current_provider_state.state { PowerState::Limited => ProviderPowerCapability { capability: self.config.provider_limited, flags: requested_power_capability.flags, @@ -91,36 +84,22 @@ impl PowerPolicy { } }; - let connected = if let Ok(action) = self.context.try_policy_action::(requester.id()).await { - if let Err(e) = action.connect_provider(target_power).await { - error!("Device{}: Failed to connect as provider, {:#?}", requester.id().0, e); - } else { - self.post_provider_connected(&mut state, requester.id(), target_power) - .await; - } - Ok(()) - } else if let Ok(action) = self - .context - .try_policy_action::(requester.id()) - .await - { - if let Err(e) = action.connect_provider(target_power).await { - error!("Device{}: Failed to connect as provider, {:#?}", requester.id().0, e); - } else { - self.post_provider_connected(&mut state, requester.id(), target_power) - .await; - } - Ok(()) - } else { - Err(Error::InvalidState( - device::StateKind::Idle, - requester.state().await.kind(), - )) - }; + let device = self.context.get_device(requester_id)?; + let mut locked_state = device.state.lock().await; + let mut locked_device = device.device.lock().await; - // Don't need to do anything special, the device is responsible for attempting to reconnect - if let Err(e) = connected { - error!("Device{}: Failed to connect as provider, {:#?}", requester.id().0, e); + if let e @ Err(_) = locked_state.connect_provider(target_power) { + error!( + "Device{}: Cannot provide, device is in state {:#?}", + device.id().0, + locked_state.state() + ); + e + } else { + locked_device.connect_provider(target_power).await?; + self.post_provider_connected(&mut policy_state, requester_id, target_power) + .await; + Ok(()) } } diff --git a/power-policy-service/src/task.rs b/power-policy-service/src/task.rs index f33327e2d..4c9c8f1e1 100644 --- a/power-policy-service/src/task.rs +++ b/power-policy-service/src/task.rs @@ -1,6 +1,9 @@ use embedded_services::{ - comms, error, info, - power::policy::{charger, device}, + comms, error, + event::Receiver, + info, + power::policy::{device::DeviceTrait, policy::RequestData}, + sync::Lockable, }; use crate::PowerPolicy; @@ -16,64 +19,19 @@ pub enum InitError { ChargerDeviceRegistrationFailed, } -/// Initializes and runs the power policy task. -/// -/// This task function initializes the power policy service by registering its endpoint -/// with the comms layer, registering any provided **non Type-C** power devices and charger devices, -/// and then continuously processes incoming power policy requests. It should be run as its own task -/// that never returns. -/// -/// # Generic Parameters -/// -/// * `POLICY_CHANNEL_SIZE` - The capacity of the channel used for power policy messages. -/// * `NUM_POWER_DEVICES` - The number of **non Type-C** power devices to be managed by power policy. -/// * `NUM_CHARGERS` - The number of charger devices to be managed by power policy. -/// -/// # Arguments -/// -/// * `policy` - A static reference to the [`PowerPolicy`] instance that manages power policies. -/// * `power_devices` - An optional array of static references to **non Type-C** power device containers. -/// If provided, each device will be registered with the policy context. -/// * `charger_devices` - An optional array of static references to charger device containers. -/// If provided, each charger will be registered with the policy context. -/// -/// # Returns -/// -/// Returns `Result`. The `Never` type indicates that -/// this function runs indefinitely once initialized. The function returns an error if -/// initialization fails at any stage: -/// - [`InitError::RegistrationFailed`] - if comms endpoint registration fails -/// - [`InitError::PowerDeviceRegistrationFailed`] - if power device registration fails -/// - [`InitError::ChargerDeviceRegistrationFailed`] - if charger device registration fails -pub async fn task( - policy: &'static PowerPolicy, - power_devices: Option<[&'static impl device::DeviceContainer; NUM_POWER_DEVICES]>, - charger_devices: Option<[&'static impl charger::ChargerContainer; NUM_CHARGERS]>, -) -> Result { +/// Runs the power policy task. +pub async fn task + 'static>( + policy: &'static PowerPolicy<'static, D, R>, +) -> Result +where + D::Inner: DeviceTrait, +{ info!("Starting power policy task"); if comms::register_endpoint(policy, &policy.tp).await.is_err() { error!("Failed to register power policy endpoint"); return Err(InitError::RegistrationFailed); } - if let Some(power_devices) = power_devices { - for device in power_devices { - policy - .context - .register_device(device) - .map_err(|_| InitError::PowerDeviceRegistrationFailed)?; - } - } - - if let Some(charger_devices) = charger_devices { - for device in charger_devices { - policy - .context - .register_charger(device) - .map_err(|_| InitError::ChargerDeviceRegistrationFailed)?; - } - } - loop { if let Err(e) = policy.process().await { error!("Error processing request: {:?}", e); diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs new file mode 100644 index 000000000..f4e00cb71 --- /dev/null +++ b/power-policy-service/tests/common/mock.rs @@ -0,0 +1,82 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::signal::Signal; +use embedded_services::power::policy::device::{DeviceTrait, InternalState}; +use embedded_services::power::policy::flags::Consumer; +use embedded_services::power::policy::policy::RequestData; +use embedded_services::power::policy::{ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability}; +use embedded_services::{GlobalRawMutex, event, info}; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +pub enum FnCall { + ConnectConsumer(ConsumerPowerCapability), + ConnectProvider(ProviderPowerCapability), + Disconnect, + Reset, +} + +pub struct Mock<'a, S: event::Sender> { + sender: S, + fn_call: &'a Signal, + // Internal state + pub state: InternalState, +} + +impl<'a, S: event::Sender> Mock<'a, S> { + pub fn new(sender: S, fn_call: &'a Signal) -> Self { + Self { + sender, + fn_call, + state: Default::default(), + } + } + + fn record_fn_call(&mut self, fn_call: FnCall) { + let num_fn_calls = self + .fn_call + .try_take() + .map(|(num_fn_calls, _)| num_fn_calls) + .unwrap_or(0); + self.fn_call.signal((num_fn_calls + 1, fn_call)); + } + + pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { + self.state.attach().unwrap(); + + self.sender.send(RequestData::Attached).await; + + let capability = Some(ConsumerPowerCapability { + capability, + flags: Consumer::none(), + }); + self.state.update_consumer_power_capability(capability).unwrap(); + self.sender + .send(RequestData::UpdatedConsumerCapability(capability)) + .await; + } + + pub async fn simulate_detach(&mut self) { + self.state.detach(); + self.sender.send(RequestData::Detached).await; + } +} + +impl<'a, S: event::Sender> DeviceTrait for Mock<'a, S> { + async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { + info!("Connect consumer {:#?}", capability); + self.record_fn_call(FnCall::ConnectConsumer(capability)); + Ok(()) + } + + async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + info!("Connect provider: {:#?}", capability); + self.record_fn_call(FnCall::ConnectProvider(capability)); + Ok(()) + } + + async fn disconnect(&mut self) -> Result<(), Error> { + info!("Disconnect"); + self.record_fn_call(FnCall::Disconnect); + Ok(()) + } +} diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs new file mode 100644 index 000000000..482ca33af --- /dev/null +++ b/power-policy-service/tests/common/mod.rs @@ -0,0 +1,134 @@ +#![allow(clippy::unwrap_used)] +use embassy_futures::{ + join::join, + select::{Either, select}, +}; +use embassy_sync::{ + channel::{Channel, DynamicReceiver, DynamicSender}, + mutex::Mutex, + signal::Signal, +}; +use embassy_time::{Duration, with_timeout}; +use embedded_services::{ + GlobalRawMutex, + power::policy::{self, DeviceId, PowerCapability, device, policy::RequestData}, +}; +use power_policy_service::PowerPolicy; + +pub mod mock; + +use mock::Mock; +use static_cell::StaticCell; + +use crate::common::mock::FnCall; + +pub const LOW_POWER: PowerCapability = PowerCapability { + voltage_mv: 5000, + current_ma: 1500, +}; + +#[allow(dead_code)] +pub const HIGH_POWER: PowerCapability = PowerCapability { + voltage_mv: 5000, + current_ma: 3000, +}; + +pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); + +const EVENT_CHANNEL_SIZE: usize = 4; + +async fn power_policy_task( + completion_signal: &'static Signal, + power_policy: &'static PowerPolicy< + 'static, + Mutex>>, + DynamicReceiver<'static, RequestData>, + >, +) { + while let Either::First(result) = select(power_policy.process(), completion_signal.wait()).await { + result.unwrap(); + } +} + +pub type RegistrationType = device::Device< + 'static, + Mutex>>, + DynamicReceiver<'static, RequestData>, +>; + +pub type ServiceType = PowerPolicy< + 'static, + Mutex>>, + DynamicReceiver<'static, RequestData>, +>; + +pub type ServiceContext = policy::policy::Context< + Mutex>>, + DynamicReceiver<'static, RequestData>, +>; + +pub async fn run_test>( + timeout: Duration, + test: impl FnOnce( + &'static Mutex>>, + &'static Signal, + &'static Mutex>>, + &'static Signal, + ) -> F, +) { + env_logger::builder().filter_level(log::LevelFilter::Trace).init(); + embedded_services::init().await; + + static DEVICE0_EVENT_CHANNEL: StaticCell> = + StaticCell::new(); + let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); + let device0_sender = device0_event_channel.dyn_sender(); + let device0_receiver = device0_event_channel.dyn_receiver(); + + static DEVICE0_SIGNAL: StaticCell> = StaticCell::new(); + let device0_signal = DEVICE0_SIGNAL.init(Signal::new()); + static DEVICE0: StaticCell>>> = StaticCell::new(); + let device0 = DEVICE0.init(Mutex::new(Mock::new(device0_sender, device0_signal))); + + static DEVICE0_REGISTRATION: StaticCell = StaticCell::new(); + let device0_registration = DEVICE0_REGISTRATION.init(device::Device::new(DeviceId(0), device0, device0_receiver)); + + static DEVICE1_EVENT_CHANNEL: StaticCell> = + StaticCell::new(); + let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); + let device1_sender = device1_event_channel.dyn_sender(); + let device1_receiver = device1_event_channel.dyn_receiver(); + + static DEVICE1_SIGNAL: StaticCell> = StaticCell::new(); + let device1_signal = DEVICE1_SIGNAL.init(Signal::new()); + static DEVICE1: StaticCell>>> = StaticCell::new(); + let device1 = DEVICE1.init(Mutex::new(Mock::new(device1_sender, device1_signal))); + + static DEVICE1_REGISTRATION: StaticCell = StaticCell::new(); + let device1_registration = DEVICE1_REGISTRATION.init(device::Device::new(DeviceId(1), device1, device1_receiver)); + + static SERVICE_CONTEXT: StaticCell = StaticCell::new(); + let service_context = SERVICE_CONTEXT.init(policy::policy::Context::new()); + + service_context.register_device(device0_registration).unwrap(); + service_context.register_device(device1_registration).unwrap(); + + static POWER_POLICY: StaticCell = StaticCell::new(); + let power_policy = POWER_POLICY.init(power_policy_service::PowerPolicy::new( + service_context, + Default::default(), + )); + + static COMPLETION_SIGNAL: StaticCell> = StaticCell::new(); + let completion_signal = COMPLETION_SIGNAL.init(Signal::new()); + + with_timeout( + timeout, + join(power_policy_task(completion_signal, power_policy), async { + test(device0, device0_signal, device1, device1_signal).await; + completion_signal.signal(()); + }), + ) + .await + .unwrap(); +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs new file mode 100644 index 000000000..bf104cbc2 --- /dev/null +++ b/power-policy-service/tests/consumer.rs @@ -0,0 +1,135 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; +use embassy_time::{Duration, TimeoutError, with_timeout}; +use embedded_services::{ + GlobalRawMutex, + power::policy::{ConsumerPowerCapability, flags::Consumer, policy::RequestData}, +}; + +mod common; + +use common::LOW_POWER; + +use crate::common::{ + DEFAULT_TIMEOUT, HIGH_POWER, + mock::{FnCall, Mock}, + run_test, +}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test the basic consumer flow with a single device. +async fn test_single( + device0: &'static Mutex>>, + device0_signal: &'static Signal, +) { + // Test initial connection + { + device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + }) + ) + ); + device0_signal.reset(); + } + // Test detach + { + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + } +} + +/// Test swapping to a higher powered device. +async fn test_swap_higher( + device0: &'static Mutex>>, + device0_signal: &'static Signal, + device1: &'static Mutex>>, + device1_signal: &'static Signal, +) { + // Device0 connection at low power + { + device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + }) + ) + ); + device0_signal.reset(); + } + // Device1 connection at high power + { + device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: Consumer::none(), + }) + ) + ); + device1_signal.reset(); + } + // Test detach device1, should reconnect device0 + { + device1.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: Consumer::none(), + }) + ) + ); + device0_signal.reset(); + } +} + +/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. +#[tokio::test] +async fn run_all_tests() { + run_test( + DEFAULT_TIMEOUT, + |device0, device0_signal, device1, device1_signal| async move { + test_single(device0, device0_signal).await; + test_swap_higher(device0, device0_signal, device1, device1_signal).await; + }, + ) + .await; +} diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 054232f0a..1e5ba66bb 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -11,6 +11,26 @@ who = "jerrysxie " criteria = "safe-to-deploy" version = "0.4.2" +[[audits.anstream]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.6.21" + +[[audits.anstyle-parse]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.7" + +[[audits.anstyle-query]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "1.1.5" + +[[audits.anstyle-wincon]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "3.0.11" + [[audits.aquamarine]] who = "Robert Zieba " criteria = "safe-to-deploy" @@ -42,6 +62,11 @@ who = "jerrysxie " criteria = "safe-to-deploy" version = "0.2.0" +[[audits.colorchoice]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "1.0.3" + [[audits.const-init]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -152,6 +177,16 @@ criteria = "safe-to-deploy" version = "0.3.0" notes = "ODP crates are always trusted." +[[audits.env_filter]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.1.4" + +[[audits.env_logger]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.11.8" + [[audits.futures-task]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -198,6 +233,11 @@ who = "jerrysxie " criteria = "safe-to-deploy" version = "2.9.0" +[[audits.is_terminal_polyfill]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "1.70.2" + [[audits.itertools]] who = "Robert Zieba " criteria = "safe-to-deploy" @@ -209,6 +249,16 @@ criteria = "safe-to-deploy" version = "0.11.0" notes = "Used as a dependency for embassy-imxrt." +[[audits.jiff]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.18" + +[[audits.jiff-static]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.18" + [[audits.libc]] who = "Robert Zieba " criteria = "safe-to-run" @@ -291,6 +341,11 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "0.36.7" +[[audits.once_cell_polyfill]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "1.70.2" + [[audits.portable-atomic]] who = "Robert Zieba " criteria = "safe-to-deploy" @@ -301,6 +356,11 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "1.11.1" +[[audits.portable-atomic-util]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.4" + [[audits.proc-macro-error]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -336,6 +396,23 @@ who = "Billy Price " criteria = "safe-to-run" delta = "1.0.26 -> 1.0.27" +[[audits.serde]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.0.228" +notes = "Changes are mostly a reorganization of the internal module structure" + +[[audits.serde_core]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.0.226" + +[[audits.serde_derive]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.0.228" +notes = "Diff is clean-up in proc macros" + [[audits.serde_spanned]] who = "jerrysxie " criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 6c5db299f..94299bee0 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -188,6 +188,13 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" +[[publisher.windows-link]] +version = "0.2.1" +when = "2025-10-06" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + [[publisher.windows-numerics]] version = "0.2.0" when = "2025-03-18" @@ -293,7 +300,11 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[audits.OpenDevicePartnership.audits] +[[audits.OpenDevicePartnership.audits.windows-sys]] +who = "Felipe Balbi " +criteria = "safe-to-run" +version = "0.61.2" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" [[audits.bytecode-alliance.audits.adler2]] who = "Alex Crichton " @@ -521,12 +532,57 @@ criteria = "safe-to-deploy" version = "1.0.1" notes = "No unsafe usage or ambient capabilities" +[[audits.embark-studios.audits.utf8parse]] +who = "Johan Andersson " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "Single unsafe usage that looks sound, no ambient capabilities" + [[audits.embark-studios.audits.valuable]] who = "Johan Andersson " criteria = "safe-to-deploy" version = "0.1.0" notes = "No unsafe usage or ambient capabilities, sane build script" +[[audits.google.audits.anstyle]] +who = "Yu-An Wang " +criteria = "safe-to-run" +version = "1.0.4" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.anstyle]] +who = "Lukasz Anforowicz " +criteria = "safe-to-run" +delta = "1.0.4 -> 1.0.6" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.anstyle]] +who = "danakj " +criteria = "safe-to-run" +delta = "1.0.6 -> 1.0.7" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.anstyle]] +who = "Lukasz Anforowicz " +criteria = "safe-to-run" +delta = "1.0.7 -> 1.0.8" +notes = "Only Cargo.toml changes in the 1.0.7 => 1.0.8 delta." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.anstyle]] +who = "Dustin J. Mitchell " +criteria = "safe-to-run" +delta = "1.0.8 -> 1.0.9" +notes = "No changes" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.anstyle]] +who = "Lukasz Anforowicz " +criteria = "safe-to-run" +delta = "1.0.9 -> 1.0.10" +notes = "Minor changes related to `write_str`." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.autocfg]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -1474,6 +1530,18 @@ criteria = "safe-to-deploy" delta = "1.0.17 -> 1.0.25" aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" +[[audits.mozilla.audits.serde_core]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.0.226 -> 1.0.227" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.serde_core]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "1.0.227 -> 1.0.228" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + [[audits.mozilla.audits.sharded-slab]] who = "Mark Hammond " criteria = "safe-to-deploy" @@ -1557,6 +1625,12 @@ criteria = "safe-to-deploy" delta = "0.3.19 -> 0.3.20" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.utf8parse]] +who = "Nika Layzell " +criteria = "safe-to-deploy" +delta = "0.2.1 -> 0.2.2" +aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" + [[audits.mozilla.audits.windows-link]] who = "Mark Hammond " criteria = "safe-to-deploy" @@ -1570,6 +1644,12 @@ criteria = "safe-to-deploy" delta = "2.0.0 -> 2.0.1" aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.anstyle]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "1.0.10 -> 1.0.13" +aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" + [[audits.zcash.audits.autocfg]] who = "Jack Grigg " criteria = "safe-to-deploy" @@ -1577,6 +1657,12 @@ delta = "1.4.0 -> 1.5.0" notes = "Filesystem change is to remove the generated LLVM IR output file after probing." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.colorchoice]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "1.0.3 -> 1.0.4" +aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" + [[audits.zcash.audits.crunchy]] who = "Jack Grigg " criteria = "safe-to-deploy" diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 95c48a636..90163392d 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -4,8 +4,12 @@ use embassy_sync::{ pubsub::{DynImmediatePublisher, DynSubscriber}, }; use embedded_services::{ - GlobalRawMutex, debug, error, info, intrusive_list, + GlobalRawMutex, debug, error, + event::Receiver, + info, intrusive_list, ipc::deferred, + power::policy::policy, + sync::Lockable, trace, type_c::{ self, comms, @@ -252,10 +256,13 @@ impl<'a> Service<'a> { } /// Register the Type-C service with the power policy service - pub fn register_comms( + pub fn register_comms + 'static>( &'static self, - power_policy_context: &power_policy::policy::Context, - ) -> Result<(), intrusive_list::Error> { + power_policy_context: &power_policy::policy::Context, + ) -> Result<(), intrusive_list::Error> + where + PD::Inner: embedded_services::power::policy::device::DeviceTrait, + { power_policy_context.register_message_receiver(&self.power_policy_event_publisher) } diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index e0afb8181..8f47d0ebd 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,29 +1,26 @@ use core::future::Future; -use embedded_services::{error, info}; +use embassy_sync::mutex::Mutex; +use embedded_services::{error, event, info, power::policy::policy, sync::Lockable}; -use crate::{service::Service, wrapper::ControllerWrapper}; +use crate::{ + service::Service, + wrapper::{ControllerWrapper, proxy::PowerProxyDevice}, +}; /// Task to run the Type-C service, takes a closure to customize the event loop -pub async fn task_closure< - 'a, - M, - C, - V, - Fut: Future, - F: Fn(&'a Service) -> Fut, - const N: usize, - const POLICY_CHANNEL_SIZE: usize, ->( +pub async fn task_closure<'a, M, D, S, R, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, C, V, POLICY_CHANNEL_SIZE>; N], - power_policy_context: &'a embedded_services::power::policy::policy::Context, + wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], + power_policy_context: &policy::Context>, R>, cfu_client: &'a cfu_service::CfuClient, f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, - C: embedded_services::sync::Lockable, + D: Lockable, + S: event::Sender, + R: event::Receiver, V: crate::wrapper::FwOfferValidator, - ::Inner: embedded_services::type_c::controller::Controller, + D::Inner: embedded_services::type_c::controller::Controller, { info!("Starting type-c task"); @@ -48,16 +45,18 @@ pub async fn task_closure< } /// Task to run the Type-C service, running the default event loop -pub async fn task<'a, M, C, V, const N: usize, const POLICY_CHANNEL_SIZE: usize>( +pub async fn task<'a, M, D, S, R, V, const N: usize>( service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, C, V, POLICY_CHANNEL_SIZE>; N], - power_policy_context: &'a embedded_services::power::policy::policy::Context, + wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], + power_policy_context: &policy::Context>, R>, cfu_client: &'a cfu_service::CfuClient, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, - C: embedded_services::sync::Lockable, + D: embedded_services::sync::Lockable, + S: event::Sender, + R: event::Receiver, V: crate::wrapper::FwOfferValidator, - ::Inner: embedded_services::type_c::controller::Controller, + ::Inner: embedded_services::type_c::controller::Controller, { task_closure( service, diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index ec076bf89..d93f03534 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -21,40 +21,63 @@ //! use embedded_services::type_c::ControllerId; //! use embedded_services::power; //! use embedded_usb_pd::GlobalPortId; -//! use type_c_service::wrapper::backing::{Storage, ReferencedStorage}; +//! use type_c_service::wrapper::backing::{Storage, IntermediateStorage, ReferencedStorage}; +//! use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +//! use embedded_services::power::policy::policy; //! -//! -//! const NUM_PORTS: usize = 2; -//! const POLICY_CHANNEL_SIZE: usize = 1; -//! -//! fn init( -//! context: &'static embedded_services::type_c::controller::Context, -//! power_policy_context: &'static embedded_services::power::policy::policy::Context -//! ) { -//! static STORAGE: StaticCell> = StaticCell::new(); +//! fn init(context: &'static embedded_services::type_c::controller::Context) { +//! static STORAGE: StaticCell> = StaticCell::new(); //! let storage = STORAGE.init(Storage::new( //! context, //! ControllerId(0), -//! 0x0, -//! [(GlobalPortId(0), power::policy::DeviceId(0)), (GlobalPortId(1), power::policy::DeviceId(1))], -//! power_policy_context +//! 0x0, // CFU component ID (unused) +//! [GlobalPortId(0)], //! )); -//! static REFERENCED: StaticCell> = StaticCell::new(); -//! let referenced = REFERENCED.init(storage.create_referenced().unwrap()); -//! let _backing = referenced.create_backing().unwrap(); +//! +//! static INTERMEDIATE: StaticCell> = +//! StaticCell::new(); +//! let intermediate = INTERMEDIATE.init(storage.try_create_intermediate().expect("Failed to create intermediate storage")); +//! +//! static POLICY_CHANNEL: StaticCell> = StaticCell::new(); +//! let policy_channel = POLICY_CHANNEL.init(Channel::new()); +//! +//! let policy_sender = policy_channel.dyn_sender(); +//! let policy_receiver = policy_channel.dyn_receiver(); +//! +//! static REFERENCED: StaticCell< +//! type_c_service::wrapper::backing::ReferencedStorage< +//! 1, +//! NoopRawMutex, +//! DynamicSender<'_, policy::RequestData>, +//! DynamicReceiver<'_, policy::RequestData>, +//! >, +//! > = StaticCell::new(); +//! let referenced = REFERENCED.init( +//! intermediate +//! .try_create_referenced([(power::policy::DeviceId(0), policy_sender, policy_receiver)]) +//! .expect("Failed to create referenced storage"), +//! ); //! } //! ``` -use core::cell::{RefCell, RefMut}; +use core::{ + array::from_fn, + cell::{RefCell, RefMut}, +}; use cfu_service::component::CfuDevice; use embassy_sync::{ blocking_mutex::raw::RawMutex, + mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}, }; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::{ - power, + event, + power::{ + self, + policy::{DeviceId, policy}, + }, type_c::{ ControllerId, controller::PortStatus, @@ -63,7 +86,13 @@ use embedded_services::{ }; use embedded_usb_pd::{GlobalPortId, ado::Ado}; -use crate::{PortEventStreamer, wrapper::cfu}; +use crate::{ + PortEventStreamer, + wrapper::{ + cfu, + proxy::{PowerProxyChannel, PowerProxyDevice, PowerProxyReceiver}, + }, +}; /// Per-port state pub struct PortState<'a> { @@ -100,15 +129,14 @@ impl Default for ControllerState { } /// Internal state containing all per-port and per-controller state -struct InternalState<'a, const N: usize> { +struct InternalState<'a, const N: usize, S: event::Sender> { controller_state: ControllerState, port_states: [PortState<'a>; N], + port_power: [PortPower; N], } -impl<'a, const N: usize> InternalState<'a, N> { - fn try_new( - storage: &'a Storage, - ) -> Option { +impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { + fn try_new(storage: &'a Storage, power_events: [S; N]) -> Option { let port_states = storage.pd_alerts.each_ref().map(|pd_alert| { Some(PortState { status: PortStatus::new(), @@ -128,11 +156,15 @@ impl<'a, const N: usize> InternalState<'a, N> { // Panic safety: All array elements checked above #[allow(clippy::unwrap_used)] port_states: port_states.map(|s| s.unwrap()), + port_power: power_events.map(|sender| PortPower { + sender, + state: Default::default(), + }), }) } } -impl<'a, const N: usize> DynPortState<'a> for InternalState<'a, N> { +impl<'a, const N: usize, S: event::Sender> DynPortState<'a, S> for InternalState<'a, N, S> { fn num_ports(&self) -> usize { self.port_states.len() } @@ -152,10 +184,18 @@ impl<'a, const N: usize> DynPortState<'a> for InternalState<'a, N> { fn controller_state_mut(&mut self) -> &mut ControllerState { &mut self.controller_state } + + fn port_power(&self) -> &[PortPower] { + &self.port_power + } + + fn port_power_mut(&mut self) -> &mut [PortPower] { + &mut self.port_power + } } /// Trait to erase the generic port count argument -pub trait DynPortState<'a> { +pub trait DynPortState<'a, S: event::Sender> { fn num_ports(&self) -> usize; fn port_states(&self) -> &[PortState<'a>]; @@ -163,17 +203,20 @@ pub trait DynPortState<'a> { fn controller_state(&self) -> &ControllerState; fn controller_state_mut(&mut self) -> &mut ControllerState; + + fn port_power(&self) -> &[PortPower]; + fn port_power_mut(&mut self) -> &mut [PortPower]; } /// Service registration objects -pub struct Registration<'a, const POLICY_CHANNEL_SIZE: usize> { +pub struct Registration<'a, M: RawMutex, R: event::Receiver> { pub context: &'a embedded_services::type_c::controller::Context, pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, pub cfu_device: &'a CfuDevice, - pub power_devices: &'a [embedded_services::power::policy::device::Device], + pub power_devices: &'a [embedded_services::power::policy::device::Device<'a, Mutex>, R>], } -impl<'a, const POLICY_CHANNEL_SIZE: usize> Registration<'a, POLICY_CHANNEL_SIZE> { +impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, M, R> { pub fn num_ports(&self) -> usize { self.power_devices.len() } @@ -182,41 +225,84 @@ impl<'a, const POLICY_CHANNEL_SIZE: usize> Registration<'a, POLICY_CHANNEL_SIZE> /// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. const MAX_BUFFERED_PD_ALERTS: usize = 4; +pub struct PortPower> { + pub sender: S, + pub state: power::policy::device::InternalState, +} + /// Base storage -pub struct Storage<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> { +pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: CfuDevice, - power_devices: [embedded_services::power::policy::device::Device; N], + power_proxy_channels: [PowerProxyChannel; N], // State-related pd_alerts: [PubSubChannel; N], } -impl<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> Storage<'a, N, M, POLICY_CHANNEL_SIZE> { +impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( context: &'a embedded_services::type_c::controller::Context, controller_id: ControllerId, cfu_id: ComponentId, - ports: [(GlobalPortId, power::policy::DeviceId); N], - power_policy_context: &'static embedded_services::power::policy::policy::Context, + pd_ports: [GlobalPortId; N], ) -> Self { Self { context, controller_id, - pd_ports: ports.map(|(port, _)| port), + pd_ports, cfu_device: CfuDevice::new(cfu_id), - power_devices: ports - .map(|(_, device)| embedded_services::power::policy::device::Device::new(device, power_policy_context)), + power_proxy_channels: from_fn(|_| PowerProxyChannel::new()), pd_alerts: [const { PubSubChannel::new() }; N], } } - /// Create referenced storage from this storage - pub fn create_referenced(&self) -> Option> { - ReferencedStorage::try_from_storage(self) + /// Create intermediate storage from this storage + pub fn try_create_intermediate(&self) -> Option> { + IntermediateStorage::try_from_storage(self) + } +} + +/// Intermediate storage that holds power proxy devices +pub struct IntermediateStorage<'a, const N: usize, M: RawMutex> { + storage: &'a Storage<'a, N, M>, + power_proxy_devices: [Mutex>; N], + power_proxy_receivers: [Mutex>; N], +} + +impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { + fn try_from_storage(storage: &'a Storage<'a, N, M>) -> Option { + let mut power_proxy_devices = heapless::Vec::<_, N>::new(); + let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); + + for power_proxy_channel in storage.power_proxy_channels.iter() { + power_proxy_devices + .push(Mutex::new(power_proxy_channel.get_device())) + .ok()?; + power_proxy_receivers + .push(Mutex::new(power_proxy_channel.get_receiver())) + .ok()?; + } + + Some(Self { + storage, + power_proxy_devices: power_proxy_devices.into_array().ok()?, + power_proxy_receivers: power_proxy_receivers.into_array().ok()?, + }) + } + + /// Create referenced storage from this intermediate storage + pub fn try_create_referenced<'b, S: event::Sender, R: event::Receiver>( + &'b self, + policy_args: [(DeviceId, S, R); N], + ) -> Option> + where + 'b: 'a, + { + ReferencedStorage::try_from_intermediate(self, policy_args) } } @@ -224,46 +310,77 @@ impl<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> Storage< /// /// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state /// completely separately. -pub struct ReferencedStorage<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> { - storage: &'a Storage<'a, N, M, POLICY_CHANNEL_SIZE>, - state: RefCell>, +pub struct ReferencedStorage< + 'a, + const N: usize, + M: RawMutex, + S: event::Sender, + R: event::Receiver, +> { + intermediate: &'a IntermediateStorage<'a, N, M>, + state: RefCell>, pd_controller: embedded_services::type_c::controller::Device<'a>, + power_devices: [embedded_services::power::policy::device::Device<'a, Mutex>, R>; N], } -impl<'a, const N: usize, M: RawMutex, const POLICY_CHANNEL_SIZE: usize> - ReferencedStorage<'a, N, M, POLICY_CHANNEL_SIZE> +impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: event::Receiver> + ReferencedStorage<'a, N, M, S, R> { - /// Create a new referenced storage from the given storage and controller ID - fn try_from_storage(storage: &'a Storage) -> Option { + /// Create a new referenced storage from the given intermediate storage + fn try_from_intermediate( + intermediate: &'a IntermediateStorage<'a, N, M>, + policy_args: [(DeviceId, S, R); N], + ) -> Option { + let mut power_senders = heapless::Vec::<_, N>::new(); + let mut power_devices = heapless::Vec::<_, N>::new(); + + for (i, (device_id, policy_sender, policy_receiver)) in policy_args.into_iter().enumerate() { + power_senders.push(policy_sender).ok()?; + power_devices + .push(embedded_services::power::policy::device::Device::new( + device_id, + intermediate.power_proxy_devices.get(i)?, + policy_receiver, + )) + .ok()?; + } + Some(Self { - storage, - state: RefCell::new(InternalState::try_new(storage)?), + intermediate, + state: RefCell::new(InternalState::try_new( + intermediate.storage, + // Safe because both have N elements + power_senders.into_array().ok()?, + )?), pd_controller: embedded_services::type_c::controller::Device::new( - storage.controller_id, - storage.pd_ports.as_slice(), + intermediate.storage.controller_id, + intermediate.storage.pd_ports.as_slice(), ), + power_devices: power_devices.into_array().ok()?, }) } /// Creates the backing, returns `None` if a backing has already been created - pub fn create_backing<'b>(&'b self) -> Option> + pub fn create_backing<'b>(&'b self) -> Option> where 'b: 'a, { - self.state.try_borrow_mut().ok().map(|state| Backing { + self.state.try_borrow_mut().ok().map(|state| Backing:: { registration: Registration { - context: self.storage.context, + context: self.intermediate.storage.context, pd_controller: &self.pd_controller, - cfu_device: &self.storage.cfu_device, - power_devices: &self.storage.power_devices, + cfu_device: &self.intermediate.storage.cfu_device, + power_devices: &self.power_devices, }, state, + power_receivers: &self.intermediate.power_proxy_receivers, }) } } /// Wrapper around registration and type-erased state -pub struct Backing<'a, const POLICY_CHANNEL_SIZE: usize> { - pub(crate) registration: Registration<'a, POLICY_CHANNEL_SIZE>, - pub(crate) state: RefMut<'a, dyn DynPortState<'a>>, +pub struct Backing<'a, M: RawMutex, S: event::Sender, R: event::Receiver> { + pub(crate) registration: Registration<'a, M, R>, + pub(crate) state: RefMut<'a, dyn DynPortState<'a, S>>, + pub(crate) power_receivers: &'a [Mutex>], } diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index 69464894c..b7d1910b2 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -4,6 +4,7 @@ use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_services::power; +use embedded_services::power::policy::policy; use embedded_services::type_c::controller::Controller; use embedded_services::{debug, error}; @@ -29,10 +30,16 @@ impl FwUpdateState { } } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> - ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + D::Inner: Controller, { /// Create a new invalid FW version response fn create_invalid_fw_version_response(&self) -> InternalResponseData { @@ -45,7 +52,7 @@ where } /// Process a GetFwVersion command - async fn process_get_fw_version(&self, target: &mut C::Inner) -> InternalResponseData { + async fn process_get_fw_version(&self, target: &mut D::Inner) -> InternalResponseData { let version = match target.get_active_fw_version().await { Ok(v) => v, Err(Error::Pd(e)) => { @@ -76,7 +83,7 @@ where } /// Process a GiveOffer command - async fn process_give_offer(&self, target: &mut C::Inner, offer: &FwUpdateOffer) -> InternalResponseData { + async fn process_give_offer(&self, target: &mut D::Inner, offer: &FwUpdateOffer) -> InternalResponseData { if offer.component_info.component_id != self.registration.cfu_device.component_id() { return Self::create_offer_rejection(); } @@ -98,8 +105,8 @@ where async fn process_abort_update( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, ) -> InternalResponseData { // abort the update process match controller.abort_fw_update().await { @@ -123,8 +130,8 @@ where /// Process a GiveContent command async fn process_give_content( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, content: &FwUpdateContentCommand, ) -> InternalResponseData { let data = if let Some(data) = content.data.get(0..content.header.data_length as usize) { @@ -142,47 +149,14 @@ where // Detach from the power policy so it doesn't attempt to do anything while we are updating let controller_id = self.registration.pd_controller.id(); - let mut detached_all = true; - for power in self.registration.power_devices { + for power in state.port_power_mut() { info!("Controller{}: checking power device", controller_id.0); - if power.state().await != power::policy::device::State::Detached { + if power.state.state() != power::policy::device::State::Detached { info!("Controller{}: Detaching power device", controller_id.0); - if let Err(e) = power.detach().await { - error!("Controller{}: Failed to detach power device: {:?}", controller_id.0, e); - - // Sync to bring the controller to a known state with all services - match self.sync_state_internal(controller, state).await { - Ok(_) => debug!( - "Controller{}: Synced state after detaching power device", - controller_id.0 - ), - Err(Error::Pd(e)) => error!( - "Controller{}: Failed to sync state after detaching power device: {:?}", - controller_id.0, e - ), - Err(Error::Bus(_)) => error!( - "Controller{}: Failed to sync state after detaching power device, bus error", - controller_id.0 - ), - } - - detached_all = false; - break; - } + power.sender.send(policy::RequestData::Detached).await; } } - if !detached_all { - error!( - "Controller{}: Failed to detach all power devices, rejecting offer", - controller_id.0 - ); - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorPrepare, - )); - } - // Need to start the update self.fw_update_ticker.lock().await.reset(); match controller.start_fw_update().await { @@ -259,7 +233,7 @@ where } /// Process a CFU tick - pub async fn process_cfu_tick(&self, controller: &mut C::Inner, state: &mut dyn DynPortState<'_>) { + pub async fn process_cfu_tick(&self, controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>) { match state.controller_state_mut().fw_update_state { FwUpdateState::Idle => { // No FW update in progress, nothing to do @@ -301,8 +275,8 @@ where /// Process a CFU command pub async fn process_cfu_command( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, command: &RequestData, ) -> InternalResponseData { if state.controller_state().fw_update_state == FwUpdateState::Recovery { diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 1b9882c8e..d54fb7b1b 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,20 +1,26 @@ use super::{ControllerWrapper, FwOfferValidator}; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_services::{sync::Lockable, trace, type_c::controller::Controller}; +use embedded_services::{event, power::policy::policy, sync::Lockable, trace, type_c::controller::Controller}; use embedded_usb_pd::{Error, LocalPortId}; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> - ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + D::Inner: Controller, { /// Process a DisplayPort status update by retrieving the current DP status from the `controller` for the appropriate `port`. pub(super) async fn process_dp_status_update( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, port: LocalPortId, - ) -> Result::BusError>> { + ) -> Result::BusError>> { trace!("Processing DP status update event on port {}", port.0); let status = controller.get_dp_status(port).await?; diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index d6efd4612..c9b1b66fd 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -31,12 +31,11 @@ pub struct EventPortNotification { } /// Power policy command event data -pub struct EventPowerPolicyCommand<'a> { +pub struct EventPowerPolicyCommand { /// Port ID pub port: LocalPortId, /// Power policy request - pub request: - deferred::Request<'a, GlobalRawMutex, policy::device::CommandData, policy::device::InternalResponseData>, + pub request: policy::device::CommandData, } /// CFU events @@ -58,7 +57,7 @@ pub enum Event<'a> { /// Port notification PortNotification(EventPortNotification), /// Power policy command received - PowerPolicyCommand(EventPowerPolicyCommand<'a>), + PowerPolicyCommand(EventPowerPolicyCommand), /// Command from TCPM ControllerCommand(deferred::Request<'a, GlobalRawMutex, controller::Command, controller::Response<'static>>), /// Cfu event @@ -88,12 +87,9 @@ pub struct OutputPdAlert { } /// Power policy command output data -pub struct OutputPowerPolicyCommand<'a> { +pub struct OutputPowerPolicyCommand { /// Port ID pub port: LocalPortId, - /// Power policy request - pub request: - deferred::Request<'a, GlobalRawMutex, policy::device::CommandData, policy::device::InternalResponseData>, /// Response pub response: policy::device::InternalResponseData, } @@ -158,7 +154,7 @@ pub enum Output<'a> { /// Vendor-defined messaging. Vdm(vdm::Output), /// Power policy command received - PowerPolicyCommand(OutputPowerPolicyCommand<'a>), + PowerPolicyCommand(OutputPowerPolicyCommand), /// TPCM command response ControllerCommand(OutputControllerCommand<'a>), /// CFU recovery tick diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index aef64ffb2..e58ff41e3 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -21,24 +21,25 @@ use core::cell::RefMut; use core::future::pending; use core::ops::DerefMut; +use cfu_service::CfuClient; use embassy_futures::select::{Either, Either5, select, select_array, select5}; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::power::policy::device::StateKind; -use embedded_services::power::policy::{self, action}; +use embedded_services::power::policy::policy; use embedded_services::sync::Lockable; use embedded_services::type_c::controller::{self, Controller, PortStatus}; use embedded_services::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; -use embedded_services::{GlobalRawMutex, intrusive_list}; use embedded_services::{debug, error, info, trace, warn}; +use embedded_services::{event, intrusive_list}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; -use crate::wrapper::backing::DynPortState; +use crate::wrapper::backing::{DynPortState, PortPower}; use crate::wrapper::message::*; +use crate::wrapper::proxy::{PowerProxyDevice, PowerProxyReceiver}; use crate::{PortEventStreamer, PortEventVariant}; pub mod backing; @@ -48,6 +49,7 @@ mod dp; pub mod message; mod pd; mod power; +pub mod proxy; mod vdm; /// Base interval for checking for FW update timeouts and recovery attempts @@ -68,35 +70,49 @@ pub trait FwOfferValidator { pub const MAX_SUPPORTED_PORTS: usize = 2; /// Common functionality implemented on top of [`embedded_services::type_c::controller::Controller`] -pub struct ControllerWrapper<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> -where - ::Inner: Controller, +pub struct ControllerWrapper< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> where + D::Inner: Controller, { - controller: &'device C, + controller: &'device D, /// Trait object for validating firmware versions fw_version_validator: V, /// FW update ticker used to check for timeouts and recovery attempts fw_update_ticker: Mutex, /// Registration information for services - registration: backing::Registration<'device, POLICY_CHANNEL_SIZE>, + pub registration: backing::Registration<'device, M, R>, /// State - state: Mutex>>, + state: Mutex>>, /// SW port status event signal sw_status_event: Signal, /// General config config: config::Config, + /// Power proxy receivers + power_proxy_receivers: &'device [Mutex>], } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> - ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + D::Inner: Controller, { /// Create a new controller wrapper, returns `None` if the backing storage is already in use pub fn try_new( - controller: &'device C, + controller: &'device D, config: config::Config, - storage: &'device backing::ReferencedStorage<'device, N, M, POLICY_CHANNEL_SIZE>, + storage: &'device backing::ReferencedStorage<'device, N, M, S, R>, fw_version_validator: V, ) -> Option { const { @@ -114,14 +130,10 @@ where registration: backing.registration, state: Mutex::new(backing.state), sw_status_event: Signal::new(), + power_proxy_receivers: backing.power_receivers, }) } - /// Get the power policy devices for this controller. - pub fn power_policy_devices(&self) -> &[policy::device::Device] { - self.registration.power_devices - } - /// Get the cached port status, returns None if the port is invalid pub async fn get_cached_port_status(&self, local_port: LocalPortId) -> Option { self.state @@ -133,7 +145,7 @@ where } /// Synchronize the state between the controller and the internal state - pub async fn sync_state(&self) -> Result<(), Error<::BusError>> { + pub async fn sync_state(&self) -> Result<(), Error<::BusError>> { let mut controller = self.controller.lock().await; let mut state = self.state.lock().await; self.sync_state_internal(&mut controller, state.deref_mut().deref_mut()) @@ -143,9 +155,9 @@ where /// Synchronize the state between the controller and the internal state async fn sync_state_internal( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, - ) -> Result<(), Error<::BusError>> { + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, + ) -> Result<(), Error<::BusError>> { // Sync the controller state with the PD controller for (i, port_state) in state.port_states_mut().iter_mut().enumerate() { let mut status_changed = port_state.sw_status_event; @@ -180,11 +192,11 @@ where /// Handle a plug event async fn process_plug_event( &self, - _controller: &mut C::Inner, - power: &policy::device::Device, + _controller: &mut D::Inner, + power: &mut PortPower, port: LocalPortId, status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { if port.0 as usize >= self.registration.num_ports() { error!("Invalid port {}", port.0); return PdError::InvalidPort.into(); @@ -193,32 +205,10 @@ where info!("Plug event"); if status.is_connected() { info!("Plug inserted"); - - // Recover if we're not in the correct state - if power.state().await.kind() != StateKind::Detached { - warn!("Power device not in detached state, recovering"); - if let Err(e) = power.detach().await { - error!("Error detaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - - if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.attach().await { - error!("Error attaching power device: {:?}", e); - return PdError::Failed.into(); - } - } else { - // This should never happen - error!("Power device not in detached state"); - return PdError::InvalidMode.into(); - } + power.sender.send(policy::RequestData::Attached).await; } else { info!("Plug removed"); - if let Err(e) = power.detach().await { - error!("Error detaching power device: {:?}", e); - return PdError::Failed.into(); - }; + power.sender.send(policy::RequestData::Detached).await; } Ok(()) @@ -227,11 +217,11 @@ where /// Process port status changed events async fn process_port_status_changed<'b>( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, local_port_id: LocalPortId, status_event: PortStatusChanged, - ) -> Result, Error<::BusError>> { + ) -> Result, Error<::BusError>> { let global_port_id = self .registration .pd_controller @@ -240,11 +230,12 @@ where let status = controller.get_port_status(local_port_id).await?; trace!("Port{} status: {:#?}", global_port_id.0, status); - - let power = self - .get_power_device(local_port_id) - .ok_or(Error::Pd(PdError::InvalidPort))?; trace!("Port{} status events: {:#?}", global_port_id.0, status_event); + + let power = state + .port_power_mut() + .get_mut(local_port_id.0 as usize) + .ok_or(PdError::InvalidPort)?; if status_event.plug_inserted_or_removed() { self.process_plug_event(controller, power, local_port_id, &status) .await?; @@ -277,11 +268,11 @@ where /// Finalize a port status change output fn finalize_port_status_change( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, status_event: PortStatusChanged, status: PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { let port_index = local_port.0 as usize; let global_port_id = self .registration @@ -315,10 +306,10 @@ where /// Finalize a PD alert output fn finalize_pd_alert( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, alert: Ado, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { let port_index = local_port.0 as usize; let global_port_id = self .registration @@ -352,8 +343,8 @@ where /// DROP SAFETY: No state that needs to be restored async fn wait_port_pending( &self, - controller: &mut C::Inner, - ) -> Result::BusError>> { + controller: &mut D::Inner, + ) -> Result::BusError>> { if self.state.lock().await.controller_state().fw_update_state.in_progress() { // Don't process events while firmware update is in progress debug!("Firmware update in progress, ignoring port events"); @@ -370,7 +361,7 @@ where // DROP SAFETY: Safe as long as `wait_port_event` is drop safe match select(controller.wait_port_event(), async { self.sw_status_event.wait().await; - Ok::<_, Error<::BusError>>(()) + Ok::<_, Error<::BusError>>(()) }) .await { @@ -383,7 +374,7 @@ where } /// Wait for the next event - pub async fn wait_next(&self) -> Result, Error<::BusError>> { + pub async fn wait_next(&self) -> Result, Error<::BusError>> { // This loop is to ensure that if we finish streaming events we go back to waiting for the next port event loop { let event = { @@ -402,7 +393,7 @@ where Either5::First(stream) => { let mut stream = stream?; if let Some((port_index, event)) = stream - .next::::BusError>, _, _>(async |port_index| { + .next::::BusError>, _, _>(async |port_index| { // Combine the event read from the controller with any software generated events // Acquire the locks first to centralize the awaits here let mut controller = self.controller.lock().await; @@ -474,10 +465,10 @@ where /// Process a port notification async fn process_port_notification<'b>( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, port: LocalPortId, notification: PortNotificationSingle, - ) -> Result, Error<::BusError>> { + ) -> Result, Error<::BusError>> { match notification { PortNotificationSingle::Alert => { let ado = controller.get_pd_alert(port).await?; @@ -509,7 +500,7 @@ where pub async fn process_event<'b>( &self, event: Event<'b>, - ) -> Result, Error<::BusError>> { + ) -> Result, Error<::BusError>> { let mut controller = self.controller.lock().await; let mut state = self.state.lock().await; match event { @@ -519,13 +510,9 @@ where } Event::PowerPolicyCommand(EventPowerPolicyCommand { port, request }) => { let response = self - .process_power_command(&mut controller, state.deref_mut().deref_mut(), port, &request.command) + .process_power_command(&mut controller, state.deref_mut().deref_mut(), port, &request) .await; - Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { - port, - request, - response, - })) + Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) } Event::ControllerCommand(request) => { let response = self @@ -555,7 +542,7 @@ where } /// Event loop finalize - pub async fn finalize<'b>(&self, output: Output<'b>) -> Result<(), Error<::BusError>> { + pub async fn finalize<'b>(&self, output: Output<'b>) -> Result<(), Error<::BusError>> { let mut state = self.state.lock().await; match output { @@ -568,9 +555,18 @@ where Output::PdAlert(OutputPdAlert { port, ado }) => { self.finalize_pd_alert(state.deref_mut().deref_mut(), port, ado) } - Output::Vdm(vdm) => self.finalize_vdm(state.deref_mut().deref_mut(), vdm).map_err(Error::Pd), - Output::PowerPolicyCommand(OutputPowerPolicyCommand { request, response, .. }) => { - request.respond(response); + Output::Vdm(vdm) => self + .finalize_vdm(state.deref_mut().deref_mut(), vdm) + .await + .map_err(Error::Pd), + Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response }) => { + self.power_proxy_receivers + .get(port.0 as usize) + .ok_or(Error::Pd(PdError::InvalidPort))? + .lock() + .await + .send(response) + .await; Ok(()) } Output::ControllerCommand(OutputControllerCommand { request, response }) => { @@ -596,13 +592,13 @@ where pub async fn process_and_finalize_event<'b>( &self, event: Event<'b>, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { let output = self.process_event(event).await?; self.finalize(output).await } /// Combined processing function - pub async fn process_next_event(&self) -> Result<(), Error<::BusError>> { + pub async fn process_next_event(&self) -> Result<(), Error<::BusError>> { let event = self.wait_next().await?; self.process_and_finalize_event(event).await } @@ -611,10 +607,12 @@ where pub fn register( &'static self, controllers: &intrusive_list::IntrusiveList, - power_policy_context: &embedded_services::power::policy::policy::Context, - cfu_client: &cfu_service::CfuClient, - ) -> Result<(), Error<::BusError>> { - // TODO: Unify these devices? + power_policy_context: &embedded_services::power::policy::policy::Context< + Mutex>, + R, + >, + cfu_client: &'static CfuClient, + ) -> Result<(), Error<::BusError>> { for device in self.registration.power_devices { power_policy_context.register_device(device).map_err(|_| { error!( @@ -646,8 +644,14 @@ where } } -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> Lockable - for ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + C: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> Lockable for ControllerWrapper<'device, M, C, S, R, V> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index 4d1660618..a47fd93e5 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -2,6 +2,7 @@ use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; use embedded_services::debug; +use embedded_services::power::policy::device::State; use embedded_services::type_c::Cached; use embedded_services::type_c::controller::{InternalResponseData, Response}; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; @@ -9,14 +10,20 @@ use embedded_usb_pd::ucsi::{self, lpm}; use super::*; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> - ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + D::Inner: Controller, { async fn process_get_pd_alert( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, ) -> Result, PdError> { loop { @@ -46,7 +53,7 @@ where /// even for controllers that might not always broadcast sink ready events. pub(super) fn check_sink_ready_timeout( &self, - state: &mut dyn DynPortState<'_>, + state: &mut dyn DynPortState<'_, S>, status: &PortStatus, port: LocalPortId, new_contract: bool, @@ -116,34 +123,24 @@ where LocalPortId(port_index as u8) } - /// Set the maximum sink voltage for a port - pub async fn set_max_sink_voltage(&self, local_port: LocalPortId, voltage_mv: Option) -> Result<(), PdError> { - let mut controller = self.controller.lock().await; - let _ = self - .process_set_max_sink_voltage(&mut controller, local_port, voltage_mv) - .await?; - Ok(()) - } - /// Process a request to set the maximum sink voltage for a port async fn process_set_max_sink_voltage( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, voltage_mv: Option, ) -> Result { - let power_device = self.get_power_device(local_port).ok_or(PdError::InvalidPort)?; - - let state = power_device.state().await; + let port_power = state + .port_power_mut() + .get_mut(local_port.0 as usize) + .ok_or(PdError::InvalidPort)?; + let state = port_power.state.state(); debug!("Port{}: Current state: {:#?}", local_port.0, state); - if let Ok(connected_consumer) = power_device.try_device_action::().await { + if matches!(state, State::ConnectedConsumer(_)) { debug!("Port{}: Set max sink voltage, connected consumer found", local_port.0); if voltage_mv.is_some() - && voltage_mv - < power_device - .consumer_capability() - .await - .map(|c| c.capability.voltage_mv) + && voltage_mv < port_power.state.consumer_capability().map(|c| c.capability.voltage_mv) { // New max voltage is lower than current consumer capability which will trigger a renegociation // So disconnect first @@ -151,7 +148,7 @@ where "Port{}: Disconnecting consumer before setting max sink voltage", local_port.0 ); - let _ = connected_consumer.disconnect().await; + port_power.sender.send(policy::RequestData::Disconnected).await; } } @@ -166,8 +163,8 @@ where async fn process_get_port_status( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, cached: Cached, ) -> Result { @@ -193,8 +190,8 @@ where /// Handle a port command async fn process_port_command( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, command: &controller::PortCommand, ) -> Response<'static> { if state.controller_state().fw_update_state.in_progress() { @@ -273,7 +270,7 @@ where controller::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { match self.registration.pd_controller.lookup_local_port(command.port) { Ok(local_port) => { - self.process_set_max_sink_voltage(controller, local_port, voltage_mv) + self.process_set_max_sink_voltage(controller, state, local_port, voltage_mv) .await } Err(e) => Err(e), @@ -402,8 +399,8 @@ where async fn process_controller_command( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, command: &controller::InternalCommandData, ) -> Response<'static> { if state.controller_state().fw_update_state.in_progress() { @@ -438,8 +435,8 @@ where /// Handle a PD controller command pub(super) async fn process_pd_command( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, command: &controller::Command, ) -> Response<'static> { match command { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index f2e6a5b29..2c2267193 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -1,52 +1,41 @@ //! Module contain power-policy related message handling -use core::future; +use core::pin::pin; +use embassy_futures::select::select_slice; use embedded_services::{ debug, - ipc::deferred, power::policy::{ ConsumerPowerCapability, ProviderPowerCapability, - device::{CommandData, InternalResponseData}, + device::{CommandData, InternalResponseData, ResponseData}, flags::PsuType, }, }; +use embedded_services::power::policy::Error as PowerError; +use embedded_services::power::policy::device::CommandData as PowerCommand; + use crate::wrapper::config::UnconstrainedSink; use super::*; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> - ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + D::Inner: Controller, { - /// Return the power device for the given port - pub fn get_power_device(&self, port: LocalPortId) -> Option<&policy::device::Device> { - self.registration.power_devices.get(port.0 as usize) - } - /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, - power: &policy::device::Device, + power: &mut PortPower, status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); - - let current_state = power.state().await.kind(); - info!("current power state: {:?}", current_state); - - // Recover if we're not in the correct state - if status.is_connected() { - if let action::device::AnyState::Detached(state) = power.device_action().await { - warn!("Power device is detached, attempting to attach"); - if let Err(e) = state.attach().await { - error!("Error attaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - } - let available_sink_contract = status.available_sink_contract.map(|c| { let mut c: ConsumerPowerCapability = c.into(); let unconstrained = match self.config.unconstrained_sink { @@ -59,89 +48,30 @@ where c }); - if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.notify_consumer_power_capability(available_sink_contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.notify_consumer_power_capability(available_sink_contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else if let Ok(state) = power.try_device_action::().await { - if let Err(e) = state.notify_consumer_power_capability(available_sink_contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else { - error!("Invalid mode"); - return PdError::InvalidMode.into(); - } - + power + .sender + .send(policy::RequestData::UpdatedConsumerCapability(available_sink_contract)) + .await; Ok(()) } /// Handle a new contract as provider pub(super) async fn process_new_provider_contract( &self, - power: &policy::device::Device, + power: &mut PortPower, status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); - - let current_state = power.state().await.kind(); - info!("current power state: {:?}", current_state); - - if let action::device::AnyState::ConnectedConsumer(state) = power.device_action().await { - info!("ConnectedConsumer"); - if let Err(e) = state.detach().await { - info!("Error detaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - - // Recover if we're not in the correct state - if status.is_connected() { - if let action::device::AnyState::Detached(state) = power.device_action().await { - warn!("Power device is detached, attempting to attach"); - if let Err(e) = state.attach().await { - error!("Error attaching power device: {:?}", e); - return PdError::Failed.into(); - } - } - } - - let contract = status.available_source_contract.map(|c| { - let mut c: ProviderPowerCapability = c.into(); - c.flags.set_psu_type(PsuType::TypeC); - c - }); - if let Ok(state) = power.try_device_action::().await { - if let Some(contract) = contract { - if let Err(e) = state.request_provider_power_capability(contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } - } else if let Ok(state) = power.try_device_action::().await { - if let Some(contract) = contract { - if let Err(e) = state.request_provider_power_capability(contract).await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } else { - // No longer need to source, so disconnect - if let Err(e) = state.disconnect().await { - error!("Error setting power contract: {:?}", e); - return PdError::Failed.into(); - } - } - } else { - error!("Invalid mode"); - return PdError::InvalidMode.into(); - } - + power + .sender + .send(policy::RequestData::RequestedProviderCapability( + status.available_source_contract.map(|caps| { + let mut caps = ProviderPowerCapability::from(caps); + caps.flags.set_psu_type(PsuType::TypeC); + caps + }), + )) + .await; Ok(()) } @@ -149,18 +79,12 @@ where async fn process_disconnect( &self, port: LocalPortId, - controller: &mut C::Inner, - power: &policy::device::Device, - ) -> Result<(), Error<::BusError>> { - let state = power.state().await.kind(); - if state == StateKind::ConnectedConsumer { - info!("Port{}: Disconnect from ConnectedConsumer", port.0); - if controller.enable_sink_path(port, false).await.is_err() { - error!("Error disabling sink path"); - return PdError::Failed.into(); - } + controller: &mut D::Inner, + ) -> Result<(), Error<::BusError>> { + if controller.enable_sink_path(port, false).await.is_err() { + error!("Error disabling sink path"); + return PdError::Failed.into(); } - Ok(()) } @@ -169,8 +93,8 @@ where &self, port: LocalPortId, capability: ProviderPowerCapability, - _controller: &mut C::Inner, - ) -> Result<(), Error<::BusError>> { + _controller: &mut D::Inner, + ) -> Result<(), Error<::BusError>> { info!("Port{}: Connect as provider: {:#?}", port.0, capability); // TODO: double check explicit contract handling Ok(()) @@ -180,22 +104,24 @@ where /// /// Returns (local port ID, deferred request) /// DROP SAFETY: Call to a select over drop safe futures - pub(super) async fn wait_power_command( - &self, - ) -> ( - LocalPortId, - deferred::Request<'_, GlobalRawMutex, CommandData, InternalResponseData>, - ) { - let futures: [_; MAX_SUPPORTED_PORTS] = from_fn(|i| async move { - if let Some(device) = self.registration.power_devices.get(i) { - device.receive().await - } else { - future::pending().await + pub(super) async fn wait_power_command(&self) -> (LocalPortId, CommandData) { + let mut futures = heapless::Vec::<_, MAX_SUPPORTED_PORTS>::new(); + for receiver in self.power_proxy_receivers { + // TODO: check this at compile time + if futures + .push(async { + let mut lock = receiver.lock().await; + lock.receive().await + }) + .is_err() + { + error!("Futures vec overflow"); } - }); + } + // DROP SAFETY: Select over drop safe futures - let (request, local_id) = select_array(futures).await; - trace!("Power command: device{} {:#?}", local_id, request.command); + let (request, local_id) = select_slice(pin!(futures.as_mut_slice())).await; + trace!("Power command: device{} {:#?}", local_id, request); (LocalPortId(local_id as u8), request) } @@ -203,50 +129,42 @@ where /// Returns no error because this is a top-level function pub(super) async fn process_power_command( &self, - controller: &mut C::Inner, - state: &mut dyn DynPortState<'_>, + controller: &mut D::Inner, + state: &mut dyn DynPortState<'_, S>, port: LocalPortId, command: &CommandData, ) -> InternalResponseData { trace!("Processing power command: device{} {:#?}", port.0, command); if state.controller_state().fw_update_state.in_progress() { debug!("Port{}: Firmware update in progress", port.0); - return Err(policy::Error::Busy); + return Err(PowerError::Busy); } - let power = match self.get_power_device(port) { - Some(power) => power, - None => { - error!("Port{}: Error getting power device for port", port.0); - return Err(policy::Error::InvalidDevice); - } - }; - match command { - policy::device::CommandData::ConnectAsConsumer(capability) => { + PowerCommand::ConnectAsConsumer(capability) => { info!( "Port{}: Connect as consumer: {:?}, enable input switch", port.0, capability ); if controller.enable_sink_path(port, true).await.is_err() { error!("Error enabling sink path"); - return Err(policy::Error::Failed); + return Err(PowerError::Failed); } } - policy::device::CommandData::ConnectAsProvider(capability) => { + PowerCommand::ConnectAsProvider(capability) => { if self.process_connect_as_provider(port, *capability, controller).is_err() { error!("Error processing connect provider"); - return Err(policy::Error::Failed); + return Err(PowerError::Failed); } } - policy::device::CommandData::Disconnect => { - if self.process_disconnect(port, controller, power).await.is_err() { + PowerCommand::Disconnect => { + if self.process_disconnect(port, controller).await.is_err() { error!("Error processing disconnect"); - return Err(policy::Error::Failed); + return Err(PowerError::Failed); } } } - Ok(policy::device::ResponseData::Complete) + Ok(ResponseData::Complete) } } diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs new file mode 100644 index 000000000..252247e88 --- /dev/null +++ b/type-c-service/src/wrapper/proxy.rs @@ -0,0 +1,105 @@ +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embedded_services::power; +use embedded_services::power::policy::device::{ + CommandData as PolicyCommandData, DeviceTrait, InternalResponseData as PolicyResponseData, +}; + +pub struct PowerProxyChannel { + command_channel: Channel, + response_channel: Channel, +} + +impl PowerProxyChannel { + pub fn new() -> Self { + Self { + command_channel: Channel::new(), + response_channel: Channel::new(), + } + } + + pub fn get_device(&self) -> PowerProxyDevice<'_> { + PowerProxyDevice { + sender: self.command_channel.dyn_sender(), + receiver: self.response_channel.dyn_receiver(), + } + } + + pub fn get_receiver(&self) -> PowerProxyReceiver<'_> { + PowerProxyReceiver { + receiver: self.command_channel.dyn_receiver(), + sender: self.response_channel.dyn_sender(), + } + } +} + +pub struct PowerProxyReceiver<'a> { + sender: DynamicSender<'a, PolicyResponseData>, + receiver: DynamicReceiver<'a, PolicyCommandData>, +} + +impl<'a> PowerProxyReceiver<'a> { + pub fn new( + receiver: DynamicReceiver<'a, PolicyCommandData>, + sender: DynamicSender<'a, PolicyResponseData>, + ) -> Self { + Self { receiver, sender } + } + + pub async fn receive(&mut self) -> PolicyCommandData { + self.receiver.receive().await + } + + pub async fn send(&mut self, response: PolicyResponseData) { + self.sender.send(response).await; + } +} + +pub struct PowerProxyDevice<'a> { + sender: DynamicSender<'a, PolicyCommandData>, + receiver: DynamicReceiver<'a, PolicyResponseData>, +} + +impl<'a> PowerProxyDevice<'a> { + pub fn new( + sender: DynamicSender<'a, PolicyCommandData>, + receiver: DynamicReceiver<'a, PolicyResponseData>, + ) -> Self { + Self { sender, receiver } + } + + async fn execute(&mut self, command: PolicyCommandData) -> PolicyResponseData { + self.sender.send(command).await; + self.receiver.receive().await + } +} + +impl<'a> DeviceTrait for PowerProxyDevice<'a> { + async fn disconnect(&mut self) -> Result<(), power::policy::Error> { + self.execute(PolicyCommandData::Disconnect).await?.complete_or_err() + } + + async fn connect_provider( + &mut self, + capability: power::policy::ProviderPowerCapability, + ) -> Result<(), power::policy::Error> { + self.execute(PolicyCommandData::ConnectAsProvider(capability)) + .await? + .complete_or_err() + } + + async fn connect_consumer( + &mut self, + capability: power::policy::ConsumerPowerCapability, + ) -> Result<(), power::policy::Error> { + self.execute(PolicyCommandData::ConnectAsConsumer(capability)) + .await? + .complete_or_err() + } +} + +impl Default for PowerProxyChannel { + fn default() -> Self { + Self::new() + } +} diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index c4d0ad947..9f8a6b4fa 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -1,5 +1,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{ + event, + power::policy::policy, sync::Lockable, trace, type_c::{ @@ -13,18 +15,24 @@ use crate::wrapper::{DynPortState, message::vdm::OutputKind}; use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; -impl<'device, M: RawMutex, C: Lockable, V: FwOfferValidator, const POLICY_CHANNEL_SIZE: usize> - ControllerWrapper<'device, M, C, V, POLICY_CHANNEL_SIZE> +impl< + 'device, + M: RawMutex, + D: Lockable, + S: event::Sender, + R: event::Receiver, + V: FwOfferValidator, +> ControllerWrapper<'device, M, D, S, R, V> where - ::Inner: Controller, + D::Inner: Controller, { /// Process a VDM event by retrieving the relevant VDM data from the `controller` for the appropriate `port`. pub(super) async fn process_vdm_event( &self, - controller: &mut C::Inner, + controller: &mut D::Inner, port: LocalPortId, event: VdmNotification, - ) -> Result::BusError>> { + ) -> Result::BusError>> { trace!("Processing VDM event: {:?} on port {}", event, port.0); let kind = match event { VdmNotification::Entered => OutputKind::Entered(controller.get_other_vdm(port).await?), @@ -37,7 +45,11 @@ where } /// Finalize a VDM output by notifying the service. - pub(super) fn finalize_vdm(&self, state: &mut dyn DynPortState<'_>, output: Output) -> Result<(), PdError> { + pub(super) async fn finalize_vdm( + &self, + state: &mut dyn DynPortState<'_, S>, + output: Output, + ) -> Result<(), PdError> { trace!("Finalizing VDM output: {:?}", output); let Output { port, kind } = output; let global_port_id = self.registration.pd_controller.lookup_global_port(port)?; From ead4bc8be21954e32d90c3edd08e1b0cc6afd709 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:18:29 -0800 Subject: [PATCH 22/79] Add facility for extensible relay services with direct async calls (#716) This change adds a facility to allow relay services to be user-extensible by hoisting the knowledge of which types are relayable out of the relay service (e.g. eSPI service) into the application layer. The application layer can pass in a list of (name, address, relay-handler) tuples and use those to instantiate a relay service. This enables OEMs to add their own services and messages that can share the same message bus. This requires a few things: - Eliminating all static state from within the eSPI service (since type sizes are no longer known by the eSPI service) - Implementing a trait for each relayable service to do conversion between wire formats and function calls The time-alarm service has been ported to leverage this new facility as an example; the other relayable services (battery, debug, thermal) will be migrated in a future change. Because we're moving from the comms system to direct async calls, we incidentally also get rid of the requirement imposed by the comms system that services have lifetime 'static. This incidentally also allows making services that leverage this new facility generic over the lifetime of the hardware that they manage, which enables some integration testing scenarios. To demonstrate this capability, a couple simple tests were added to the time-alarm service. --- Cargo.lock | 4 + Cargo.toml | 1 + embedded-service/Cargo.toml | 1 + embedded-service/src/comms.rs | 6 - embedded-service/src/lib.rs | 9 + embedded-service/src/relay/mod.rs | 1242 +++++++++++------ espi-service/src/espi_service.rs | 561 +++++--- espi-service/src/mctp.rs | 36 +- espi-service/src/task.rs | 37 +- examples/pico-de-gallo/Cargo.lock | 7 + examples/rt633/Cargo.lock | 1 + examples/rt685s-evk/Cargo.lock | 1 + examples/rt685s-evk/src/bin/time_alarm.rs | 119 +- examples/std/Cargo.lock | 1 + .../src/acpi_timestamp.rs | 31 +- time-alarm-service-messages/src/lib.rs | 6 + time-alarm-service/Cargo.toml | 6 + time-alarm-service/src/lib.rs | 376 +++-- time-alarm-service/src/task.rs | 21 +- time-alarm-service/src/timer.rs | 40 +- time-alarm-service/tests/common/mocks.rs | 117 ++ time-alarm-service/tests/common/mod.rs | 1 + time-alarm-service/tests/tad_test.rs | 109 ++ uart-service/src/mctp.rs | 32 +- 24 files changed, 1788 insertions(+), 977 deletions(-) create mode 100644 time-alarm-service/tests/common/mocks.rs create mode 100644 time-alarm-service/tests/common/mod.rs create mode 100644 time-alarm-service/tests/tad_test.rs diff --git a/Cargo.lock b/Cargo.lock index e1c633b9a..24d0c21b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1012,6 +1012,7 @@ dependencies = [ "log", "mctp-rs", "num_enum", + "paste", "portable-atomic", "rstest", "serde", @@ -2315,15 +2316,18 @@ name = "time-alarm-service" version = "0.1.0" dependencies = [ "bitfield 0.17.0", + "critical-section", "defmt 0.3.100", "embassy-executor", "embassy-futures", "embassy-sync", "embassy-time", + "embassy-time-driver", "embedded-mcu-hal", "embedded-services", "log", "time-alarm-service-messages", + "tokio", "zerocopy", ] diff --git a/Cargo.toml b/Cargo.toml index 2e634339f..06f23b96d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb mctp-rs = { git = "https://github.com/dymk/mctp-rs" } num_enum = { version = "0.7.5", default-features = false } portable-atomic = { version = "1.11", default-features = false } +paste = "1.0.15" fixed = "1.23.1" heapless = "0.8.*" log = "0.4" diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index 4199d9ec8..5e83b291d 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -31,6 +31,7 @@ embedded-usb-pd.workspace = true heapless.workspace = true log = { workspace = true, optional = true } num_enum.workspace = true +paste.workspace = true uuid.workspace = true [dependencies.mctp-rs] diff --git a/embedded-service/src/comms.rs b/embedded-service/src/comms.rs index fc3eb0b30..b2e606771 100644 --- a/embedded-service/src/comms.rs +++ b/embedded-service/src/comms.rs @@ -53,9 +53,6 @@ pub enum Internal { /// Security service provider Security, - /// Time alarm service provider - TimeAlarm, - /// OEM defined receiver Oem(OemKey), } @@ -280,7 +277,6 @@ fn get_list(target: EndpointID) -> &'static OnceLock { static INTERNAL_LIST_NONVOL: OnceLock = OnceLock::new(); static INTERNAL_LIST_DEBUG: OnceLock = OnceLock::new(); static INTERNAL_LIST_SECURITY: OnceLock = OnceLock::new(); - static INTERNAL_LIST_TIME_ALARM: OnceLock = OnceLock::new(); static INTERNAL_LIST_OEM: OnceLock = OnceLock::new(); match int_endpoint { @@ -296,7 +292,6 @@ fn get_list(target: EndpointID) -> &'static OnceLock { Nonvol => &INTERNAL_LIST_NONVOL, Debug => &INTERNAL_LIST_DEBUG, Security => &INTERNAL_LIST_SECURITY, - TimeAlarm => &INTERNAL_LIST_TIME_ALARM, Oem(_key) => &INTERNAL_LIST_OEM, } } @@ -342,7 +337,6 @@ pub(crate) fn init() { get_list(Internal::Nonvol.into()).get_or_init(IntrusiveList::new); get_list(Internal::Debug.into()).get_or_init(IntrusiveList::new); get_list(Internal::Security.into()).get_or_init(IntrusiveList::new); - get_list(Internal::TimeAlarm.into()).get_or_init(IntrusiveList::new); get_list(Internal::Oem(0).into()).get_or_init(IntrusiveList::new); // initialize external subscriber lists diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index 18c4cd932..f20d6e892 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -26,6 +26,15 @@ pub mod relay; pub mod sync; pub mod type_c; +/// Hidden re-exports used by macros defined in this crate. +/// Not part of the public API — do not depend on these directly. +#[doc(hidden)] +pub mod _macro_internal { + pub use bitfield; + pub use mctp_rs; + pub use paste; +} + /// Global Mutex type, ThreadModeRawMutex is used in a microcontroller context, whereas CriticalSectionRawMutex is used /// in a standard context for unit testing. /// diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index fbf6e988f..72ed2c3a0 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -1,421 +1,821 @@ -//! Helper code for serialization/deserialization of arbitrary messages to/from the embedded controller via a relay service, e.g. the eSPI service. - -/// Error type for serializing/deserializing messages -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum MessageSerializationError { - /// The message payload does not represent a valid message - InvalidPayload(&'static str), - - /// The message discriminant does not represent a known message type - UnknownMessageDiscriminant(u16), - - /// The provided buffer is too small to serialize the message - BufferTooSmall, - - /// Unspecified error - Other(&'static str), -} - -/// Trait for serializing and deserializing messages -pub trait SerializableMessage: Sized { - /// Serializes the message into the provided buffer. - /// On success, returns the number of bytes written - fn serialize(self, buffer: &mut [u8]) -> Result; - - /// Returns the discriminant needed to deserialize this type of message. - fn discriminant(&self) -> u16; - - /// Deserializes the message from the provided buffer. - fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; -} - -// Prevent other types from implementing SerializableResult - they should instead use SerializableMessage on a Response type and an Error type -#[doc(hidden)] -mod private { - pub trait Sealed {} - - impl Sealed for Result {} -} - -/// Responses sent over MCTP are called "Results" and are of type Result where T and E both implement SerializableMessage -pub trait SerializableResult: private::Sealed + Sized { - /// The type of the result when the operation being responded to succeeded - type SuccessType: SerializableMessage; - - /// The type of the result when the operation being responded to failed - type ErrorType: SerializableMessage; - - /// Returns true if the result represents a successful operation, false otherwise - fn is_ok(&self) -> bool; - - /// Returns a unique discriminant that can be used to deserialize the specific type of result. - /// Discriminants can be reused for success and error messages. - fn discriminant(&self) -> u16; - - /// Writes the result into the provided buffer. - /// On success, returns the number of bytes written - fn serialize(self, buffer: &mut [u8]) -> Result; - - /// Attempts to deserialize the result from the provided buffer. - fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; -} - -impl SerializableResult for Result -where - T: SerializableMessage, - E: SerializableMessage, -{ - type SuccessType = T; - type ErrorType = E; - - fn is_ok(&self) -> bool { - Result::::is_ok(self) - } - - fn discriminant(&self) -> u16 { - match self { - Ok(success_value) => success_value.discriminant(), - Err(error_value) => error_value.discriminant(), - } - } - - fn serialize(self, buffer: &mut [u8]) -> Result { - match self { - Ok(success_value) => success_value.serialize(buffer), - Err(error_value) => error_value.serialize(buffer), - } - } - - fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result { - if is_error { - Ok(Err(E::deserialize(discriminant, buffer)?)) - } else { - Ok(Ok(T::deserialize(discriminant, buffer)?)) - } - } -} - -pub mod mctp { - //! Contains helper functions for services that relay comms messages over MCTP - - /// Error type for MCTP relay operations - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum MctpError { - /// The endpoint ID does not correspond to a known service - UnknownEndpointId, - } - - /// This macro generates the necessary types and impls to support relaying ODP messages to and from the comms system. - /// It takes as input a list of (service name, service ID, comms endpoint ID, request type, result type) tuples and - /// emits the following types: - /// - enum OdpService - a mapping from service name to MCTP endpoint ID - /// - enum HostRequest - an enum containing all the possible request types that were passed into the macro - /// - enum HostResult - an enum containing all the possible result types that were passed into the macro - /// - struct OdpHeader - a type representing the ODP MCTP header. - /// - fn send_to_comms(&comms::Message, impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, - /// a function that takes a received message and sends it to the appropriate service based on its type using the provided send function. - /// - /// Because this macro emits a number of types, it is recommended to invoke it inside a dedicated module. - /// - /// Arguments: - /// $service_name (identifier) - the name that this service will have in the emitted OdpService enum - /// $service_id (u8) - the service ID that will be used in the ODP MCTP header for messages related to this service. - /// $endpoint_id (comms::EndpointID value) - the comms endpoint ID that this service corresponds to. - /// NOTE: due to technical limitations in Rust macros, this must be surrounded with parentheses. - /// $request_type (type implementing SerializableMessage) - the type that represents requests for this service - /// $result_type (type implementing SerializableResult) - the type that represents results for this service - /// - /// Example usage: - /// - /// impl_odp_relay_types!( - /// Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - /// Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - /// Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; - /// ); - /// ^ ^ - /// note the above parentheses - these are required - #[macro_export] - macro_rules! impl_odp_mctp_relay_types { - ($($service_name:ident, - $service_id:expr, - ($($endpoint_id:tt)+), - $request_type:ty, - $result_type:ty; - )+) => { - - use bitfield::bitfield; - use core::convert::Infallible; - use mctp_rs::smbus_espi::SmbusEspiMedium; - use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; - - #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[repr(u8)] - pub(crate) enum OdpService { - $( - $service_name = $service_id, - )+ - } - - impl TryFrom for OdpService { - type Error = embedded_services::relay::mctp::MctpError; - fn try_from(endpoint_id_value: comms::EndpointID) -> Result { - match endpoint_id_value { - $( - $($endpoint_id)+ => Ok(OdpService::$service_name), - )+ - _ => Err(embedded_services::relay::mctp::MctpError::UnknownEndpointId), - } - } - } - - impl OdpService { - pub fn get_endpoint_id(&self) -> comms::EndpointID { - match self { - $( - OdpService::$service_name => $($endpoint_id)+, - )+ - } - } - } - - pub(crate) enum HostRequest { - $( - $service_name($request_type), - )+ - } - - impl HostRequest { - pub(crate) async fn send_to_endpoint(&self, source_endpoint: &comms::Endpoint, destination_endpoint_id: comms::EndpointID) -> Result<(), Infallible> { - match self { - $( - HostRequest::$service_name(request) => source_endpoint.send(destination_endpoint_id, request).await, - )+ - } - } - } - - impl MctpMessageTrait<'_> for HostRequest { - type Header = OdpHeader; - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - $( - HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), - )+ - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - $( - OdpService::$service_name => Self::$service_name( - <$request_type as SerializableMessage>::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, - ), - )+ - }) - } - } - - #[derive(Clone)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub(crate) enum HostResult { - $( - $service_name($result_type), - )+ - } - - impl HostResult { - pub(crate) fn discriminant(&self) -> u16 { - match self { - $( - HostResult::$service_name(result) => result.discriminant(), - )+ - } - } - - pub(crate) fn is_ok(&self) -> bool { - match self { - $( - HostResult::$service_name(result) => result.is_ok(), - )+ - } - } - } - - impl MctpMessageTrait<'_> for HostResult { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - $( - HostResult::$service_name(result) => result - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), - )+ - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - match header.service { - $( - OdpService::$service_name => { - match header.message_type { - OdpMessageType::Request => { - Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) - } - OdpMessageType::Result { is_error } => { - Ok(HostResult::$service_name(<$result_type as SerializableResult>::deserialize(is_error, header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) - } - } - }, - )+ - } - } - } - - bitfield! { - /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct OdpHeaderWireFormat(u32); - impl Debug; - impl new; - /// If true, represents a request; otherwise, represents a result - is_request, set_is_request: 25; - - // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - is_datagram, set_is_datagram: 24; - - /// The service ID that this message is related to - /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. - u8, service_id, set_service_id: 23, 16; - - /// On results, indicates if the result message is an error. Unused on requests. - is_error, set_is_error: 15; - - /// The message type/discriminant - u16, message_id, set_message_id: 14, 0; - } - - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub(crate) enum OdpMessageType { - Request, - Result { is_error: bool }, - } - - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub(crate) struct OdpHeader { - pub message_type: OdpMessageType, - pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - pub service: OdpService, - pub message_id: u16, - } - - impl From for OdpHeaderWireFormat { - fn from(src: OdpHeader) -> Self { - Self::new( - matches!(src.message_type, OdpMessageType::Request), - src.is_datagram, - src.service.into(), - match src.message_type { - OdpMessageType::Request => false, // unused on requests - OdpMessageType::Result { is_error } => is_error, - }, - src.message_id, - ) - } - } - - impl TryFrom for OdpHeader { - type Error = MctpPacketError; - - fn try_from(src: OdpHeaderWireFormat) -> Result { - let service = OdpService::try_from(src.service_id()) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; - - let message_type = if src.is_request() { - OdpMessageType::Request - } else { - OdpMessageType::Result { - is_error: src.is_error(), - } - }; - - Ok(OdpHeader { - message_type, - is_datagram: src.is_datagram(), - service, - message_id: src.message_id(), - }) - } - } - - impl MctpMessageHeaderTrait for OdpHeader { - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - let wire_format = OdpHeaderWireFormat::from(self); - let bytes = wire_format.0.to_be_bytes(); - buffer - .get_mut(0..bytes.len()) - .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? - .copy_from_slice(&bytes); - - Ok(bytes.len()) - } - - fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { - let bytes = buffer - .get(0..core::mem::size_of::()) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; - let raw = u32::from_be_bytes( - bytes - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - ); - - let parsed_wire_format = OdpHeaderWireFormat(raw); - let header = OdpHeader::try_from(parsed_wire_format) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; - - Ok(( - header, - buffer - .get(core::mem::size_of::()..) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - )) - } - } - - /// Attempt to route the provided message to the service that is registered to handle it based on its type. - pub(crate) fn send_to_comms( - message: &comms::Message, - send_fn: impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, - ) -> Result<(), comms::MailboxDelegateError> { - $( - if let Some(msg) = message.data.get::<$result_type>() { - send_fn( - $($endpoint_id)+, - HostResult::$service_name(*msg), - )?; - Ok(()) - } else - )+ - { - Err(comms::MailboxDelegateError::MessageNotFound) - } - } - }; -} - - pub use impl_odp_mctp_relay_types; -} +//! Helper code for serialization/deserialization of arbitrary messages to/from the embedded controller via a relay service, e.g. the eSPI service. + +/// Error type for serializing/deserializing messages +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MessageSerializationError { + /// The message payload does not represent a valid message + InvalidPayload(&'static str), + + /// The message discriminant does not represent a known message type + UnknownMessageDiscriminant(u16), + + /// The provided buffer is too small to serialize the message + BufferTooSmall, + + /// Unspecified error + Other(&'static str), +} + +/// Trait for serializing and deserializing messages +pub trait SerializableMessage: Sized { + /// Serializes the message into the provided buffer. + /// On success, returns the number of bytes written + fn serialize(self, buffer: &mut [u8]) -> Result; + + /// Returns the discriminant needed to deserialize this type of message. + fn discriminant(&self) -> u16; + + /// Deserializes the message from the provided buffer. + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result; +} + +// Prevent other types from implementing SerializableResult - they should instead use SerializableMessage on a Response type and an Error type +#[doc(hidden)] +mod private { + pub trait Sealed {} + + impl Sealed for Result {} +} + +/// Responses sent over MCTP are called "Results" and are of type Result where T and E both implement SerializableMessage +pub trait SerializableResult: private::Sealed + Sized { + /// The type of the result when the operation being responded to succeeded + type SuccessType: SerializableMessage; + + /// The type of the result when the operation being responded to failed + type ErrorType: SerializableMessage; + + /// Returns true if the result represents a successful operation, false otherwise + fn is_ok(&self) -> bool; + + /// Returns a unique discriminant that can be used to deserialize the specific type of result. + /// Discriminants can be reused for success and error messages. + fn discriminant(&self) -> u16; + + /// Writes the result into the provided buffer. + /// On success, returns the number of bytes written + fn serialize(self, buffer: &mut [u8]) -> Result; + + /// Attempts to deserialize the result from the provided buffer. + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result; +} + +impl SerializableResult for Result +where + T: SerializableMessage, + E: SerializableMessage, +{ + type SuccessType = T; + type ErrorType = E; + + fn is_ok(&self) -> bool { + Result::::is_ok(self) + } + + fn discriminant(&self) -> u16 { + match self { + Ok(success_value) => success_value.discriminant(), + Err(error_value) => error_value.discriminant(), + } + } + + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Ok(success_value) => success_value.serialize(buffer), + Err(error_value) => error_value.serialize(buffer), + } + } + + fn deserialize(is_error: bool, discriminant: u16, buffer: &[u8]) -> Result { + if is_error { + Ok(Err(E::deserialize(discriminant, buffer)?)) + } else { + Ok(Ok(T::deserialize(discriminant, buffer)?)) + } + } +} + +pub mod mctp { + //! Contains helper functions for services that relay comms messages over MCTP + + /// Error type for MCTP relay operations + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MctpError { + /// The endpoint ID does not correspond to a known service + UnknownEndpointId, + } + + /// This macro generates the necessary types and impls to support relaying ODP messages to and from the comms system. + /// It takes as input a list of (service name, service ID, comms endpoint ID, request type, result type) tuples and + /// emits the following types: + /// - enum OdpService - a mapping from service name to MCTP endpoint ID + /// - enum HostRequest - an enum containing all the possible request types that were passed into the macro + /// - enum HostResult - an enum containing all the possible result types that were passed into the macro + /// - struct OdpHeader - a type representing the ODP MCTP header. + /// - fn send_to_comms(&comms::Message, impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, + /// a function that takes a received message and sends it to the appropriate service based on its type using the provided send function. + /// + /// Because this macro emits a number of types, it is recommended to invoke it inside a dedicated module. + /// + /// Arguments: + /// $service_name (identifier) - the name that this service will have in the emitted OdpService enum + /// $service_id (u8) - the service ID that will be used in the ODP MCTP header for messages related to this service. + /// $endpoint_id (comms::EndpointID value) - the comms endpoint ID that this service corresponds to. + /// NOTE: due to technical limitations in Rust macros, this must be surrounded with parentheses. + /// $request_type (type implementing SerializableMessage) - the type that represents requests for this service + /// $result_type (type implementing SerializableResult) - the type that represents results for this service + /// + /// Example usage: + /// + /// impl_odp_relay_types!( + /// Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + /// Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + /// Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + /// ); + /// ^ ^ + /// note the above parentheses - these are required + #[deprecated( + note = "This macro is replaced by impl_odp_mctp_relay_handler - services should implement the RelayServiceHandler trait and relay services should be generic over an instance of the RelayHandler trait generated by impl_odp_mctp_relay_handler." + )] + #[macro_export] + macro_rules! impl_odp_mctp_relay_types { + ($($service_name:ident, + $service_id:expr, + ($($endpoint_id:tt)+), + $request_type:ty, + $result_type:ty; + )+) => { + + use bitfield::bitfield; + use core::convert::Infallible; + use mctp_rs::smbus_espi::SmbusEspiMedium; + use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; + + #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(u8)] + pub(crate) enum OdpService { + $( + $service_name = $service_id, + )+ + } + + impl TryFrom for OdpService { + type Error = embedded_services::relay::mctp::MctpError; + fn try_from(endpoint_id_value: comms::EndpointID) -> Result { + match endpoint_id_value { + $( + $($endpoint_id)+ => Ok(OdpService::$service_name), + )+ + _ => Err(embedded_services::relay::mctp::MctpError::UnknownEndpointId), + } + } + } + + impl OdpService { + pub fn get_endpoint_id(&self) -> comms::EndpointID { + match self { + $( + OdpService::$service_name => $($endpoint_id)+, + )+ + } + } + } + + pub(crate) enum HostRequest { + $( + $service_name($request_type), + )+ + } + + impl HostRequest { + pub(crate) async fn send_to_endpoint(&self, source_endpoint: &comms::Endpoint, destination_endpoint_id: comms::EndpointID) -> Result<(), Infallible> { + match self { + $( + HostRequest::$service_name(request) => source_endpoint.send(destination_endpoint_id, request).await, + )+ + } + } + } + + impl MctpMessageTrait<'_> for HostRequest { + type Header = OdpHeader; + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + Ok(match header.service { + $( + OdpService::$service_name => Self::$service_name( + <$request_type as SerializableMessage>::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, + ), + )+ + }) + } + } + + #[derive(Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) enum HostResult { + $( + $service_name($result_type), + )+ + } + + impl HostResult { + pub(crate) fn discriminant(&self) -> u16 { + match self { + $( + HostResult::$service_name(result) => result.discriminant(), + )+ + } + } + + pub(crate) fn is_ok(&self) -> bool { + match self { + $( + HostResult::$service_name(result) => result.is_ok(), + )+ + } + } + } + + impl MctpMessageTrait<'_> for HostResult { + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + type Header = OdpHeader; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostResult::$service_name(result) => result + .serialize(buffer) + .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + match header.service { + $( + OdpService::$service_name => { + match header.message_type { + OdpMessageType::Request => { + Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) + } + OdpMessageType::Result { is_error } => { + Ok(HostResult::$service_name(<$result_type as SerializableResult>::deserialize(is_error, header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) + } + } + }, + )+ + } + } + } + + bitfield! { + /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct OdpHeaderWireFormat(u32); + impl Debug; + impl new; + /// If true, represents a request; otherwise, represents a result + is_request, set_is_request: 25; + + // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + is_datagram, set_is_datagram: 24; + + /// The service ID that this message is related to + /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. + u8, service_id, set_service_id: 23, 16; + + /// On results, indicates if the result message is an error. Unused on requests. + is_error, set_is_error: 15; + + /// The message type/discriminant + u16, message_id, set_message_id: 14, 0; + } + + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) enum OdpMessageType { + Request, + Result { is_error: bool }, + } + + #[derive(Copy, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub(crate) struct OdpHeader { + pub message_type: OdpMessageType, + pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... + pub service: OdpService, + pub message_id: u16, + } + + impl From for OdpHeaderWireFormat { + fn from(src: OdpHeader) -> Self { + Self::new( + matches!(src.message_type, OdpMessageType::Request), + src.is_datagram, + src.service.into(), + match src.message_type { + OdpMessageType::Request => false, // unused on requests + OdpMessageType::Result { is_error } => is_error, + }, + src.message_id, + ) + } + } + + impl TryFrom for OdpHeader { + type Error = MctpPacketError; + + fn try_from(src: OdpHeaderWireFormat) -> Result { + let service = OdpService::try_from(src.service_id()) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; + + let message_type = if src.is_request() { + OdpMessageType::Request + } else { + OdpMessageType::Result { + is_error: src.is_error(), + } + }; + + Ok(OdpHeader { + message_type, + is_datagram: src.is_datagram(), + service, + message_id: src.message_id(), + }) + } + } + + impl MctpMessageHeaderTrait for OdpHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let wire_format = OdpHeaderWireFormat::from(self); + let bytes = wire_format.0.to_be_bytes(); + buffer + .get_mut(0..bytes.len()) + .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? + .copy_from_slice(&bytes); + + Ok(bytes.len()) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + let bytes = buffer + .get(0..core::mem::size_of::()) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; + let raw = u32::from_be_bytes( + bytes + .try_into() + .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + ); + + let parsed_wire_format = OdpHeaderWireFormat(raw); + let header = OdpHeader::try_from(parsed_wire_format) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; + + Ok(( + header, + buffer + .get(core::mem::size_of::()..) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + )) + } + } + + /// Attempt to route the provided message to the service that is registered to handle it based on its type. + pub(crate) fn send_to_comms( + message: &comms::Message, + send_fn: impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, + ) -> Result<(), comms::MailboxDelegateError> { + $( + if let Some(msg) = message.data.get::<$result_type>() { + send_fn( + $($endpoint_id)+, + HostResult::$service_name(*msg), + )?; + Ok(()) + } else + )+ + { + Err(comms::MailboxDelegateError::MessageNotFound) + } + } + }; +} + + #[allow(deprecated)] // Allow reexport until everyone has migrated away + pub use impl_odp_mctp_relay_types; + + /////////////////////////////////////////////////////////////////////////////////////////// + // V2 relay implementation + + /// Trait for types that are used by a relay service to relay messages from your service over the wire. + /// If you are implementing this trait, you should also implement RelayServiceHandler. + /// + pub trait RelayServiceHandlerTypes { + /// The request type that this service handler processes + type RequestType: super::SerializableMessage; + + /// The result type that this service handler processes + type ResultType: super::SerializableResult; + } + + /// Trait for a service that can be relayed over an external bus (e.g. battery service, thermal service, time-alarm service) + /// + pub trait RelayServiceHandler: RelayServiceHandlerTypes { + /// Process the provided request and yield a result. + fn process_request<'a>( + &'a self, + request: Self::RequestType, + ) -> impl core::future::Future + Send + 'a; + } + + // Traits below this point are intended for consumption by relay services (e.g. the eSPI service), not individual services that want their messages relayed. + // In general, you should not implement these yourself; rather, you should leverage the `impl_odp_mctp_relay_handler` macro to do that for you. + + /// Contains additional methods that must be implemented on the relay header type. + /// Do not implement this yourself - rather, rely on the `impl_odp_mctp_relay_handler` macro to implement this. + #[doc(hidden)] + pub trait RelayHeader { + /// Return the ID of the service associated with the request + fn get_service_id(&self) -> ServiceIdType; + } + + /// Contains additional methods that must be implemented on the relay response type. + /// Do not implement this yourself - rather, rely on the `impl_odp_mctp_relay_handler` macro to implement this. + #[doc(hidden)] + pub trait RelayResponse { + /// Construct an MCTP header suitable for representing the result based on the provided service handler ID and result + fn create_header(&self, service_id: &ServiceIdType) -> HeaderType; + } + + /// Trait for aggregating collections of services that can be relayed over an external bus. + /// Do not implement this yourself - rather, rely on the `impl_odp_mctp_relay_handler` macro to implement this. + /// + pub trait RelayHandler { + /// The type that uniquely identifies individual services. Generally expected to be a C-style enum. + type ServiceIdType: Into + TryFrom + Copy; + + /// The header type used by request and result enums + type HeaderType: mctp_rs::MctpMessageHeaderTrait + RelayHeader; + + /// An enum over all possible request types + type RequestEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType>; + + /// An enum over all possible result types + type ResultEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType> + + RelayResponse; + + /// Process the provided request and yield a result. + fn process_request<'a>( + &'a self, + message: Self::RequestEnumType, + ) -> impl core::future::Future + Send + 'a; + } + + /// This macro generates a relay type over a collection of message types, which can be used by a relay service to + /// receive messages over the wire and translate them into calls to a particular service on the EC. + /// + /// This is the recommended way to implement a relay handler - you should not implement the RelayHandler trait yourself. + /// + /// This macro will emit a type with the name you specify that is generic over a lifetime for the hardware (probably 'static in production code), + /// implements the `RelayHandler` trait, and has a single constructor method `new` that takes as arguments references to the service handler + /// types that you specify that have the 'hardware lifetime'. + /// + /// The macro takes the following inputs once: + /// relay_type_name: The name of the relay type to generate. This is arbitrary. The macro will emit a type with this name. + /// + /// Followed by a list of any number of service entries, which are specified by the following inputs: + /// service_name: A name to assign to generated identifiers associated with the service, e.g. "Battery". + /// This can be arbitrary. + /// service_id: A unique u8 that addresses that service on the EC. + /// service_handler_type: A type that implements the RelayServiceHandler trait, which will be used to process messages + /// for this service. + /// + /// Example usage: + /// + /// ```ignore + /// + /// impl_odp_mctp_relay_handler!( + /// MyRelayHanderType; + /// Battery, 0x9, battery_service::Service<'static>; + /// TimeAlarm, 0xB, time_alarm_service::Service<'static>; + /// ); + /// + /// let relay_handler = MyRelayHandlerType::new(battery_service_instance, time_alarm_service_instance); + /// + /// // Then, pass relay_handler to your relay service (e.g. eSPI service), which should be generic over an `impl RelayHandler`. + /// + /// ``` + /// + #[macro_export] + macro_rules! impl_odp_mctp_relay_handler { + ( + $relay_type_name:ident; + $( + $service_name:ident, + $service_id:expr, + $service_handler_type:ty; + )+ + ) => { + $crate::_macro_internal::paste::paste! { + mod [< _odp_impl_ $relay_type_name:snake >] { + use $crate::_macro_internal::bitfield::bitfield; + use core::convert::Infallible; + use $crate::_macro_internal::mctp_rs::smbus_espi::SmbusEspiMedium; + use $crate::_macro_internal::mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; + use $crate::relay::{SerializableMessage, SerializableResult}; + use $crate::relay::mctp::RelayServiceHandler; + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[repr(u8)] + pub enum OdpService { + $( + $service_name = $service_id, + )+ + } + + impl From for u8 { + fn from(val: OdpService) -> u8 { + val as u8 + } + } + + impl TryFrom for OdpService { + type Error = u8; + fn try_from(value: u8) -> Result { + match value { + $( + $service_id => Ok(OdpService::$service_name), + )+ + other => Err(other), + } + } + } + + pub enum HostRequest { + $( + $service_name(<$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::RequestType), + )+ + } + + impl MctpMessageTrait<'_> for HostRequest { + type Header = OdpHeader; + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) + .map_err(|_| MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + Ok(match header.service { + $( + OdpService::$service_name => Self::$service_name( + <$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::RequestType::deserialize(header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, + ), + )+ + }) + } + } + + bitfield! { + /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. + #[derive(Copy, Clone, PartialEq, Eq)] + struct OdpHeaderWireFormat(u32); + impl Debug; + impl new; + /// If true, represents a request; otherwise, represents a result + is_request, set_is_request: 25; + + /// The service ID that this message is related to + /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. + u8, service_id, set_service_id: 23, 16; + + /// On results, indicates if the result message is an error. Unused on requests. + is_error, set_is_error: 15; + + /// The message type/discriminant + u16, message_id, set_message_id: 14, 0; + } + + #[derive(Copy, Clone, PartialEq, Eq)] + pub enum OdpMessageType { + Request, + Result { is_error: bool }, + } + + #[derive(Copy, Clone, PartialEq, Eq)] + pub struct OdpHeader { + pub message_type: OdpMessageType, + pub service: OdpService, + pub message_id: u16, + } + + impl From for OdpHeaderWireFormat { + fn from(src: OdpHeader) -> Self { + Self::new( + matches!(src.message_type, OdpMessageType::Request), + src.service.into(), + match src.message_type { + OdpMessageType::Request => false, // unused on requests + OdpMessageType::Result { is_error } => is_error, + }, + src.message_id, + ) + } + } + + impl TryFrom for OdpHeader { + type Error = MctpPacketError; + + fn try_from(src: OdpHeaderWireFormat) -> Result { + let service = OdpService::try_from(src.service_id()) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; + + let message_type = if src.is_request() { + OdpMessageType::Request + } else { + OdpMessageType::Result { + is_error: src.is_error(), + } + }; + + Ok(OdpHeader { + message_type, + service, + message_id: src.message_id(), + }) + } + } + + impl MctpMessageHeaderTrait for OdpHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let wire_format = OdpHeaderWireFormat::from(self); + let bytes = wire_format.0.to_be_bytes(); + buffer + .get_mut(0..bytes.len()) + .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? + .copy_from_slice(&bytes); + + Ok(bytes.len()) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + let bytes = buffer + .get(0..core::mem::size_of::()) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; + let raw = u32::from_be_bytes( + bytes + .try_into() + .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + ); + + let parsed_wire_format = OdpHeaderWireFormat(raw); + let header = OdpHeader::try_from(parsed_wire_format) + .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; + + Ok(( + header, + buffer + .get(core::mem::size_of::()..) + .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, + )) + } + } + + impl $crate::relay::mctp::RelayHeader for OdpHeader { + fn get_service_id(&self) -> OdpService { + self.service + } + } + + #[derive(Clone)] + pub enum HostResult { + $( + $service_name(<$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::ResultType), + )+ + } + + impl $crate::relay::mctp::RelayResponse for HostResult { + fn create_header(&self, service_id: &OdpService) -> OdpHeader { + match (self) { + $( + (HostResult::$service_name(result)) => OdpHeader { + message_type: OdpMessageType::Result { is_error: !result.is_ok() }, + service: *service_id, + message_id: result.discriminant(), + }, + )+ + } + } + } + + impl MctpMessageTrait<'_> for HostResult { + const MESSAGE_TYPE: u8 = 0x7D; // ODP message type + type Header = OdpHeader; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + $( + HostResult::$service_name(result) => result + .serialize(buffer) + .map_err(|_| MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), + )+ + } + } + + fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { + match header.service { + $( + OdpService::$service_name => { + match header.message_type { + OdpMessageType::Request => { + Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) + } + OdpMessageType::Result { is_error } => { + Ok(HostResult::$service_name(<$service_handler_type as $crate::relay::mctp::RelayServiceHandlerTypes>::ResultType::deserialize(is_error, header.message_id, buffer) + .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) + } + } + }, + )+ + } + } + } + + + pub struct $relay_type_name<'hw> { + $( + [<$service_name:snake _handler>]: &'hw $service_handler_type, + )+ + } + + impl<'hw> $relay_type_name<'hw> { + pub fn new( + $( + [<$service_name:snake _handler>]: &'hw $service_handler_type, + )+ + ) -> Self { + Self { + $( + [<$service_name:snake _handler>], + )+ + } + } + } + + impl<'hw> $crate::relay::mctp::RelayHandler for $relay_type_name<'hw> { + type ServiceIdType = OdpService; + type HeaderType = OdpHeader; + type RequestEnumType = HostRequest; + type ResultEnumType = HostResult; + + fn process_request<'a>( + &'a self, + message: HostRequest, + ) -> impl core::future::Future + Send + 'a { + async move { + match message { + $( + HostRequest::$service_name(request) => { + let result = self.[<$service_name:snake _handler>].process_request(request).await; + HostResult::$service_name(result) + } + )+ + } + } + } + } + } // end mod __odp_impl + + use [< _odp_impl_ $relay_type_name:snake >]::$relay_type_name; + + } // end paste! + }; // end macro arm + } // end macro + + pub use impl_odp_mctp_relay_handler; +} diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 79b2ac03f..479059e94 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -1,12 +1,12 @@ use core::slice; use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService}; -use core::borrow::BorrowMut; +use embassy_futures::select::select; use embassy_imxrt::espi; use embassy_sync::channel::Channel; +use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embedded_services::buffer::OwnedRef; -use embedded_services::comms::{self, EndpointID, External}; +use embedded_services::comms as DEPRECATED_comms; use embedded_services::{GlobalRawMutex, debug, error, info, trace}; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -20,15 +20,29 @@ const OOB_PORT_ID: usize = 1; // Should be as large as the largest possible MCTP packet and its metadata. const ASSEMBLY_BUF_SIZE: usize = 256; -embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; ASSEMBLY_BUF_SIZE]); - #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct HostResultMessage { - pub source_endpoint: EndpointID, +struct LegacyHostResultMessage { + pub source_endpoint: DEPRECATED_comms::EndpointID, pub message: HostResult, } +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct HostResultMessage { + pub handler_service_id: RelayHandler::ServiceIdType, + pub message: RelayHandler::ResultEnumType, +} + +// TODO While we're migrating from the comms service to direct async calls, we need to support both the old and new message types, +// so we use this enum to route them to the correct processing function. Once migration is complete, we should remove this and +// just use RelayHandler::ResultEnumType everywhere. +// +enum HostResultMessageMigrationEnum { + Legacy(LegacyHostResultMessage), + New(HostResultMessage), +} + #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { @@ -36,24 +50,74 @@ pub enum Error { Buffer(embedded_services::buffer::Error), } -pub struct Service<'a> { - endpoint: comms::Endpoint, - host_tx_queue: Channel, - assembly_buf_owned_ref: OwnedRef<'a, u8>, +pub struct Service { + endpoint: DEPRECATED_comms::Endpoint, + espi: Mutex>, + host_tx_queue: Channel, HOST_TX_QUEUE_SIZE>, + relay_handler: RelayHandler, } -impl Service<'_> { - #[allow(clippy::new_without_default)] // When we break the dependency on embassy-imxrt, we'll need to take an eSPI trait implementation as an argument, so it doesn't make sense to provide a default implementation - pub fn new() -> Self { - Service { - endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), +// TODO we're currently transitioning from the comms service to direct async calls to support better testing, reduced code size, and better performance. +// As part of this transition, each service that interacts with the eSPI service needs to migrate to expose a direct async call API and implement +// some additional traits to be able to interface with relay services in the new way. +// +// Until all services have been migrated, we need to support both the old and new methods for interfacing with services. Once migration is complete, +// we can remove all the legacy code that supports the old comms service method of interfacing with the eSPI service. +// +// To ease this transition, we've split this module into four impl blocks - "common", "new", "legacy", and "routing". +// Common code should be largely unimpacted by this transition. The "new" and "legacy" impl blocks provide two different implementations of the same +// core functionality, one for the new direct async call method and one for the old comms service method. The "routing" impl block contains code that +// is responsible for routing messages to the correct implementation based on the type of the incoming message. When migration is complete, all "legacy" +// and "routing" functions should be removed and the "new" functions should be renamed to drop the "new" and migrated to the "common" block. +// +// This approach leads to a bit more code duplication than we'd like during the transition, but should make the final migration a lot simpler and +// less error-prone, and the transition should be relatively brief. +// + +///////// COMMON FUNCTIONS /////////// +impl Service { + // TODO a lot of the input lifetimes here have to be static because we have a dependency on the comms system, which requires + // that everything that talks over it is 'static. Once we eliminate that dependency, we should be able to relax these lifetimes. + pub async fn init( + service_storage: &'static OnceLock, + mut espi: espi::Espi<'static>, + relay_handler: RelayHandler, + ) -> &'static Self { + espi.wait_for_plat_reset().await; + + let result = service_storage.get_or_init(|| Service { + endpoint: DEPRECATED_comms::Endpoint::uninit(DEPRECATED_comms::EndpointID::External( + DEPRECATED_comms::External::Host, + )), + espi: Mutex::new(espi), host_tx_queue: Channel::new(), - assembly_buf_owned_ref: assembly_buf::get_mut().unwrap(), - } + relay_handler, + }); + + DEPRECATED_comms::register_endpoint(result, &result.endpoint) + .await + .unwrap(); + result } - pub(crate) async fn wait_for_response(&self) -> HostResultMessage { - self.host_tx_queue.receive().await + pub(crate) async fn run_service(&self) -> ! { + let mut espi = self.espi.lock().await; + loop { + let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await; + + match event { + embassy_futures::select::Either::First(controller_event) => { + self.process_controller_event(&mut espi, controller_event) + .await + .unwrap_or_else(|e| { + error!("Critical error processing eSPI controller event: {:?}", e); + }); + } + embassy_futures::select::Either::Second(host_msg) => { + self.process_response_to_host_routing(&mut espi, host_msg).await + } + } + } } // TODO The notification system was not actually used, so this is currently dead code. @@ -64,20 +128,192 @@ impl Service<'_> { // info!("espi: Notification id {} sent to Host!", notification.offset); // } - async fn serialize_packet_from_subsystem( + fn write_to_hw(&self, espi: &mut espi::Espi<'static>, packet: &[u8]) -> Result<(), embassy_imxrt::espi::Error> { + // Send packet via your transport medium + // SAFETY: Safe as the access to espi is protected by a mut reference. + let dest_slice = unsafe { espi.oob_get_write_buffer(OOB_PORT_ID)? }; + dest_slice[..packet.len()].copy_from_slice(&packet[..packet.len()]); + + // Write response over OOB + espi.oob_write_data(OOB_PORT_ID, packet.len() as u8) + } + + async fn process_controller_event( &self, espi: &mut espi::Espi<'static>, - result: &HostResultMessage, + event: Result, ) -> Result<(), Error> { - let mut assembly_buf_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; - let pkt_ctx_buf = assembly_buf_access.borrow_mut(); - let mut mctp_ctx = mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, pkt_ctx_buf); + match event { + Ok(espi::Event::PeripheralEvent(port_event)) => { + info!( + "eSPI PeripheralEvent Port: {}, direction: {}, address: {}, offset: {}, length: {}", + port_event.port, port_event.direction, port_event.offset, port_event.base_addr, port_event.length, + ); - let source_service: OdpService = OdpService::try_from(result.source_endpoint).map_err(|_| Error::Serialize)?; + // We're not handling these - communication is all through OOB + + espi.complete_port(port_event.port); + } + Ok(espi::Event::OOBEvent(port_event)) => { + info!( + "eSPI OOBEvent Port: {}, direction: {}, address: {}, offset: {}, length: {}", + port_event.port, port_event.direction, port_event.offset, port_event.base_addr, port_event.length, + ); + + if port_event.direction { + let src_slice = + unsafe { slice::from_raw_parts(port_event.base_addr as *const u8, port_event.length) }; + + // TODO: This is a workaround because mctp_rs expects a PEC byte, so we hardcode a 0 at the end. + // We should add functionality to mctp_rs to disable PEC. + let mut with_pec = [0u8; 100]; + with_pec[..src_slice.len()].copy_from_slice(src_slice); + with_pec[src_slice.len()] = 0; + let with_pec = &with_pec[..=src_slice.len()]; + + #[cfg(feature = "defmt")] // Required because without defmt, there is no implementation of UpperHex for [u8] + debug!("OOB message: {:02X}", &src_slice[0..]); + + let mut assembly_buf = [0u8; ASSEMBLY_BUF_SIZE]; + let mut mctp_ctx = mctp_rs::MctpPacketContext::::new( + SmbusEspiMedium, + assembly_buf.as_mut_slice(), + ); + + match mctp_ctx.deserialize_packet(with_pec) { + Ok(Some(message)) => { + trace!("MCTP packet successfully deserialized"); + self.process_request_to_ec_routing(espi, &message, &port_event).await?; + } + Ok(None) => { + // Partial message, waiting for more packets + error!("Partial msg, should not happen"); + espi.complete_port(OOB_PORT_ID); + + return Err(Error::Serialize); + } + Err(_e) => { + // Handle protocol or medium error + error!("MCTP packet malformed"); + + error!("error code: {:?}", _e); + espi.complete_port(OOB_PORT_ID); + + return Err(Error::Serialize); + } + } + } else { + espi.complete_port(port_event.port); + } + } + Ok(espi::Event::Port80) => { + info!("eSPI Port 80"); + } + Ok(espi::Event::WireChange(_)) => { + info!("eSPI WireChange"); + } + Err(e) => { + error!("eSPI Failed with error: {:?}", e); + } + } + Ok(()) + } +} + +/////////// ROUTING FUNCTIONS /////////// +impl Service { + async fn process_response_to_host_routing( + &self, + espi: &mut espi::Espi<'static>, + response: HostResultMessageMigrationEnum, + ) { + match response { + HostResultMessageMigrationEnum::Legacy(legacy_msg) => { + self.process_response_to_host_legacy(espi, legacy_msg).await + } + HostResultMessageMigrationEnum::New(new_msg) => self.process_response_to_host_new(espi, new_msg).await, + } + } + + async fn process_request_to_ec_routing( + &self, + espi: &mut espi::Espi<'static>, + message: &mctp_rs::MctpMessage<'_, mctp_rs::smbus_espi::SmbusEspiMedium>, + port_event: &espi::PortEvent, + ) -> Result<(), Error> { + if let Ok((header, body)) = message.parse_as::() { + self.process_request_to_ec_legacy((header, body), espi, port_event) + .await + } else { + match message.parse_as::() { + Ok((header, body)) => self.process_request_to_ec_new((header, body), espi, port_event).await, + Err(e) => { + error!("MCTP ODP type malformed: {:?}", e); + espi.complete_port(port_event.port); + Err(Error::Serialize) + } + } + } + } +} + +///////////// NEW FUNCTIONS ///////////// +impl Service { + async fn process_request_to_ec_new( + &self, + (header, body): ( + >::Header, + RelayHandler::RequestEnumType, + ), + espi: &mut espi::Espi<'static>, + port_event: &espi::PortEvent, + ) -> Result<(), Error> { + use embedded_services::relay::mctp::RelayHeader; + info!("Host Request received"); + + espi.complete_port(port_event.port); + + let response = self.relay_handler.process_request(body).await; + self.host_tx_queue + .try_send(HostResultMessageMigrationEnum::New(HostResultMessage { + handler_service_id: header.get_service_id(), + message: response, + })) + .map_err(|_| Error::Serialize)?; + + Ok(()) + } + + async fn process_response_to_host_new( + &self, + espi: &mut espi::Espi<'static>, + response: HostResultMessage, + ) { + match self.serialize_packet_from_subsystem_new(espi, response).await { + Ok(()) => { + trace!("Full packet successfully sent to host!") + } + Err(e) => { + // TODO we may want to consider sending a failure message to the debug service or something, but that'll require + // a 'facility of last resort' on the relay handler, so for now we just log the error + error!("Packet serialize error {:?}", e); + } + } + } + + async fn serialize_packet_from_subsystem_new( + &self, + espi: &mut espi::Espi<'static>, + result: HostResultMessage, + ) -> Result<(), Error> { + use embedded_services::relay::mctp::RelayResponse; + let mut assembly_buf = [0u8; ASSEMBLY_BUF_SIZE]; + let mut mctp_ctx = + mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, assembly_buf.as_mut_slice()); let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { source_endpoint_id: mctp_rs::EndpointId::Id(0x80), - destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), // TODO We're currently using this incorrectly - it should be the bus address of the host. Revisit once we have assigned a bus address to the host. + destination_endpoint_id: mctp_rs::EndpointId::Id(result.handler_service_id.into()), // TODO We're currently using this incorrectly - it should be the bus address of the host. Revisit once we have assigned a bus address to the host. packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); @@ -89,17 +325,9 @@ impl Service<'_> { }, // Medium-specific context }; - let header = OdpHeader { - message_type: OdpMessageType::Result { - is_error: !result.message.is_ok(), - }, - is_datagram: false, - service: source_service, - message_id: result.message.discriminant(), - }; - + let header = result.message.create_header(&result.handler_service_id); let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, result.message.clone())) + .serialize_packet(reply_context, (header, result.message)) .map_err(|e| { error!("serialize_packet_from_subsystem: {:?}", e); Error::Serialize @@ -121,180 +349,141 @@ impl Service<'_> { // Immediately service the packet with the ESPI HAL let event = espi.wait_for_event().await; - process_controller_event(espi, self, event).await?; + self.process_controller_event(espi, event).await?; } Ok(()) } +} - fn write_to_hw(&self, espi: &mut espi::Espi<'static>, packet: &[u8]) -> Result<(), embassy_imxrt::espi::Error> { - // Send packet via your transport medium - // SAFETY: Safe as the access to espi is protected by a mut reference. - let dest_slice = unsafe { espi.oob_get_write_buffer(OOB_PORT_ID)? }; - dest_slice[..packet.len()].copy_from_slice(&packet[..packet.len()]); +//////////// LEGACY FUNCTIONS /////////// +impl Service { + async fn process_request_to_ec_legacy( + &self, + (header, body): (OdpHeader, HostRequest), + espi: &mut espi::Espi<'static>, + port_event: &espi::PortEvent, + ) -> Result<(), Error> { + let target_endpoint = header.service.get_endpoint_id(); + trace!( + "Host Request: Service {:?}, Command {:?}", + target_endpoint, header.message_id, + ); + + espi.complete_port(port_event.port); + body.send_to_endpoint(&self.endpoint, target_endpoint) + .await + .expect("result error type is infallible"); + info!("MCTP packet forwarded to service: {:?}", target_endpoint); - // Write response over OOB - espi.oob_write_data(OOB_PORT_ID, packet.len() as u8) + Ok(()) } - async fn send_mctp_error_response(&self, endpoint: EndpointID, espi: &mut espi::Espi<'static>) { - // TODO we may want to add more detail in future, but that will require more integration with the debug service - let error_msg = HostResultMessage { + async fn process_response_to_host_legacy(&self, espi: &mut espi::Espi<'static>, response: LegacyHostResultMessage) { + let source_endpoint = response.source_endpoint; + match self.serialize_packet_from_subsystem_legacy(espi, response).await { + Ok(()) => { + trace!("Full packet successfully sent to host!") + } + Err(e) => { + error!("Packet serialize error {:?}", e); + + self.send_mctp_error_response_legacy(source_endpoint, espi).await; + } + } + } + + async fn send_mctp_error_response_legacy( + &self, + endpoint: DEPRECATED_comms::EndpointID, + espi: &mut espi::Espi<'static>, + ) { + let error_msg = LegacyHostResultMessage { source_endpoint: endpoint, message: HostResult::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), }; - self.serialize_packet_from_subsystem(espi, &error_msg) + self.serialize_packet_from_subsystem_legacy(espi, error_msg) .await .unwrap_or_else(|_| { error!("Critical error reporting MCTP protocol error to host!"); }); } - pub(crate) async fn process_response_to_host(&self, espi: &mut espi::Espi<'static>, response: HostResultMessage) { - match self.serialize_packet_from_subsystem(espi, &response).await { - Err(e) => { - error!("Packet serialize error {:?}", e); + async fn serialize_packet_from_subsystem_legacy( + &self, + espi: &mut espi::Espi<'static>, + result: LegacyHostResultMessage, + ) -> Result<(), Error> { + let mut assembly_buf = [0u8; ASSEMBLY_BUF_SIZE]; + let mut mctp_ctx = + mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, assembly_buf.as_mut_slice()); - self.send_mctp_error_response(response.source_endpoint, espi).await; - } - Ok(()) => { - trace!("Full packet successfully sent to host!") - } - } - } + let source_service: OdpService = OdpService::try_from(result.source_endpoint).map_err(|_| Error::Serialize)?; - pub(crate) fn endpoint(&self) -> &comms::Endpoint { - &self.endpoint - } + let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { + source_endpoint_id: mctp_rs::EndpointId::Id(0x80), + destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), // TODO We're currently using this incorrectly - it should be the bus address of the host. Revisit once we have assigned a bus address to the host. + packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), + message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(|e| { + error!("serialize_packet_from_subsystem_legacy: {:?}", e); + Error::Serialize + })?, + medium_context: SmbusEspiReplyContext { + destination_slave_address: 1, + source_slave_address: 0, + }, // Medium-specific context + }; - fn queue_response_to_host( - &self, - source_endpoint: EndpointID, - message: HostResult, - ) -> Result<(), comms::MailboxDelegateError> { - debug!("Espi service: recvd response"); - self.host_tx_queue - .try_send(HostResultMessage { - source_endpoint, - message, - }) - .map_err(|_| comms::MailboxDelegateError::BufferFull)?; + let header = OdpHeader { + message_type: OdpMessageType::Result { + is_error: !result.message.is_ok(), + }, + is_datagram: false, + service: source_service, + message_id: result.message.discriminant(), + }; + + let mut packet_state = mctp_ctx + .serialize_packet(reply_context, (header, result.message)) + .map_err(|e| { + error!("serialize_packet_from_subsystem_legacy: {:?}", e); + Error::Serialize + })?; + // Send each packet + while let Some(packet_result) = packet_state.next() { + let packet = packet_result.map_err(|e| { + error!("serialize_packet_from_subsystem_legacy: {:?}", e); + Error::Serialize + })?; + // Last byte is PEC, ignore for now + let packet = &packet[..packet.len() - 1]; + trace!("Sending MCTP response: {:?}", packet); + + self.write_to_hw(espi, packet).map_err(|e| { + error!("serialize_packet_from_subsystem_legacy: {:?}", e); + Error::Serialize + })?; + // Immediately service the packet with the ESPI HAL + let event = espi.wait_for_event().await; + self.process_controller_event(espi, event).await?; + } Ok(()) } } -impl comms::MailboxDelegate for Service<'_> { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { +// TODO this impl is also a 'legacy function' and needs to go away when we no longer have services communicating with the eSPI service via the comms service +impl DEPRECATED_comms::MailboxDelegate + for Service +{ + fn receive(&self, message: &DEPRECATED_comms::Message) -> Result<(), DEPRECATED_comms::MailboxDelegateError> { crate::mctp::send_to_comms(message, |source_endpoint, message| { - self.queue_response_to_host(source_endpoint, message) + debug!("Espi service: recvd response"); + self.host_tx_queue + .try_send(HostResultMessageMigrationEnum::Legacy(LegacyHostResultMessage { + source_endpoint, + message, + })) + .map_err(|_| DEPRECATED_comms::MailboxDelegateError::BufferFull) }) } } - -pub(crate) static ESPI_SERVICE: OnceLock = OnceLock::new(); - -pub(crate) async fn process_controller_event( - espi: &mut espi::Espi<'static>, - espi_service: &Service<'_>, - event: Result, -) -> Result<(), Error> { - match event { - Ok(espi::Event::PeripheralEvent(port_event)) => { - info!( - "eSPI PeripheralEvent Port: {}, direction: {}, address: {}, offset: {}, length: {}", - port_event.port, port_event.direction, port_event.offset, port_event.base_addr, port_event.length, - ); - - // We're not handling these - communication is all through OOB - - espi.complete_port(port_event.port); - } - Ok(espi::Event::OOBEvent(port_event)) => { - info!( - "eSPI OOBEvent Port: {}, direction: {}, address: {}, offset: {}, length: {}", - port_event.port, port_event.direction, port_event.offset, port_event.base_addr, port_event.length, - ); - - if port_event.direction { - let src_slice = unsafe { slice::from_raw_parts(port_event.base_addr as *const u8, port_event.length) }; - - // TODO: This is a workaround because mctp_rs expects a PEC byte, so we hardcode a 0 at the end. - // We should add functionality to mctp_rs to disable PEC. - let mut with_pec = [0u8; 100]; - with_pec[..src_slice.len()].copy_from_slice(src_slice); - with_pec[src_slice.len()] = 0; - let with_pec = &with_pec[..=src_slice.len()]; - - #[cfg(feature = "defmt")] // Required because without defmt, there is no implementation of UpperHex for [u8] - debug!("OOB message: {:02X}", &src_slice[0..]); - - let mut assembly_access = espi_service - .assembly_buf_owned_ref - .borrow_mut() - .map_err(Error::Buffer)?; - let mut mctp_ctx = - mctp_rs::MctpPacketContext::::new(SmbusEspiMedium, assembly_access.borrow_mut()); - - match mctp_ctx.deserialize_packet(with_pec) { - Ok(Some(message)) => { - trace!("MCTP packet successfully deserialized"); - - match message.parse_as::() { - Ok((header, body)) => { - let target_endpoint = header.service.get_endpoint_id(); - trace!( - "Host Request: Service {:?}, Command {:?}", - target_endpoint, header.message_id, - ); - - drop(assembly_access); - - espi.complete_port(port_event.port); - - body.send_to_endpoint(&espi_service.endpoint, target_endpoint) - .await - .expect("result error type is infallible"); - info!("MCTP packet forwarded to service: {:?}", target_endpoint); - } - Err(e) => { - error!("MCTP ODP type malformed: {:?}", e); - - espi.complete_port(port_event.port); - - return Err(Error::Serialize); - } - } - } - Ok(None) => { - // Partial message, waiting for more packets - error!("Partial msg, should not happen"); - espi.complete_port(OOB_PORT_ID); - - return Err(Error::Serialize); - } - Err(_e) => { - // Handle protocol or medium error - error!("MCTP packet malformed"); - - error!("error code: {:?}", _e); - espi.complete_port(OOB_PORT_ID); - - return Err(Error::Serialize); - } - } - } else { - espi.complete_port(port_event.port); - } - } - Ok(espi::Event::Port80) => { - info!("eSPI Port 80"); - } - Ok(espi::Event::WireChange(_)) => { - info!("eSPI WireChange"); - } - Err(e) => { - error!("eSPI Failed with error: {:?}", e); - } - } - Ok(()) -} diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs index 33778deb5..41e05bcaa 100644 --- a/espi-service/src/mctp.rs +++ b/espi-service/src/mctp.rs @@ -1,17 +1,27 @@ use embedded_services::{ comms, - relay::{SerializableMessage, SerializableResult, mctp::impl_odp_mctp_relay_types}, + relay::{SerializableMessage, SerializableResult}, }; -// TODO We'd ideally like these types to be passed in as a generic or something when the eSPI service is instantiated -// so the eSPI service can be extended to handle 3rd party message types without needing to fork the eSPI service, -// but that's dependant on us migrating to have storage for the eSPI service be allocated by the caller of init() -// rather than statically allocated inside this module, so for now we accept this hardcoded list of supported message -// types. - -impl_odp_mctp_relay_types!( - Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; - TimeAlarm, 0x0B, (comms::EndpointID::Internal(comms::Internal::TimeAlarm)), time_alarm_service_messages::AcpiTimeAlarmRequest, time_alarm_service_messages::AcpiTimeAlarmResult; -); +// TODO we're currently transitioning from the comms service to direct async calls to support better testing, reduced code size, and better performance. +// As part of this transition, each service that interacts with the eSPI service needs to migrate to expose a direct async call API and implement +// some additional traits to be able to interface with relay services in the new way. +// +// Until all services have been migrated, we need to support both the old and new methods for interfacing with services. Once migration is complete, +// we can remove all the legacy code that supports the old comms service method of interfacing with the eSPI service. +// These are the ones that have not been migrated to using the new method. When a service is migrated, remove it here and pass it into +// the new impl macro from the 'application' layer. +// +// You should not add any new types here. Instead, implement the embedded_services::relay::mctp::RelayServiceHandler trait for your service. +// +#[allow(deprecated)] +mod legacy_relay { + use super::*; + use embedded_services::relay::mctp::impl_odp_mctp_relay_types; + impl_odp_mctp_relay_types!( + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + ); +} +pub(crate) use legacy_relay::*; diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs index 458302593..ff7f7fe8f 100644 --- a/espi-service/src/task.rs +++ b/espi-service/src/task.rs @@ -1,29 +1,12 @@ -use embassy_futures::select::select; -use embassy_imxrt::espi; -use embedded_services::comms; - -use crate::{ESPI_SERVICE, Service, process_controller_event}; - -pub async fn espi_service( - mut espi: espi::Espi<'static>, +use crate::Service; + +// TODO: We currently require that the service has lifetime 'static because we still communicate with +// some services over the legacy comms system, which requires that things that interact with it +// have lifetime 'static. Once we've fully transitioned to the direct async call method of interfacing +// between services, we should be able to relax this requirement to just require that the service has +// the same lifetime as the services it's communicating with. +pub async fn espi_service( + espi_service: &'static Service, ) -> Result { - espi.wait_for_plat_reset().await; - - let espi_service = ESPI_SERVICE.get_or_init(Service::new); - comms::register_endpoint(espi_service, espi_service.endpoint()) - .await - .unwrap(); - - loop { - let event = select(espi.wait_for_event(), espi_service.wait_for_response()).await; - - match event { - embassy_futures::select::Either::First(controller_event) => { - process_controller_event(&mut espi, espi_service, controller_event).await? - } - embassy_futures::select::Either::Second(host_msg) => { - espi_service.process_response_to_host(&mut espi, host_msg).await - } - } - } + espi_service.run_service().await } diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index c8aa49b95..dc595eb54 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -638,6 +638,7 @@ dependencies = [ "log", "mctp-rs", "num_enum", + "paste", "portable-atomic", "serde", "uuid", @@ -1180,6 +1181,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.2" diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 36196f2b8..4fb0c9dd6 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -786,6 +786,7 @@ dependencies = [ "heapless", "mctp-rs", "num_enum", + "paste", "portable-atomic", "serde", "uuid", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 12c8dc171..333705b88 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -744,6 +744,7 @@ dependencies = [ "heapless", "mctp-rs", "num_enum", + "paste", "portable-atomic", "serde", "uuid", diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 5f4d3d305..9b1a8182a 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -2,66 +2,15 @@ #![no_main] use embassy_sync::once_lock::OnceLock; -use embedded_mcu_hal::Nvram; -use embedded_services::{error, info}; +use embedded_mcu_hal::{ + Nvram, + time::{Datetime, Month, UncheckedDatetime}, +}; +use embedded_services::info; use static_cell::StaticCell; +use time_alarm_service_messages::{AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimeZoneOffset, AcpiTimestamp}; use {defmt_rtt as _, panic_probe as _}; -mod mock_espi_service { - - use crate::OnceLock; - use crate::{error, info}; - use embassy_time::{Duration, Ticker}; - use embedded_services::comms::{self, EndpointID, External, Internal}; - use time_alarm_service_messages::{AcpiTimeAlarmRequest, AcpiTimeAlarmResult}; - - pub struct Service { - endpoint: comms::Endpoint, - } - - impl Service { - pub async fn init(spawner: embassy_executor::Spawner, service_storage: &'static OnceLock) { - let instance = service_storage.get_or_init(|| Service { - endpoint: comms::Endpoint::uninit(EndpointID::External(External::Host)), - }); - - comms::register_endpoint(instance, &instance.endpoint).await.unwrap(); - - spawner.must_spawn(run_mock_service(instance)); - } - } - - impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - let msg = message.data.get::().ok_or_else(|| { - error!("Mock eSPI service received unknown message type"); - comms::MailboxDelegateError::MessageNotFound - })?; - - info!("Mock eSPI service received ACPI Time Alarm Response: {:?}", msg); - - Ok(()) - } - } - - #[embassy_executor::task] - async fn run_mock_service(espi_service: &'static Service) { - let mut ticker = Ticker::every(Duration::from_secs(1)); - - loop { - ticker.next().await; - espi_service - .endpoint - .send( - EndpointID::Internal(Internal::TimeAlarm), - &AcpiTimeAlarmRequest::GetRealTime, - ) - .await - .unwrap(); - } - } -} - #[embassy_executor::main] async fn main(spawner: embassy_executor::Spawner) { let p = embassy_imxrt::init(Default::default()); @@ -75,11 +24,7 @@ async fn main(spawner: embassy_executor::Spawner) { embedded_services::init().await; info!("services initialized"); - static MOCK_ESPI_SERVICE: OnceLock = OnceLock::new(); - mock_espi_service::Service::init(spawner, &MOCK_ESPI_SERVICE).await; - - static TIME_SERVICE: embassy_sync::once_lock::OnceLock = - embassy_sync::once_lock::OnceLock::new(); + static TIME_SERVICE: OnceLock = OnceLock::new(); let time_service = time_alarm_service::Service::init( &TIME_SERVICE, dt_clock, @@ -93,21 +38,43 @@ async fn main(spawner: embassy_executor::Spawner) { .expect("Failed to initialize time-alarm service"); #[embassy_executor::task] - async fn command_handler_task(service: &'static time_alarm_service::Service) { - time_alarm_service::task::command_handler_task(service).await + async fn time_alarm_task(service: &'static time_alarm_service::Service<'static>) { + time_alarm_service::task::run_service(service).await } - #[embassy_executor::task] - async fn ac_timer_task(service: &'static time_alarm_service::Service) { - time_alarm_service::task::ac_timer_task(service).await + spawner.must_spawn(time_alarm_task(time_service)); + + use embedded_services::relay::mctp::impl_odp_mctp_relay_handler; + impl_odp_mctp_relay_handler!( + EspiRelayHandler; + TimeAlarm, 0x0B, time_alarm_service::Service<'static>; + ); + + let _relay_handler = EspiRelayHandler::new(time_service); + + // Here, you'd normally pass _relay_handler to your relay service (e.g. eSPI service). + // In this example, we're not leveraging a relay service, so we'll just demonstrate some direct calls. + // + + time_service + .set_real_time(AcpiTimestamp { + datetime: Datetime::new(UncheckedDatetime { + year: 2024, + month: Month::January, + day: 10, + hour: 12, + minute: 0, + second: 0, + nanosecond: 0, + }) + .unwrap(), + time_zone: AcpiTimeZone::MinutesFromUtc(AcpiTimeZoneOffset::new(60 * -8).unwrap()), + dst_status: AcpiDaylightSavingsTimeStatus::NotAdjusted, + }) + .unwrap(); + + loop { + embassy_time::Timer::after(embassy_time::Duration::from_secs(10)).await; + info!("Current time from service: {:?}", time_service.get_real_time().unwrap()); } - - #[embassy_executor::task] - async fn dc_timer_task(service: &'static time_alarm_service::Service) { - time_alarm_service::task::dc_timer_task(service).await - } - - spawner.must_spawn(command_handler_task(time_service)); - spawner.must_spawn(ac_timer_task(time_service)); - spawner.must_spawn(dc_timer_task(time_service)); } diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 63e2911ec..f2659ecf2 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -806,6 +806,7 @@ dependencies = [ "log", "mctp-rs", "num_enum", + "paste", "portable-atomic", "serde", "uuid", diff --git a/time-alarm-service-messages/src/acpi_timestamp.rs b/time-alarm-service-messages/src/acpi_timestamp.rs index c89f2ff32..25d99b52f 100644 --- a/time-alarm-service-messages/src/acpi_timestamp.rs +++ b/time-alarm-service-messages/src/acpi_timestamp.rs @@ -1,6 +1,4 @@ -use embedded_mcu_hal::time::{Datetime, Month, UncheckedDatetime}; - -use crate::AcpiTimeAlarmError; +use embedded_mcu_hal::time::{Datetime, DatetimeClockError, Month, UncheckedDatetime}; use zerocopy::{FromBytes, I16, Immutable, IntoBytes, KnownLayout, LE, U16, Unaligned}; // Timestamp structure as specified in the ACPI spec. Must be exactly this layout. @@ -86,9 +84,9 @@ pub struct AcpiTimeZoneOffset { } impl AcpiTimeZoneOffset { - pub fn new(minutes_from_utc: i16) -> Result { + pub fn new(minutes_from_utc: i16) -> Result { if !(-1440..=1440).contains(&minutes_from_utc) { - Err(AcpiTimeAlarmError::UnspecifiedFailure) + Err(DatetimeClockError::UnsupportedDatetime) } else { Ok(Self { minutes_from_utc }) } @@ -110,9 +108,9 @@ pub enum AcpiTimeZone { } impl TryFrom for AcpiTimeZone { - type Error = AcpiTimeAlarmError; + type Error = DatetimeClockError; - fn try_from(value: i16) -> Result { + fn try_from(value: i16) -> Result { if value == 2047 { Ok(Self::Unknown) } else { @@ -152,26 +150,31 @@ impl AcpiTimestamp { .expect("Size is guaranteed to be the size of RawAcpiTimestamp") } - pub fn try_from_bytes(bytes: &[u8]) -> Result { + pub fn try_from_bytes(bytes: &[u8]) -> Result { let raw = RawAcpiTimestamp::ref_from_bytes( bytes .get(..core::mem::size_of::()) - .ok_or(AcpiTimeAlarmError::UnspecifiedFailure)?, + .ok_or(DatetimeClockError::Unknown)?, ) - .map_err(|_| AcpiTimeAlarmError::UnspecifiedFailure)?; + .map_err(|_| DatetimeClockError::Unknown)?; Ok(Self { datetime: Datetime::new(UncheckedDatetime { year: raw.year.get(), - month: Month::try_from(raw.month).map_err(|_| AcpiTimeAlarmError::UnspecifiedFailure)?, + month: Month::try_from(raw.month).map_err(|_| DatetimeClockError::Unknown)?, day: raw.day, hour: raw.hour, minute: raw.minute, second: raw.second, nanosecond: (raw.milliseconds.get() as u32) * 1_000_000, - })?, - time_zone: raw.time_zone.get().try_into()?, - dst_status: raw.daylight.try_into()?, + }) + .map_err(|_| DatetimeClockError::Unknown)?, + time_zone: raw + .time_zone + .get() + .try_into() + .map_err(|_| DatetimeClockError::Unknown)?, + dst_status: raw.daylight.try_into().map_err(|_| DatetimeClockError::Unknown)?, }) } } diff --git a/time-alarm-service-messages/src/lib.rs b/time-alarm-service-messages/src/lib.rs index 0b6329eb0..26b6e40f7 100644 --- a/time-alarm-service-messages/src/lib.rs +++ b/time-alarm-service-messages/src/lib.rs @@ -361,6 +361,12 @@ impl From for AcpiTimeAlarmError { } } +impl From for AcpiTimeAlarmError { + fn from(_error: embedded_mcu_hal::time::DatetimeClockError) -> Self { + AcpiTimeAlarmError::UnspecifiedFailure + } +} + pub type AcpiTimeAlarmResult = Result; fn safe_put_u32(buffer: &mut [u8], index: usize, val: u32) -> Result { diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index 3b3f3fcff..a1e1d12b1 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -33,3 +33,9 @@ log = ["dep:log", "embedded-services/log", "embassy-time/log"] [lints] workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros", "time"] } +critical-section = { version = "1.1", features = ["std"]} +embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } +embassy-time-driver = { workspace = true } diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 10d5732c9..333420ae6 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -1,15 +1,13 @@ #![no_std] use core::cell::RefCell; -use embassy_futures::select::{Either, select}; use embassy_sync::blocking_mutex::Mutex; -use embassy_sync::channel::Channel; use embassy_sync::once_lock::OnceLock; use embassy_sync::signal::Signal; use embedded_mcu_hal::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; -use embedded_services::{GlobalRawMutex, comms::MailboxDelegateError}; -use embedded_services::{comms, info, warn}; +use embedded_services::GlobalRawMutex; +use embedded_services::{info, warn}; use time_alarm_service_messages::*; pub mod task; @@ -18,48 +16,14 @@ use timer::Timer; // ------------------------------------------------- -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum TimeAlarmError { - UnknownCommand, - DoubleInitError, - MailboxFullError, - ClockError(DatetimeClockError), -} - -impl From for MailboxDelegateError { - fn from(error: TimeAlarmError) -> Self { - match error { - TimeAlarmError::UnknownCommand => MailboxDelegateError::InvalidData, - TimeAlarmError::DoubleInitError => MailboxDelegateError::Other, - TimeAlarmError::MailboxFullError => MailboxDelegateError::BufferFull, - TimeAlarmError::ClockError(_) => MailboxDelegateError::Other, - } - } -} - -impl From for TimeAlarmError { - fn from(e: DatetimeClockError) -> Self { - TimeAlarmError::ClockError(e) - } -} - -impl From for TimeAlarmError { - fn from(_error: embedded_services::intrusive_list::Error) -> Self { - TimeAlarmError::DoubleInitError - } -} - -// ------------------------------------------------- - mod time_zone_data { use crate::AcpiDaylightSavingsTimeStatus; use crate::AcpiTimeZone; use crate::NvramStorage; - pub struct TimeZoneData { + pub struct TimeZoneData<'hw> { // Storage used to back the timezone and DST settings. - storage: &'static mut dyn NvramStorage<'static, u32>, + storage: &'hw mut dyn NvramStorage<'hw, u32>, } #[repr(C)] @@ -70,8 +34,8 @@ mod time_zone_data { _padding: u8, } - impl TimeZoneData { - pub fn new(storage: &'static mut dyn NvramStorage<'static, u32>) -> Self { + impl<'hw> TimeZoneData<'hw> { + pub fn new(storage: &'hw mut dyn NvramStorage<'hw, u32>) -> Self { Self { storage } } @@ -93,10 +57,11 @@ mod time_zone_data { /// pub fn get_data(&self) -> (AcpiTimeZone, AcpiDaylightSavingsTimeStatus) { let representation: RawTimeZoneData = zerocopy::transmute!(self.storage.read()); - (|| -> Result<(AcpiTimeZone, AcpiDaylightSavingsTimeStatus), time_alarm_service_messages::AcpiTimeAlarmError> { - Ok((representation.tz.try_into()?, representation.dst.try_into()?)) - })() - .unwrap_or((AcpiTimeZone::Unknown, AcpiDaylightSavingsTimeStatus::NotObserved)) + + let time_zone = AcpiTimeZone::try_from(representation.tz).unwrap_or(AcpiTimeZone::Unknown); + let dst_status = AcpiDaylightSavingsTimeStatus::try_from(representation.dst) + .unwrap_or(AcpiDaylightSavingsTimeStatus::NotObserved); + (time_zone, dst_status) } } } @@ -104,20 +69,20 @@ use time_zone_data::TimeZoneData; // ------------------------------------------------- -struct ClockState { - datetime_clock: &'static mut dyn DatetimeClock, - tz_data: TimeZoneData, +struct ClockState<'hw> { + datetime_clock: &'hw mut dyn DatetimeClock, + tz_data: TimeZoneData<'hw>, } // ------------------------------------------------- -struct Timers { - ac_timer: Timer, - dc_timer: Timer, +struct Timers<'hw> { + ac_timer: Timer<'hw>, + dc_timer: Timer<'hw>, } -impl Timers { - fn get_timer(&self, timer: AcpiTimerId) -> &Timer { +impl<'hw> Timers<'hw> { + fn get_timer(&self, timer: AcpiTimerId) -> &Timer<'hw> { match timer { AcpiTimerId::AcPower => &self.ac_timer, AcpiTimerId::DcPower => &self.dc_timer, @@ -125,10 +90,10 @@ impl Timers { } fn new( - ac_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, - ac_policy_storage: &'static mut dyn NvramStorage<'static, u32>, - dc_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, - dc_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + ac_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, + dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, ) -> Self { Self { ac_timer: Timer::new(ac_expiration_storage, ac_policy_storage), @@ -139,37 +104,30 @@ impl Timers { // ------------------------------------------------- -pub struct Service { - endpoint: comms::Endpoint, - - // ACPI messages from the host are sent through this channel. - acpi_channel: Channel, - - clock_state: Mutex>, +pub struct Service<'hw> { + clock_state: Mutex>>, // TODO [POWER_SOURCE] signal this whenever the power source changes power_source_signal: Signal, - timers: Timers, + timers: Timers<'hw>, capabilities: TimeAlarmDeviceCapabilities, } -impl Service { +impl<'hw> Service<'hw> { pub async fn init( - service_storage: &'static OnceLock, - backing_clock: &'static mut impl DatetimeClock, - tz_storage: &'static mut dyn NvramStorage<'static, u32>, - ac_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, - ac_policy_storage: &'static mut dyn NvramStorage<'static, u32>, - dc_expiration_storage: &'static mut dyn NvramStorage<'static, u32>, - dc_policy_storage: &'static mut dyn NvramStorage<'static, u32>, - ) -> Result<&'static Service, TimeAlarmError> { + service_storage: &'hw OnceLock>, + backing_clock: &'hw mut impl DatetimeClock, + tz_storage: &'hw mut dyn NvramStorage<'hw, u32>, + ac_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, + dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, + ) -> Result<&'hw Service<'hw>, DatetimeClockError> { info!("Starting time-alarm service task"); let service = service_storage.get_or_init(|| Service { - endpoint: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::TimeAlarm)), - acpi_channel: Channel::new(), clock_state: Mutex::new(RefCell::new(ClockState { datetime_clock: backing_clock, tz_data: TimeZoneData::new(tz_storage), @@ -202,52 +160,147 @@ impl Service { service.timers.ac_timer.start(&service.clock_state, true)?; service.timers.dc_timer.start(&service.clock_state, false)?; - comms::register_endpoint(service, &service.endpoint).await?; - Ok(service) } - pub(crate) async fn handle_requests(&'static self) -> ! { - loop { - let acpi_command = self.acpi_channel.receive(); - let power_source_change = self.power_source_signal.wait(); - - match select(acpi_command, power_source_change).await { - Either::First((respond_to_endpoint, acpi_command)) => { - info!("[Time/Alarm] Received command: {:?}", acpi_command); - - let result: AcpiTimeAlarmResult = self - .handle_acpi_command(acpi_command) - .await - .map_err(|_| time_alarm_service_messages::AcpiTimeAlarmError::UnspecifiedFailure); - - info!("[Time/Alarm] Responding with: {:?}", result); - - let _: Result<(), core::convert::Infallible> = - self.endpoint.send(respond_to_endpoint, &result).await; - } - Either::Second(new_power_source) => { - info!("[Time/Alarm] Power source changed to {:?}", new_power_source); - - self.timers - .get_timer(new_power_source.get_other_timer_id()) - .set_active(&self.clock_state, false); - self.timers - .get_timer(new_power_source) - .set_active(&self.clock_state, true); - } + /// Query clock capabilities. Analogous to ACPI TAD's _GRT method. + pub fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { + self.capabilities + } + + /// Query the current time. Analogous to ACPI TAD's _GRT method. + pub fn get_real_time(&self) -> Result { + self.clock_state.lock(|clock_state| { + let clock_state = clock_state.borrow(); + let datetime = clock_state.datetime_clock.get_current_datetime()?; + let (time_zone, dst_status) = clock_state.tz_data.get_data(); + Ok(AcpiTimestamp { + datetime, + time_zone, + dst_status, + }) + }) + } + + /// Change the current time. Analogous to ACPI TAD's _SRT method. + pub fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { + self.clock_state.lock(|clock_state| { + let mut clock_state = clock_state.borrow_mut(); + clock_state.datetime_clock.set_current_datetime(×tamp.datetime)?; + clock_state.tz_data.set_data(timestamp.time_zone, timestamp.dst_status); + Ok(()) + }) + } + + /// Query the current wake status. Analogous to ACPI TAD's _GWS method. + pub fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus { + self.timers.get_timer(timer_id).get_wake_status() + } + + /// Clear the current wake status. Analogous to ACPI TAD's _CWS method. + pub fn clear_wake_status(&self, timer_id: AcpiTimerId) { + self.timers.get_timer(timer_id).clear_wake_status(); + } + + /// Configures behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _STP method. + pub fn set_expired_timer_policy( + &self, + timer_id: AcpiTimerId, + policy: AlarmExpiredWakePolicy, + ) -> Result<(), DatetimeClockError> { + self.timers + .get_timer(timer_id) + .set_timer_wake_policy(&self.clock_state, policy)?; + Ok(()) + } + + /// Query current behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _TIP method. + pub fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy { + self.timers.get_timer(timer_id).get_timer_wake_policy() + } + + /// Change the expiry time for the given timer. Analogous to ACPI TAD's _STV method. + pub fn set_timer_value( + &self, + timer_id: AcpiTimerId, + timer_value: AlarmTimerSeconds, + ) -> Result<(), DatetimeClockError> { + let new_expiration_time = match timer_value { + AlarmTimerSeconds::DISABLED => None, + AlarmTimerSeconds(secs) => { + let current_time = self + .clock_state + .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; + + Some(Datetime::from_unix_time_seconds( + current_time.to_unix_time_seconds() + u64::from(secs), + )) } + }; + + self.timers + .get_timer(timer_id) + .set_expiration_time(&self.clock_state, new_expiration_time)?; + Ok(()) + } + + /// Query the expiry time for the given timer. Analogous to ACPI TAD's _TIV method. + pub fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result { + let expiration_time = self.timers.get_timer(timer_id).get_expiration_time(); + match expiration_time { + Some(expiration_time) => { + let current_time = self + .clock_state + .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; + + Ok(AlarmTimerSeconds( + expiration_time + .to_unix_time_seconds() + .saturating_sub(current_time.to_unix_time_seconds()) as u32, + )) + } + None => Ok(AlarmTimerSeconds::DISABLED), } } - pub(crate) async fn handle_timer(&'static self, timer_id: AcpiTimerId) -> ! { + pub(crate) async fn run_service(&'hw self) -> ! { + loop { + embassy_futures::select::select3( + self.handle_power_source_updates(), + self.handle_timer(AcpiTimerId::AcPower), + self.handle_timer(AcpiTimerId::DcPower), + ) + .await; + } + } + + async fn handle_power_source_updates(&'hw self) -> ! { + loop { + let new_power_source = self.power_source_signal.wait().await; + info!("[Time/Alarm] Power source changed to {:?}", new_power_source); + + self.timers + .get_timer(new_power_source.get_other_timer_id()) + .set_active(&self.clock_state, false); + self.timers + .get_timer(new_power_source) + .set_active(&self.clock_state, true); + } + } + + async fn handle_timer(&'hw self, timer_id: AcpiTimerId) -> ! { let timer = self.timers.get_timer(timer_id); loop { timer.wait_until_wake(&self.clock_state).await; - let _ = self - .timers + self.timers .get_timer(timer_id.get_other_timer_id()) - .set_timer_wake_policy(&self.clock_state, AlarmExpiredWakePolicy::NEVER); + .set_timer_wake_policy(&self.clock_state, AlarmExpiredWakePolicy::NEVER) + .unwrap_or_else(|e| { + warn!( + "[Time/Alarm] Failed to update wake policy on timer expiry - this should never happen: {:?}", + e + ); + }); warn!( "[Time/Alarm] Timer {:?} expired and would trigger a wake now, but the power service is not yet implemented so will currently do nothing", @@ -256,99 +309,44 @@ impl Service { // TODO [COMMS] We can't currently trigger a wake because the power service isn't implemented yet - when it is, we need to notify it here } } +} - async fn handle_acpi_command( - &'static self, - command: AcpiTimeAlarmRequest, - ) -> Result { - match command { - AcpiTimeAlarmRequest::GetCapabilities => Ok(AcpiTimeAlarmResponse::Capabilities(self.capabilities)), - AcpiTimeAlarmRequest::GetRealTime => self.clock_state.lock(|clock_state| { - let clock_state = clock_state.borrow(); - let datetime = clock_state.datetime_clock.get_current_datetime()?; - let (time_zone, dst_status) = clock_state.tz_data.get_data(); - Ok(AcpiTimeAlarmResponse::RealTime(AcpiTimestamp { - datetime, - time_zone, - dst_status, - })) - }), - AcpiTimeAlarmRequest::SetRealTime(timestamp) => self.clock_state.lock(|clock_state| { - let mut clock_state = clock_state.borrow_mut(); - clock_state.datetime_clock.set_current_datetime(×tamp.datetime)?; - clock_state.tz_data.set_data(timestamp.time_zone, timestamp.dst_status); +impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { + type RequestType = AcpiTimeAlarmRequest; + type ResultType = AcpiTimeAlarmResult; +} +impl<'hw> embedded_services::relay::mctp::RelayServiceHandler for Service<'hw> { + async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { + use time_alarm_service_messages::AcpiTimeAlarmResponse; + match request { + AcpiTimeAlarmRequest::GetCapabilities => Ok(AcpiTimeAlarmResponse::Capabilities(self.get_capabilities())), + AcpiTimeAlarmRequest::GetRealTime => Ok(AcpiTimeAlarmResponse::RealTime(self.get_real_time()?)), + AcpiTimeAlarmRequest::SetRealTime(timestamp) => { + self.set_real_time(timestamp)?; Ok(AcpiTimeAlarmResponse::OkNoData) - }), + } AcpiTimeAlarmRequest::GetWakeStatus(timer_id) => { - let status = self.timers.get_timer(timer_id).get_wake_status(); - Ok(AcpiTimeAlarmResponse::TimerStatus(status)) + Ok(AcpiTimeAlarmResponse::TimerStatus(self.get_wake_status(timer_id))) } AcpiTimeAlarmRequest::ClearWakeStatus(timer_id) => { - self.timers.get_timer(timer_id).clear_wake_status(); + self.clear_wake_status(timer_id); Ok(AcpiTimeAlarmResponse::OkNoData) } AcpiTimeAlarmRequest::SetExpiredTimerPolicy(timer_id, timer_policy) => { - self.timers - .get_timer(timer_id) - .set_timer_wake_policy(&self.clock_state, timer_policy)?; + self.set_expired_timer_policy(timer_id, timer_policy)?; Ok(AcpiTimeAlarmResponse::OkNoData) } + AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id) => Ok(AcpiTimeAlarmResponse::WakePolicy( + self.get_expired_timer_policy(timer_id), + )), AcpiTimeAlarmRequest::SetTimerValue(timer_id, timer_value) => { - let new_expiration_time = match timer_value { - AlarmTimerSeconds::DISABLED => None, - AlarmTimerSeconds(secs) => { - let current_time = self - .clock_state - .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; - - Some(Datetime::from_unix_time_seconds( - current_time.to_unix_time_seconds() + u64::from(secs), - )) - } - }; - - self.timers - .get_timer(timer_id) - .set_expiration_time(&self.clock_state, new_expiration_time)?; + self.set_timer_value(timer_id, timer_value)?; Ok(AcpiTimeAlarmResponse::OkNoData) } - AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id) => Ok(AcpiTimeAlarmResponse::WakePolicy( - self.timers.get_timer(timer_id).get_timer_wake_policy(), - )), AcpiTimeAlarmRequest::GetTimerValue(timer_id) => { - let expiration_time = self.timers.get_timer(timer_id).get_expiration_time(); - - let timer_wire_format = match expiration_time { - Some(expiration_time) => { - let current_time = self - .clock_state - .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; - - AlarmTimerSeconds( - expiration_time - .to_unix_time_seconds() - .saturating_sub(current_time.to_unix_time_seconds()) as u32, - ) - } - None => AlarmTimerSeconds::DISABLED, - }; - - Ok(AcpiTimeAlarmResponse::TimerSeconds(timer_wire_format)) + Ok(AcpiTimeAlarmResponse::TimerSeconds(self.get_timer_value(timer_id)?)) } } } } - -impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(acpi_cmd) = message.data.get::() { - self.acpi_channel - .try_send((message.from, *acpi_cmd)) - .map_err(|_| MailboxDelegateError::BufferFull)?; - Ok(()) - } else { - Err(comms::MailboxDelegateError::InvalidData) - } - } -} diff --git a/time-alarm-service/src/task.rs b/time-alarm-service/src/task.rs index d1f1e6d44..a45fdf62f 100644 --- a/time-alarm-service/src/task.rs +++ b/time-alarm-service/src/task.rs @@ -1,20 +1,9 @@ -use crate::{AcpiTimerId, Service}; +use crate::Service; use embedded_services::info; -/// Call this from a dedicated async task. Must be called exactly once per service. -pub async fn command_handler_task(service: &'static Service) { +/// Call this from a dedicated async task. Must be called exactly once on each service instance. +/// Note that on-device, 'hw must be 'static. We're generic over 'hw to enable some test scenarios leveraging tokio and mocks. +pub async fn run_service<'hw>(service: &'hw Service<'hw>) -> ! { info!("Starting time-alarm service task"); - service.handle_requests().await; -} - -/// Call this from a dedicated async task. Must be called exactly once per service. -pub async fn ac_timer_task(service: &'static Service) { - info!("Starting time-alarm AC timer task"); - service.handle_timer(AcpiTimerId::AcPower).await; -} - -/// Call this from a dedicated async task. Must be called exactly once per service. -pub async fn dc_timer_task(service: &'static Service) { - info!("Starting time-alarm DC timer task"); - service.handle_timer(AcpiTimerId::DcPower).await; + service.run_service().await } diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs index 78d46cb85..05840d464 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -27,20 +27,20 @@ mod persistent_storage { use crate::{AlarmExpiredWakePolicy, Datetime}; use embedded_mcu_hal::NvramStorage; - pub struct PersistentStorage { + pub struct PersistentStorage<'hw> { /// When the timer is programmed to expire, or None if the timer is not set /// This can't be part of the wake_state because we need to be able to report its value for _CWS even when the timer has expired and /// we're handling the power source policy. - expiration_time_storage: &'static mut dyn NvramStorage<'static, u32>, + expiration_time_storage: &'hw mut dyn NvramStorage<'hw, u32>, // Persistent storage for the AlarmExpiredWakePolicy - wake_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + wake_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, } - impl PersistentStorage { + impl<'hw> PersistentStorage<'hw> { pub fn new( - expiration_time_storage: &'static mut dyn NvramStorage<'static, u32>, - wake_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + expiration_time_storage: &'hw mut dyn NvramStorage<'hw, u32>, + wake_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, ) -> Self { Self { expiration_time_storage, @@ -80,8 +80,8 @@ mod persistent_storage { } use persistent_storage::PersistentStorage; -struct TimerState { - persistent_storage: PersistentStorage, +struct TimerState<'hw> { + persistent_storage: PersistentStorage<'hw>, wake_state: WakeState, @@ -92,16 +92,16 @@ struct TimerState { is_active: bool, } -pub(crate) struct Timer { - timer_state: Mutex>, +pub(crate) struct Timer<'hw> { + timer_state: Mutex>>, timer_signal: Signal>, } -impl Timer { +impl<'hw> Timer<'hw> { pub fn new( - expiration_time_storage: &'static mut dyn NvramStorage<'static, u32>, - wake_policy_storage: &'static mut dyn NvramStorage<'static, u32>, + expiration_time_storage: &'hw mut dyn NvramStorage<'hw, u32>, + wake_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, ) -> Self { Self { timer_state: Mutex::new(RefCell::new(TimerState { @@ -116,7 +116,7 @@ impl Timer { pub fn start( &self, - clock_state: &'static Mutex>, + clock_state: &Mutex>>, active: bool, ) -> Result<(), DatetimeClockError> { self.set_timer_wake_policy( @@ -157,7 +157,7 @@ impl Timer { pub fn set_timer_wake_policy( &self, - clock_state: &'static Mutex>, + clock_state: &Mutex>>, wake_policy: AlarmExpiredWakePolicy, ) -> Result<(), DatetimeClockError> { self.timer_state.lock(|timer_state| { @@ -176,7 +176,7 @@ impl Timer { pub fn set_expiration_time( &self, - clock_state: &'static Mutex>, + clock_state: &Mutex>>, expiration_time: Option, ) -> Result<(), DatetimeClockError> { self.timer_state.lock(|timer_state| { @@ -209,7 +209,7 @@ impl Timer { .lock(|timer_state| timer_state.borrow().persistent_storage.get_expiration_time()) } - pub fn set_active(&self, clock_state: &'static Mutex>, is_active: bool) { + pub fn set_active(&self, clock_state: &Mutex>>, is_active: bool) { self.timer_state.lock(|timer_state| { let mut timer_state = timer_state.borrow_mut(); @@ -276,7 +276,7 @@ impl Timer { }); } - pub(crate) async fn wait_until_wake(&self, clock_state: &'static Mutex>) { + pub(crate) async fn wait_until_wake(&self, clock_state: &Mutex>>) { loop { let mut wait_duration: Option = self.timer_signal.wait().await; 'waiting_for_timer: loop { @@ -309,7 +309,7 @@ impl Timer { /// Handles state changes for when the timer expires (figuring out what to do based on the current power source, etc). /// Returns true if the timer's expiry indicates that a wake event should be signaled to the host. - fn process_expired_timer(&self, clock_state: &'static Mutex>) -> bool { + fn process_expired_timer(&self, clock_state: &Mutex>>) -> bool { self.timer_state.lock(|timer_state| { let mut timer_state = timer_state.borrow_mut(); @@ -393,7 +393,7 @@ impl Timer { } fn get_current_datetime( - clock_state: &'static Mutex>, + clock_state: &Mutex>>, ) -> Result { clock_state.lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime()) } diff --git a/time-alarm-service/tests/common/mocks.rs b/time-alarm-service/tests/common/mocks.rs new file mode 100644 index 000000000..1b88661b0 --- /dev/null +++ b/time-alarm-service/tests/common/mocks.rs @@ -0,0 +1,117 @@ +#![allow(dead_code)] // We have some functionality in these mocks that isn't used yet but will be in future tests. + +use embedded_mcu_hal::NvramStorage; +use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; +use std::time::SystemTime; + +pub(crate) enum MockDatetimeClock { + Running { seconds_offset_from_system_time: i64 }, + Paused { frozen_time: Datetime }, +} + +impl MockDatetimeClock { + pub(crate) fn new_running() -> Self { + Self::Running { + seconds_offset_from_system_time: 0, + } + } + + pub(crate) fn new_paused() -> Self { + Self::Paused { + frozen_time: Datetime::from_unix_time_seconds( + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("System clock was adjusted during test") + .as_secs(), + ), + } + } + + /// Stop time from advancing. + pub(crate) fn pause(&mut self) { + if let Self::Running { .. } = self { + *self = MockDatetimeClock::Paused { + frozen_time: self.get_current_datetime().unwrap(), + }; + } + } + + /// Resume time advancing. + pub(crate) fn resume(&mut self) { + if let Self::Paused { frozen_time } = self { + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("System clock was adjusted during test"); + let target_secs = frozen_time.to_unix_time_seconds() as i64; + let adjusted_seconds = now.as_secs() as i64; + *self = MockDatetimeClock::Running { + seconds_offset_from_system_time: target_secs - adjusted_seconds, + }; + } + } +} + +impl DatetimeClock for MockDatetimeClock { + fn get_current_datetime(&self) -> Result { + match self { + MockDatetimeClock::Paused { frozen_time } => Ok(*frozen_time), + MockDatetimeClock::Running { + seconds_offset_from_system_time, + } => { + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("System clock was adjusted during test"); + let adjusted_seconds = now.as_secs() as i64 + seconds_offset_from_system_time; + Ok(Datetime::from_unix_time_seconds(adjusted_seconds as u64)) + } + } + } + + fn set_current_datetime(&mut self, datetime: &Datetime) -> Result<(), DatetimeClockError> { + match self { + MockDatetimeClock::Paused { .. } => { + *self = MockDatetimeClock::Paused { frozen_time: *datetime }; + Ok(()) + } + MockDatetimeClock::Running { .. } => { + let target_secs = datetime.to_unix_time_seconds() as i64; + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("System clock was adjusted during test"); + + *self = MockDatetimeClock::Running { + seconds_offset_from_system_time: target_secs - (now.as_secs() as i64), + }; + Ok(()) + } + } + } + + fn max_resolution_hz(&self) -> u32 { + 1 + } +} + +pub(crate) struct MockNvramStorage<'a> { + value: u32, + _phantom: core::marker::PhantomData<&'a ()>, +} + +impl<'a> MockNvramStorage<'a> { + pub(crate) fn new(initial_value: u32) -> Self { + Self { + value: initial_value, + _phantom: core::marker::PhantomData, + } + } +} + +impl<'a> NvramStorage<'a, u32> for MockNvramStorage<'a> { + fn read(&self) -> u32 { + self.value + } + + fn write(&mut self, value: u32) { + self.value = value; + } +} diff --git a/time-alarm-service/tests/common/mod.rs b/time-alarm-service/tests/common/mod.rs new file mode 100644 index 000000000..64bafca62 --- /dev/null +++ b/time-alarm-service/tests/common/mod.rs @@ -0,0 +1 @@ +pub mod mocks; diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs new file mode 100644 index 000000000..dc3d43d8e --- /dev/null +++ b/time-alarm-service/tests/tad_test.rs @@ -0,0 +1,109 @@ +// Panicking is how tests communicate failure, so we need to allow it here. +#![allow(clippy::unwrap_used)] +#![allow(clippy::expect_used)] + +mod common; + +#[cfg(test)] +mod test { + use embassy_sync::once_lock::OnceLock; + use embassy_time::Timer; + use embedded_mcu_hal::time::{Datetime, DatetimeClock}; + + use time_alarm_service_messages as msg; + + use crate::common::mocks::*; + + #[tokio::test] + async fn test_get_time() { + let mut tz_storage = MockNvramStorage::new(0); + let mut ac_exp_storage = MockNvramStorage::new(0); + let mut ac_pol_storage = MockNvramStorage::new(0); + let mut dc_exp_storage = MockNvramStorage::new(0); + let mut dc_pol_storage = MockNvramStorage::new(0); + + let mut clock = MockDatetimeClock::new_running(); + + let storage = OnceLock::new(); + let service = time_alarm_service::Service::init( + &storage, + &mut clock, + &mut tz_storage, + &mut ac_exp_storage, + &mut ac_pol_storage, + &mut dc_exp_storage, + &mut dc_pol_storage, + ) + .await + .unwrap(); + + // We need to have the service have non-static lifetime for our test use cases so we can have + // multiple test cases. This means we can't spawn tasks that require 'static lifetime. + // + // Instead, we'll use select! to run the worker task in the local scope, which lets us take + // borrows from the stack and not require 'static. The worker task is expected to + // return !, so we should go until the test arm completes and then shut down. + // + tokio::select! { + _ = time_alarm_service::task::run_service(service) => unreachable!("time alarm service task finished unexpectedly"), + _ = async { + let delay_secs = 2; + let begin = service.get_real_time().unwrap(); + println!("Current time from service: {begin:?}"); + Timer::after(embassy_time::Duration::from_millis(delay_secs * 1000)).await; + let end = service.get_real_time().unwrap(); + println!("Current time from service after delay: {end:?}"); + assert!(end.datetime.to_unix_time_seconds() - begin.datetime.to_unix_time_seconds() <= delay_secs + 1); + assert!(end.datetime.to_unix_time_seconds() - begin.datetime.to_unix_time_seconds() >= delay_secs - 1); + } => {} + } + } + + #[tokio::test] + async fn test_set_time() { + let mut tz_storage = MockNvramStorage::new(0); + let mut ac_exp_storage = MockNvramStorage::new(0); + let mut ac_pol_storage = MockNvramStorage::new(0); + let mut dc_exp_storage = MockNvramStorage::new(0); + let mut dc_pol_storage = MockNvramStorage::new(0); + + let mut clock = MockDatetimeClock::new_paused(); + const TEST_UNIX_TIME: u64 = 1_234_567_890; + clock + .set_current_datetime(&Datetime::from_unix_time_seconds(TEST_UNIX_TIME)) + .unwrap(); + + let storage = OnceLock::new(); + let service = time_alarm_service::Service::init( + &storage, + &mut clock, + &mut tz_storage, + &mut ac_exp_storage, + &mut ac_pol_storage, + &mut dc_exp_storage, + &mut dc_pol_storage, + ) + .await + .unwrap(); + + tokio::select! { + _ = time_alarm_service::task::run_service(service) => unreachable!("time alarm service task finished unexpectedly"), + _ = async { + // Clock is paused, so time shouldn't advance unless we set it. + let begin = service.get_real_time().unwrap(); + assert_eq!(begin.datetime.to_unix_time_seconds(), TEST_UNIX_TIME); + + let target_timestamp = msg::AcpiTimestamp { + datetime: Datetime::from_unix_time_seconds(TEST_UNIX_TIME), + time_zone: msg::AcpiTimeZone::Unknown, + dst_status: msg::AcpiDaylightSavingsTimeStatus::Adjusted, + }; + service.set_real_time(target_timestamp).unwrap(); + + let actual_timestamp = service.get_real_time().unwrap(); + assert_eq!(actual_timestamp, target_timestamp); + + } => {} + } + } +} diff --git a/uart-service/src/mctp.rs b/uart-service/src/mctp.rs index 1d6fb8183..41e05bcaa 100644 --- a/uart-service/src/mctp.rs +++ b/uart-service/src/mctp.rs @@ -1,13 +1,27 @@ use embedded_services::{ comms, - relay::{SerializableMessage, SerializableResult, mctp::impl_odp_mctp_relay_types}, + relay::{SerializableMessage, SerializableResult}, }; -// TODO We'd ideally like these types to be passed in as a generic or something when the UART service is instantiated -// so the UART service can be extended to handle 3rd party message types without needing to fork the UART service -impl_odp_mctp_relay_types!( - Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug) ), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; - TimeAlarm, 0x0B, (comms::EndpointID::Internal(comms::Internal::TimeAlarm)), time_alarm_service_messages::AcpiTimeAlarmRequest, time_alarm_service_messages::AcpiTimeAlarmResult; -); +// TODO we're currently transitioning from the comms service to direct async calls to support better testing, reduced code size, and better performance. +// As part of this transition, each service that interacts with the eSPI service needs to migrate to expose a direct async call API and implement +// some additional traits to be able to interface with relay services in the new way. +// +// Until all services have been migrated, we need to support both the old and new methods for interfacing with services. Once migration is complete, +// we can remove all the legacy code that supports the old comms service method of interfacing with the eSPI service. +// These are the ones that have not been migrated to using the new method. When a service is migrated, remove it here and pass it into +// the new impl macro from the 'application' layer. +// +// You should not add any new types here. Instead, implement the embedded_services::relay::mctp::RelayServiceHandler trait for your service. +// +#[allow(deprecated)] +mod legacy_relay { + use super::*; + use embedded_services::relay::mctp::impl_odp_mctp_relay_types; + impl_odp_mctp_relay_types!( + Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; + Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; + Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; + ); +} +pub(crate) use legacy_relay::*; From 963f77c1fed198b23e70eb7519229649999627c0 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Tue, 24 Feb 2026 19:50:44 -0800 Subject: [PATCH 23/79] comms: Update uart, thermal, and battery to use Relay trait (#732) Refactors thermal, battery, and uart services to make use of the latest Relay trait for direct async calls so we don't need to go through comms service to interact with host/relay services. ## Thermal - Removes all statics - Removes all uses of intrusive list and instead just uses slices for maintaining lists of sensors and fans - Removes comms dependency entirely and only implements Relay trait ## Battery - Replaces communications with host from comms to Relay trait. But I've kept the comms system in place since the battery service seems to talk to other power services and I didn't want to try and change that in this PR. Likewise some uses of statics and intrusive lists still exist in battery since I'm not 100% sure yet on if changing any of that will break things so can be addressed in a future PR - The changes I did make allowed for some code reduction since we no longer have to differentiate between ACPI events and battery state machine events so could do a little refactoring there ## Uart - Completely removes legacy comms system and MCTP macro. Only relies now on updated Relay traits. ## Espi - Removed thermal and battery from the legacy handler. In a followup PR, I will update Debug service and then can compeltely remove all legacy code from the espi service, completing the transition. These changes were all tested on several dev platforms that can make use of the uart-service. Tested changes on an espi platform and things were mostly correct, but it appears there is a bug that existed even before these changes were made which we are investigating now. Resolves #721 #723 #725 --- battery-service/src/context.rs | 11 --- battery-service/src/lib.rs | 60 ++++++----------- embedded-service/src/relay/mod.rs | 3 +- espi-service/src/mctp.rs | 2 - examples/std/src/bin/thermal.rs | 24 +++---- thermal-service/src/context.rs | 80 +++++----------------- thermal-service/src/fan.rs | 12 +--- thermal-service/src/lib.rs | 107 +++++++----------------------- thermal-service/src/mptf.rs | 44 ++++++------ thermal-service/src/sensor.rs | 14 +--- thermal-service/src/task.rs | 34 ++-------- uart-service/src/lib.rs | 89 ++++++++----------------- uart-service/src/mctp.rs | 27 -------- uart-service/src/task.rs | 13 ++-- 14 files changed, 137 insertions(+), 383 deletions(-) delete mode 100644 uart-service/src/mctp.rs diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index b43a03fea..d02867955 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -5,7 +5,6 @@ use battery_service_messages::{AcpiBatteryRequest, AcpiBatteryResponse, DeviceId use embassy_sync::channel::Channel; use embassy_sync::channel::TrySendError; use embassy_sync::mutex::Mutex; -use embassy_sync::signal::Signal; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::comms::MailboxDelegateError; @@ -138,7 +137,6 @@ pub struct Context { battery_response: Channel, no_op_retry_count: AtomicUsize, config: Config, - acpi_request: Signal, power_info: Mutex, } @@ -180,7 +178,6 @@ impl Context { battery_response: Channel::new(), no_op_retry_count: AtomicUsize::new(0), config, - acpi_request: Signal::new(), power_info: Mutex::new(PsuState::new()), } } @@ -485,14 +482,6 @@ impl Context { self.battery_event.receive().await } - pub(super) fn send_acpi_cmd(&self, request: AcpiBatteryRequest) { - self.acpi_request.signal(request); - } - - pub(super) async fn wait_acpi_cmd(&self) -> AcpiBatteryRequest { - self.acpi_request.wait().await - } - pub async fn get_state(&self) -> State { *self.state.lock().await } diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index 7b5d21f64..60e7edb26 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -2,9 +2,8 @@ use core::{any::Any, convert::Infallible}; -use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest}; +use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest, AcpiBatteryResult}; use context::BatteryEvent; -use embassy_futures::select::select; use embedded_services::{ comms::{self, EndpointID}, error, trace, @@ -50,36 +49,14 @@ impl Service { } /// Wait for next event. - pub async fn wait_next(&self) -> Event { - match select(self.context.wait_event(), self.context.wait_acpi_cmd()).await { - embassy_futures::select::Either::First(event) => Event::StateMachine(event), - embassy_futures::select::Either::Second(acpi_msg) => Event::AcpiRequest(acpi_msg), - } + pub async fn wait_next(&self) -> BatteryEvent { + self.context.wait_event().await } /// Process battery service event. - pub async fn process_event(&self, event: Event) { - match event { - Event::StateMachine(event) => { - trace!("Battery service: state machine event recvd {:?}", event); - self.context.process(event).await - } - Event::AcpiRequest(acpi_msg) => { - trace!("Battery service: ACPI cmd recvd"); - let response = self.context.process_acpi_cmd(&acpi_msg).await; - if let Err(e) = response { - error!("Battery service command failed: {:?}", e) - } - - // TODO We should probably be responding to the requestor rather than just assuming the request came from the host - self.comms_send( - crate::EndpointID::External(embedded_services::comms::External::Host), - &response, - ) - .await - .expect("comms_send is infallible") - } - } + pub async fn process_event(&self, event: BatteryEvent) { + trace!("Battery service: state machine event recvd {:?}", event); + self.context.process(event).await } /// Register fuel gauge device with the battery service. @@ -121,27 +98,34 @@ impl Service { } } -#[derive(Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event { - StateMachine(BatteryEvent), - AcpiRequest(AcpiBatteryRequest), -} - impl Default for Service { fn default() -> Self { Self::new() } } +impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service { + type RequestType = AcpiBatteryRequest; + type ResultType = AcpiBatteryResult; +} + +impl embedded_services::relay::mctp::RelayServiceHandler for Service { + async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { + trace!("Battery service: ACPI cmd recvd"); + let response = self.context.process_acpi_cmd(&request).await; + if let Err(e) = response { + error!("Battery service command failed: {:?}", e) + } + response + } +} + impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { if let Some(event) = message.data.get::() { self.context.send_event_no_wait(*event).map_err(|e| match e { embassy_sync::channel::TrySendError::Full(_) => comms::MailboxDelegateError::BufferFull, })? - } else if let Some(battery_request) = message.data.get::() { - self.context.send_acpi_cmd(*battery_request); } else if let Some(power_policy_msg) = message.data.get::() { self.context.set_power_info(&power_policy_msg.data)?; } diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index 72ed2c3a0..391ac4814 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -811,7 +811,8 @@ pub mod mctp { } } // end mod __odp_impl - use [< _odp_impl_ $relay_type_name:snake >]::$relay_type_name; + // Allows this generated relay type to be publicly re-exported + pub use [< _odp_impl_ $relay_type_name:snake >]::$relay_type_name; } // end paste! }; // end macro arm diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs index 41e05bcaa..e4760375c 100644 --- a/espi-service/src/mctp.rs +++ b/espi-service/src/mctp.rs @@ -19,8 +19,6 @@ mod legacy_relay { use super::*; use embedded_services::relay::mctp::impl_odp_mctp_relay_types; impl_odp_mctp_relay_types!( - Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; ); } diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 357ef97f2..93f899e7e 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -15,14 +15,17 @@ async fn run(spawner: Spawner) { static FAN: StaticCell = StaticCell::new(); let fan = FAN.init(ts::mock::new_fan()); - static SERVICE: OnceLock = OnceLock::new(); - let service = ts::Service::new(&SERVICE, &[sensor.device()], &[fan.device()]) - .await - .expect("Failed to initialize thermal service"); + static SENSORS: StaticCell<[&'static ts::sensor::Device; 1]> = StaticCell::new(); + let sensors = SENSORS.init([sensor.device()]); + + static FANS: StaticCell<[&'static ts::fan::Device; 1]> = StaticCell::new(); + let fans = FANS.init([fan.device()]); + + static STORAGE: OnceLock> = OnceLock::new(); + let service = ts::Service::init(&STORAGE, sensors, fans).await; spawner.must_spawn(sensor_task(service, sensor)); spawner.must_spawn(fan_task(service, fan)); - spawner.must_spawn(handle_requests_task(service)); spawner.must_spawn(monitor(service)); } @@ -37,22 +40,17 @@ fn main() { } #[embassy_executor::task] -async fn sensor_task(service: &'static ts::Service, sensor: &'static ts::mock::TsMockSensor) { +async fn sensor_task(service: &'static ts::Service<'static>, sensor: &'static ts::mock::TsMockSensor) { ts::task::sensor_task(sensor, service).await } #[embassy_executor::task] -async fn fan_task(service: &'static ts::Service, fan: &'static ts::mock::TsMockFan) { +async fn fan_task(service: &'static ts::Service<'static>, fan: &'static ts::mock::TsMockFan) { ts::task::fan_task(fan, service).await; } #[embassy_executor::task] -async fn handle_requests_task(service: &'static ts::Service) { - ts::task::handle_requests(service).await; -} - -#[embassy_executor::task] -async fn monitor(service: &'static ts::Service) { +async fn monitor(service: &'static ts::Service<'static>) { loop { match service .execute_sensor_request(ts::mock::MOCK_SENSOR_ID, ts::sensor::Request::GetTemp) diff --git a/thermal-service/src/context.rs b/thermal-service/src/context.rs index 7975a4793..fa1cc442d 100644 --- a/thermal-service/src/context.rs +++ b/thermal-service/src/context.rs @@ -1,54 +1,32 @@ //! Thermal service context -use crate::{Error, Event, fan, sensor}; +use crate::{Event, fan, sensor}; use embassy_sync::channel::Channel; use embedded_services::GlobalRawMutex; -use embedded_services::{error, intrusive_list}; -pub(crate) struct Context { +pub(crate) struct Context<'hw> { // Registered temperature sensors - sensors: intrusive_list::IntrusiveList, + sensors: &'hw [&'hw sensor::Device], // Registered fans - fans: intrusive_list::IntrusiveList, - // Pending MPTF request queue - mptf: Channel, + fans: &'hw [&'hw fan::Device], // Event queue events: Channel, } -impl Context { - pub(crate) const fn new() -> Self { +impl<'hw> Context<'hw> { + pub(crate) const fn new(sensors: &'hw [&'hw sensor::Device], fans: &'hw [&'hw fan::Device]) -> Self { Self { - sensors: intrusive_list::IntrusiveList::new(), - fans: intrusive_list::IntrusiveList::new(), - mptf: Channel::new(), + sensors, + fans, events: Channel::new(), } } - pub(crate) fn register_sensor(&self, sensor: &'static sensor::Device) -> Result<(), intrusive_list::Error> { - if self.get_sensor(sensor.id()).is_some() { - return Err(intrusive_list::Error::NodeAlreadyInList); - } - - self.sensors.push(sensor) - } - - pub(crate) fn sensors(&self) -> &intrusive_list::IntrusiveList { - &self.sensors + pub(crate) fn sensors(&self) -> &[&sensor::Device] { + self.sensors } - pub(crate) fn get_sensor(&self, id: sensor::DeviceId) -> Option<&'static sensor::Device> { - for sensor in &self.sensors { - if let Some(data) = sensor.data::() { - if data.id() == id { - return Some(data); - } - } else { - error!("Non-device located in sensors list"); - } - } - - None + pub(crate) fn get_sensor(&self, id: sensor::DeviceId) -> Option<&sensor::Device> { + self.sensors.iter().find(|sensor| sensor.id() == id).copied() } pub(crate) async fn execute_sensor_request( @@ -60,30 +38,12 @@ impl Context { sensor.execute_request(request).await } - pub(crate) fn register_fan(&self, fan: &'static fan::Device) -> Result<(), intrusive_list::Error> { - if self.get_fan(fan.id()).is_some() { - return Err(intrusive_list::Error::NodeAlreadyInList); - } - - self.fans.push(fan) - } - - pub(crate) fn fans(&self) -> &intrusive_list::IntrusiveList { - &self.fans + pub(crate) fn fans(&self) -> &[&fan::Device] { + self.fans } - pub(crate) fn get_fan(&self, id: fan::DeviceId) -> Option<&'static fan::Device> { - for fan in &self.fans { - if let Some(data) = fan.data::() { - if data.id() == id { - return Some(data); - } - } else { - error!("Non-device located in fan list"); - } - } - - None + pub(crate) fn get_fan(&self, id: fan::DeviceId) -> Option<&fan::Device> { + self.fans.iter().find(|fan| fan.id() == id).copied() } pub(crate) async fn execute_fan_request(&self, id: fan::DeviceId, request: fan::Request) -> fan::Response { @@ -91,14 +51,6 @@ impl Context { fan.execute_request(request).await } - pub(crate) fn queue_mptf_request(&self, msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { - self.mptf.try_send(msg).map_err(|_| Error) - } - - pub(crate) async fn wait_mptf_request(&self) -> thermal_service_messages::ThermalRequest { - self.mptf.receive().await - } - pub(crate) async fn send_event(&self, event: Event) { self.events.send(event).await } diff --git a/thermal-service/src/fan.rs b/thermal-service/src/fan.rs index fe8e8c62d..9f90cc8f6 100644 --- a/thermal-service/src/fan.rs +++ b/thermal-service/src/fan.rs @@ -8,7 +8,6 @@ use embedded_fans_async::{self as fan_traits, Error as HardwareError}; use embedded_sensors_hal_async::temperature::DegreesCelsius; use embedded_services::GlobalRawMutex; use embedded_services::ipc::deferred as ipc; -use embedded_services::{Node, intrusive_list}; use embedded_services::{error, trace}; /// Convenience type for Fan response result @@ -144,8 +143,6 @@ pub struct DeviceId(pub u8); /// Fan device struct pub struct Device { - // Intrusive list node allowing Device to be contained in a list - node: Node, // Device ID id: DeviceId, // Channel for IPC requests and responses @@ -158,7 +155,6 @@ impl Device { /// Create a new fan device pub fn new(id: DeviceId) -> Self { Self { - node: Node::uninit(), id, ipc: ipc::Channel::new(), auto_control_enable: Signal::new(), @@ -176,12 +172,6 @@ impl Device { } } -impl intrusive_list::NodeContainer for Device { - fn get_node(&self) -> &Node { - &self.node - } -} - /// Fan profile #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -394,7 +384,7 @@ impl Fan { } } - pub async fn handle_auto_control(&self, thermal_service: &'static crate::Service) { + pub async fn handle_auto_control<'hw>(&self, thermal_service: &crate::Service<'hw>) { loop { if self.profile.lock().await.auto_control { let temp = match thermal_service diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index b49ac43c7..dc8f70c9a 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -4,7 +4,7 @@ #![allow(clippy::unwrap_used)] use embedded_sensors_hal_async::temperature::DegreesCelsius; -use embedded_services::{comms, error, info, intrusive_list}; +use thermal_service_messages::{ThermalRequest, ThermalResult}; mod context; pub mod fan; @@ -34,66 +34,19 @@ pub enum Event { FanFailure(fan::DeviceId, fan::Error), } -pub struct Service { - context: context::Context, - endpoint: comms::Endpoint, +pub struct Service<'hw> { + context: context::Context<'hw>, } -impl Service { - pub async fn new( - service_storage: &'static embassy_sync::once_lock::OnceLock, - sensors: &[&'static sensor::Device], - fans: &[&'static fan::Device], - ) -> Result<&'static Self, Error> { - let service = service_storage.get_or_init(|| Self { - context: context::Context::new(), - endpoint: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Thermal)), - }); - - for sensor in sensors { - service.register_sensor(sensor).unwrap(); - } - - for fan in fans { - service.register_fan(fan).unwrap(); - } - - service.init().await?; - - Ok(service) - } - - async fn init(&'static self) -> Result<(), Error> { - info!("Starting thermal service task"); - - if comms::register_endpoint(self, &self.endpoint).await.is_err() { - error!("Failed to register thermal service endpoint"); - Err(Error) - } else { - Ok(()) - } - } - - /// Used to send messages to other services from the Thermal service, - /// such as notifying the Host of thresholds crossed or the Power service if CRT TEMP is reached. - pub async fn send_service_msg( - &self, - to: comms::EndpointID, - data: &(impl embedded_services::Any + Send + Sync), - ) -> Result<(), Error> { - // TODO: When this gets updated to return error, handle retrying send on failure - self.endpoint.send(to, data).await.map_err(|_| Error)?; - Ok(()) - } - - /// Send a MPTF request - pub fn queue_mptf_request(&self, msg: thermal_service_messages::ThermalRequest) -> Result<(), Error> { - self.context.queue_mptf_request(msg) - } - - /// Wait for a MPTF request - pub async fn wait_mptf_request(&self) -> thermal_service_messages::ThermalRequest { - self.context.wait_mptf_request().await +impl<'hw> Service<'hw> { + pub async fn init( + service_storage: &'hw embassy_sync::once_lock::OnceLock>, + sensors: &'hw [&'hw sensor::Device], + fans: &'hw [&'hw fan::Device], + ) -> &'hw Self { + service_storage.get_or_init(|| Self { + context: context::Context::new(sensors, fans), + }) } /// Send a thermal event @@ -106,18 +59,13 @@ impl Service { self.context.wait_event().await } - /// Register a sensor with the thermal service - pub fn register_sensor(&self, sensor: &'static sensor::Device) -> Result<(), intrusive_list::Error> { - self.context.register_sensor(sensor) - } - /// Provides access to the sensors list - pub fn sensors(&'static self) -> &'static intrusive_list::IntrusiveList { + pub fn sensors(&self) -> &[&sensor::Device] { self.context.sensors() } /// Find a sensor by its ID - pub fn get_sensor(&self, id: sensor::DeviceId) -> Option<&'static sensor::Device> { + pub fn get_sensor(&self, id: sensor::DeviceId) -> Option<&sensor::Device> { self.context.get_sensor(id) } @@ -126,18 +74,13 @@ impl Service { self.context.execute_sensor_request(id, request).await } - /// Register a fan with the thermal service - pub fn register_fan(&self, fan: &'static fan::Device) -> Result<(), intrusive_list::Error> { - self.context.register_fan(fan) - } - /// Provides access to the fans list - pub fn fans(&'static self) -> &'static intrusive_list::IntrusiveList { + pub fn fans(&self) -> &[&fan::Device] { self.context.fans() } /// Find a fan by its ID - pub fn get_fan(&self, id: fan::DeviceId) -> Option<&'static fan::Device> { + pub fn get_fan(&self, id: fan::DeviceId) -> Option<&fan::Device> { self.context.get_fan(id) } @@ -147,15 +90,13 @@ impl Service { } } -impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - // Queue for later processing - if let Some(msg) = message.data.get::() { - self.context - .queue_mptf_request(*msg) - .map_err(|_| comms::MailboxDelegateError::BufferFull) - } else { - Err(comms::MailboxDelegateError::InvalidData) - } +impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { + type RequestType = ThermalRequest; + type ResultType = ThermalResult; +} + +impl<'hw> embedded_services::relay::mctp::RelayServiceHandler for Service<'hw> { + async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { + mptf::process_request(&request, self).await } } diff --git a/thermal-service/src/mptf.rs b/thermal-service/src/mptf.rs index 24b968859..25e62c49f 100644 --- a/thermal-service/src/mptf.rs +++ b/thermal-service/src/mptf.rs @@ -33,9 +33,9 @@ pub enum Notify { Critical, } -async fn sensor_get_tmp( +async fn sensor_get_tmp<'hw>( instance_id: u8, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { if let Ok(ts::sensor::ResponseData::Temp(temp)) = thermal_service .execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp) @@ -49,10 +49,10 @@ async fn sensor_get_tmp( } } -async fn get_var_handler( +async fn get_var_handler<'hw>( instance_id: u8, var_uuid: uuid::Bytes, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match var_uuid { uuid_standard::CRT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Critical, thermal_service).await, @@ -78,11 +78,11 @@ async fn get_var_handler( } } -async fn set_var_handler( +async fn set_var_handler<'hw>( instance_id: u8, var_uuid: uuid::Bytes, set_var: u32, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match var_uuid { uuid_standard::CRT_TEMP => { @@ -138,9 +138,9 @@ async fn set_var_handler( } } -async fn sensor_get_warn_thrs( +async fn sensor_get_warn_thrs<'hw>( instance_id: u8, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { let low = thermal_service .execute_sensor_request( @@ -167,12 +167,12 @@ async fn sensor_get_warn_thrs( } } -async fn sensor_set_warn_thrs( +async fn sensor_set_warn_thrs<'hw>( instance_id: u8, _timeout: Milliseconds, low: DeciKelvin, high: DeciKelvin, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { let low_res = thermal_service .execute_sensor_request( @@ -194,10 +194,10 @@ async fn sensor_set_warn_thrs( } } -async fn sensor_get_thrs( +async fn sensor_get_thrs<'hw>( instance: u8, threshold_type: sensor::ThresholdType, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match thermal_service .execute_sensor_request( @@ -213,10 +213,10 @@ async fn sensor_get_thrs( } } -async fn fan_get_temp( +async fn fan_get_temp<'hw>( instance: u8, fan_request: fan::Request, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match thermal_service .execute_fan_request(fan::DeviceId(instance), fan_request) @@ -229,10 +229,10 @@ async fn fan_get_temp( } } -async fn fan_get_rpm( +async fn fan_get_rpm<'hw>( instance: u8, fan_request: fan::Request, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match thermal_service .execute_fan_request(fan::DeviceId(instance), fan_request) @@ -245,11 +245,11 @@ async fn fan_get_rpm( } } -async fn sensor_set_thrs( +async fn sensor_set_thrs<'hw>( instance: u8, threshold_type: sensor::ThresholdType, threshold_dk: u32, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match thermal_service .execute_sensor_request( @@ -263,10 +263,10 @@ async fn sensor_set_thrs( } } -async fn fan_set_var( +async fn fan_set_var<'hw>( instance: u8, fan_request: fan::Request, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match thermal_service .execute_fan_request(fan::DeviceId(instance), fan_request) @@ -277,9 +277,9 @@ async fn fan_set_var( } } -pub(crate) async fn process_request( +pub(crate) async fn process_request<'hw>( request: &thermal_service_messages::ThermalRequest, - thermal_service: &'static crate::Service, + thermal_service: &crate::Service<'hw>, ) -> thermal_service_messages::ThermalResult { match request { thermal_service_messages::ThermalRequest::ThermalGetTmpRequest { instance_id } => { diff --git a/thermal-service/src/sensor.rs b/thermal-service/src/sensor.rs index e057a84e0..69562f34d 100644 --- a/thermal-service/src/sensor.rs +++ b/thermal-service/src/sensor.rs @@ -8,7 +8,6 @@ use embedded_sensors_hal_async::temperature::{DegreesCelsius, TemperatureSensor, use embedded_services::GlobalRawMutex; use embedded_services::error; use embedded_services::ipc::deferred as ipc; -use embedded_services::{Node, intrusive_list}; // Timeout period (in ms) for physical bus access const BUS_TIMEOUT: u64 = 200; @@ -144,8 +143,6 @@ pub struct DeviceId(pub u8); /// Sensor device struct pub struct Device { - /// Intrusive list node allowing Device to be contained in a list - node: Node, /// Device ID id: DeviceId, /// Channel for IPC requests and responses @@ -158,7 +155,6 @@ impl Device { /// Create a new sensor device pub fn new(id: DeviceId) -> Self { Self { - node: Node::uninit(), id, ipc: ipc::Channel::new(), enable: Signal::new(), @@ -176,12 +172,6 @@ impl Device { } } -impl intrusive_list::NodeContainer for Device { - fn get_node(&self) -> &Node { - &self.node - } -} - /// Sensor profile #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -396,7 +386,7 @@ impl Sensor { } } - async fn check_thresholds(&self, temp: DegreesCelsius, thermal_service: &'static crate::Service) { + async fn check_thresholds<'hw>(&self, temp: DegreesCelsius, thermal_service: &crate::Service<'hw>) { let profile = self.profile.lock().await; let mut state = self.state.lock().await; @@ -450,7 +440,7 @@ impl Sensor { } /// Periodically samples temperature from physical sensor and caches it - pub async fn handle_sampling(&self, thermal_service: &'static crate::Service) { + pub async fn handle_sampling<'hw>(&self, thermal_service: &crate::Service<'hw>) { loop { // Only sample temperature if enabled if self.profile.lock().await.sampling_enabled { diff --git a/thermal-service/src/task.rs b/thermal-service/src/task.rs index 75d02596e..29e413f47 100644 --- a/thermal-service/src/task.rs +++ b/thermal-service/src/task.rs @@ -1,28 +1,6 @@ -use embedded_services::{comms, error}; - -use crate::mptf::process_request; - -pub async fn handle_requests(service: &'static crate::Service) { - loop { - let request = service.wait_mptf_request().await; - let result = process_request(&request, service).await; - let send_result = service - .send_service_msg( - // TODO we should probably respond to the endpoint that requested us rather than hardcoding the return address like this - comms::EndpointID::External(comms::External::Host), - &result, - ) - .await; - - if send_result.is_err() { - error!("Failed to send response to MPTF request!"); - } - } -} - -pub async fn fan_task( - fan: &'static crate::fan::Fan, - thermal_service: &'static crate::Service, +pub async fn fan_task<'hw, T: crate::fan::Controller, const SAMPLE_BUF_LEN: usize>( + fan: &crate::fan::Fan, + thermal_service: &crate::Service<'hw>, ) { let _ = embassy_futures::join::join3( fan.handle_rx(), @@ -32,9 +10,9 @@ pub async fn fan_task( .await; } -pub async fn sensor_task( - sensor: &'static crate::sensor::Sensor, - thermal_service: &'static crate::Service, +pub async fn sensor_task<'hw, T: crate::sensor::Controller, const SAMPLE_BUF_LEN: usize>( + sensor: &crate::sensor::Sensor, + thermal_service: &crate::Service<'hw>, ) { let _ = embassy_futures::join::join(sensor.handle_rx(), sensor.handle_sampling(thermal_service)).await; } diff --git a/uart-service/src/lib.rs b/uart-service/src/lib.rs index afac706fc..9f31f9866 100644 --- a/uart-service/src/lib.rs +++ b/uart-service/src/lib.rs @@ -8,17 +8,13 @@ //! provide GPIO pin we can use). #![no_std] -mod mctp; pub mod task; -use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService}; -use core::borrow::BorrowMut; use embassy_sync::channel::Channel; use embedded_io_async::Read as UartRead; use embedded_io_async::Write as UartWrite; use embedded_services::GlobalRawMutex; -use embedded_services::buffer::OwnedRef; -use embedded_services::comms::{self, Endpoint, EndpointID, External}; +use embedded_services::relay::mctp::{RelayHandler, RelayHeader, RelayResponse}; use embedded_services::trace; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -29,13 +25,11 @@ const HOST_TX_QUEUE_SIZE: usize = 5; const SMBUS_HEADER_SIZE: usize = 4; const SMBUS_LEN_IDX: usize = 2; -embedded_services::define_static_buffer!(assembly_buf, u8, [0u8; BUF_SIZE]); - #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct HostResponseMessage { - pub source_endpoint: EndpointID, - pub message: HostResult, +pub(crate) struct HostResultMessage { + pub handler_service_id: R::ServiceIdType, + pub message: R::ResultEnumType, } #[derive(Debug, Clone, Copy)] @@ -55,32 +49,26 @@ pub enum Error { Buffer(embedded_services::buffer::Error), } -pub struct Service<'a> { - endpoint: Endpoint, - host_tx_queue: Channel, - assembly_buf_owned_ref: OwnedRef<'a, u8>, +pub struct Service { + host_tx_queue: Channel, HOST_TX_QUEUE_SIZE>, + relay_handler: R, } -impl Service<'_> { - pub fn new() -> Result { +impl Service { + pub fn new(relay_handler: R) -> Result { Ok(Self { - endpoint: Endpoint::uninit(EndpointID::External(External::Host)), host_tx_queue: Channel::new(), - assembly_buf_owned_ref: assembly_buf::get_mut() - .ok_or(Error::Buffer(embedded_services::buffer::Error::InvalidRange))?, + relay_handler, }) } - async fn process_response(&self, uart: &mut T, response: &HostResponseMessage) -> Result<(), Error> { - let mut assembly_buf_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; - let pkt_ctx_buf = assembly_buf_access.borrow_mut(); - let mut mctp_ctx = mctp_rs::MctpPacketContext::new(SmbusEspiMedium, pkt_ctx_buf); - - let source_service: OdpService = OdpService::try_from(response.source_endpoint).map_err(|_| Error::Comms)?; + async fn process_response(&self, uart: &mut T, response: HostResultMessage) -> Result<(), Error> { + let mut assembly_buf = [0u8; BUF_SIZE]; + let mut mctp_ctx = mctp_rs::MctpPacketContext::new(SmbusEspiMedium, &mut assembly_buf); let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { source_endpoint_id: mctp_rs::EndpointId::Id(0x80), - destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), + destination_endpoint_id: mctp_rs::EndpointId::Id(response.handler_service_id.into()), packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(Error::Serialize)?, medium_context: SmbusEspiReplyContext { @@ -89,17 +77,9 @@ impl Service<'_> { }, // Medium-specific context }; - let header = OdpHeader { - message_type: OdpMessageType::Result { - is_error: !response.message.is_ok(), - }, - is_datagram: false, - service: source_service, - message_id: response.message.discriminant(), - }; - + let header = response.message.create_header(&response.handler_service_id); let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, response.message.clone())) + .serialize_packet(reply_context, (header, response.message)) .map_err(Error::Mctp)?; while let Some(packet_result) = packet_state.next() { @@ -115,9 +95,8 @@ impl Service<'_> { } async fn wait_for_request(&self, uart: &mut T) -> Result<(), Error> { - let mut assembly_access = self.assembly_buf_owned_ref.borrow_mut().map_err(Error::Buffer)?; - let mut mctp_ctx = - mctp_rs::MctpPacketContext::::new(SmbusEspiMedium, assembly_access.borrow_mut()); + let mut assembly_buf = [0u8; BUF_SIZE]; + let mut mctp_ctx = mctp_rs::MctpPacketContext::::new(SmbusEspiMedium, &mut assembly_buf); // First wait for SMBUS header, which tells us how big the incoming packet is let mut buf = [0; BUF_SIZE]; @@ -139,35 +118,21 @@ impl Service<'_> { .map_err(Error::Mctp)? .ok_or(Error::Serialize("Partial message not supported"))?; - let (header, host_request) = message.parse_as::().map_err(Error::Mctp)?; - let target_endpoint: EndpointID = header.service.get_endpoint_id(); - trace!( - "Host Request: Service {:?}, Command {:?}", - target_endpoint, header.message_id, - ); + let (header, body) = message.parse_as::().map_err(Error::Mctp)?; + trace!("Received host request"); - host_request - .send_to_endpoint(&self.endpoint, target_endpoint) - .await + let response = self.relay_handler.process_request(body).await; + self.host_tx_queue + .try_send(HostResultMessage { + handler_service_id: header.get_service_id(), + message: response, + }) .map_err(|_| Error::Comms)?; Ok(()) } - async fn wait_for_response(&self) -> HostResponseMessage { + async fn wait_for_response(&self) -> HostResultMessage { self.host_tx_queue.receive().await } } - -impl comms::MailboxDelegate for Service<'_> { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - crate::mctp::send_to_comms(message, |source_endpoint, message| { - self.host_tx_queue - .try_send(HostResponseMessage { - source_endpoint, - message, - }) - .map_err(|_| comms::MailboxDelegateError::BufferFull) - }) - } -} diff --git a/uart-service/src/mctp.rs b/uart-service/src/mctp.rs deleted file mode 100644 index 41e05bcaa..000000000 --- a/uart-service/src/mctp.rs +++ /dev/null @@ -1,27 +0,0 @@ -use embedded_services::{ - comms, - relay::{SerializableMessage, SerializableResult}, -}; - -// TODO we're currently transitioning from the comms service to direct async calls to support better testing, reduced code size, and better performance. -// As part of this transition, each service that interacts with the eSPI service needs to migrate to expose a direct async call API and implement -// some additional traits to be able to interface with relay services in the new way. -// -// Until all services have been migrated, we need to support both the old and new methods for interfacing with services. Once migration is complete, -// we can remove all the legacy code that supports the old comms service method of interfacing with the eSPI service. -// These are the ones that have not been migrated to using the new method. When a service is migrated, remove it here and pass it into -// the new impl macro from the 'application' layer. -// -// You should not add any new types here. Instead, implement the embedded_services::relay::mctp::RelayServiceHandler trait for your service. -// -#[allow(deprecated)] -mod legacy_relay { - use super::*; - use embedded_services::relay::mctp::impl_odp_mctp_relay_types; - impl_odp_mctp_relay_types!( - Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; - ); -} -pub(crate) use legacy_relay::*; diff --git a/uart-service/src/task.rs b/uart-service/src/task.rs index d77e4fccf..c5f7d59e4 100644 --- a/uart-service/src/task.rs +++ b/uart-service/src/task.rs @@ -1,18 +1,13 @@ use crate::{Error, Service}; use embedded_io_async::Read as UartRead; use embedded_io_async::Write as UartWrite; -use embedded_services::comms; use embedded_services::error; +use embedded_services::relay::mctp::RelayHandler; -pub async fn uart_service( - uart_service: &'static Service<'_>, +pub async fn uart_service( + uart_service: &Service, mut uart: T, ) -> Result { - // Register uart-service as the host service - comms::register_endpoint(uart_service, &uart_service.endpoint) - .await - .map_err(|_| Error::Comms)?; - // Note: eSPI service uses `select!` to seemingly allow asyncrhonous `responses` from services, // but there are concerns around async cancellation here at least for UART service. // @@ -23,7 +18,7 @@ pub async fn uart_service( error!("uart-service request error: {:?}", e); } else { let host_msg = uart_service.wait_for_response().await; - if let Err(e) = uart_service.process_response(&mut uart, &host_msg).await { + if let Err(e) = uart_service.process_response(&mut uart, host_msg).await { error!("uart-service response error: {:?}", e) } } From c78562a9d72bb4b5c8634a56187bc779fab72da5 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Wed, 25 Feb 2026 11:47:33 -0800 Subject: [PATCH 24/79] espi-service: Remove legacy comms (#735) Removes all legacy comms paths from the eSPI service and removes the `'static` lifetime (instead preferring a generic `'hw` lifetime). Also removes the legacy comms path from the debug service. However, the effort here was minimal. The debug service is due for a major overhaul in the near future so more effort will be made getting debug up to parity with the other services in a future PR. Resolves #703 Resolves #722 --- debug-service/src/debug_service.rs | 94 +++------ debug-service/src/task.rs | 21 +- embedded-service/src/relay/mod.rs | 319 ----------------------------- espi-service/src/espi_service.rs | 279 +++---------------------- espi-service/src/lib.rs | 3 - espi-service/src/mctp.rs | 25 --- espi-service/src/task.rs | 9 +- examples/std/src/bin/debug.rs | 3 +- 8 files changed, 71 insertions(+), 682 deletions(-) delete mode 100644 espi-service/src/mctp.rs diff --git a/debug-service/src/debug_service.rs b/debug-service/src/debug_service.rs index 1626ca6bd..ef9762307 100644 --- a/debug-service/src/debug_service.rs +++ b/debug-service/src/debug_service.rs @@ -1,8 +1,8 @@ +use debug_service_messages::{DebugRequest, DebugResult}; use embassy_sync::{once_lock::OnceLock, signal::Signal}; use embedded_services::GlobalRawMutex; use embedded_services::buffer::{OwnedRef, SharedRef}; -use embedded_services::comms::{self, EndpointID, Internal}; -use embedded_services::{debug, error}; +use embedded_services::debug; // Maximum number of bytes to request per defmt frame write grant. // This decouples the logger from any external protocol-specific size constants. @@ -20,69 +20,37 @@ pub(crate) const DEFMT_MAX_BYTES: u16 = debug_service_messages::STD_DEBUG_BUF_SI embedded_services::define_static_buffer!(defmt_acpi_buf, u8, [0u8; DEFMT_MAX_BYTES as usize]); /// Debug service that bridges an internal endpoint to an external transport. -/// -/// Terminology: -/// - Transport: The external-facing `comms::Endpoint` used to reach the host/PC. -/// It is provided by the platform (eSPI, USB, RTT bridge, etc.) and passed to -/// [`Service::new`]. Its ID is commonly `EndpointID::External(External::Host)`, -/// but the service does not assume a specific value. -/// - Endpoint: The internal endpoint owned by this service and registered under -/// `EndpointID::Internal(Internal::Debug)`. Messages addressed to this ID are -/// dispatched to the service via [`comms::MailboxDelegate::receive`]. -/// -/// Direction: -/// - Device → Host: Producers (e.g., the defmt forwarding task) should send from -/// `EndpointID::Internal(Internal::Debug)` to the transport endpoint ID exposed -/// by [`Service::endpoint_id`] or [`host_endpoint_id`]. -/// - Host → Device: The platform transport should deliver host messages to -/// `EndpointID::Internal(Internal::Debug)`, which this service handles in -/// [`receive`](comms::MailboxDelegate::receive). +#[derive(Default)] pub struct Service { - // The service-owned internal endpoint (Internal::Debug) that is registered - // with the comms layer and used as the "device side" address. - endpoint: comms::Endpoint, - // The external transport endpoint through which host traffic flows. - // This is provided by the platform and may map to eSPI/USB/etc. - transport: comms::Endpoint, // Hack frame_available: core::sync::atomic::AtomicBool, } impl Service { - pub const fn new(endpoint: comms::Endpoint) -> Self { + pub const fn new() -> Self { Service { - endpoint: comms::Endpoint::uninit(EndpointID::Internal(Internal::Debug)), - transport: endpoint, frame_available: core::sync::atomic::AtomicBool::new(false), } } +} - /// Returns the `EndpointID` of the external transport used by this service. - /// - /// Other components should target this ID when sending messages to the host - /// via the debug service - pub fn endpoint_id(&self) -> comms::EndpointID { - self.transport.get_id() - } +impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service { + type RequestType = DebugRequest; + type ResultType = DebugResult; } -impl comms::MailboxDelegate for Service { - fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - if let Some(_request) = message.data.get::() { - // Host sent an ACPI/MCTP request (e.g. GetDebugBuffer). Treat this as the - // trigger to send the staged debug buffer back to the host. - embedded_services::trace!("Received host ACPI request for debug buffer from {:?}", message.from); - // We only use the signal as a wakeup; the defmt task ignores any payload here. - if self.frame_available.load(core::sync::atomic::Ordering::SeqCst) { - response_notify_signal().signal(()); - } else { - no_avail_notify_signal().signal(()); - } +impl embedded_services::relay::mctp::RelayServiceHandler for Service { + async fn process_request(&self, _request: Self::RequestType) -> Self::ResultType { + // Host sent an ACPI/MCTP request (e.g. GetDebugBuffer). Treat this as the + // trigger to send the staged debug buffer back to the host. + // We only use the signal as a wakeup; the defmt task ignores any payload here. + if self.frame_available.load(core::sync::atomic::Ordering::SeqCst) { + response_notify_signal().signal(()); } else { - error!("Received unknown message from host"); + no_avail_notify_signal().signal(()); } - Ok(()) + frame_ready_signal().wait().await } } @@ -95,6 +63,9 @@ static RESP_NOTIFY: OnceLock> = OnceLock::new(); // For no frame avail task static NO_AVAIL_NOTIFY: OnceLock> = OnceLock::new(); +// Frame to send to host +static FRAME_READY: OnceLock> = OnceLock::new(); + pub(crate) fn owned_buffer() -> OwnedRef<'static, u8> { defmt_acpi_buf::get_mut().expect("defmt staging buffer already initialized elsewhere") } @@ -116,18 +87,13 @@ pub(crate) fn no_avail_notify_signal() -> &'static Signal { NO_AVAIL_NOTIFY.get_or_init(Signal::new) } -/// Returns the endpoint ID of the transport used by the debug service. -pub async fn host_endpoint_id() -> EndpointID { - let svc = DEBUG_SERVICE.get().await; - svc.endpoint_id() +pub(crate) fn frame_ready_signal() -> &'static Signal { + FRAME_READY.get_or_init(Signal::new) } /// Initialize and register the global Debug service endpoint. /// -/// This creates (or reuses) a single [`Service`] instance backed by the -/// provided transport [`comms::Endpoint`], then registers its internal -/// endpoint so messages addressed to [`EndpointID::Internal(Internal::Debug)`] -/// are dispatched to the service's [`comms::MailboxDelegate`] implementation. +/// This creates (or reuses) a single [`Service`] instance. /// /// Behavior: /// - Idempotent: repeated or concurrent calls return the same global instance. @@ -137,18 +103,14 @@ pub async fn host_endpoint_id() -> EndpointID { /// /// # Example /// ```no_run -/// use embedded_services::comms; /// use debug_service::debug_service_entry; /// -/// async fn boot(ep: comms::Endpoint) { -/// debug_service_entry(ep).await; +/// async fn boot() { +/// debug_service_entry().await; /// } /// ``` -pub async fn debug_service_entry(endpoint: comms::Endpoint) { - let debug_service = DEBUG_SERVICE.get_or_init(|| Service::new(endpoint)); - comms::register_endpoint(debug_service, &debug_service.endpoint) - .await - .unwrap(); +pub async fn debug_service_entry() { + let _debug_service = DEBUG_SERVICE.get_or_init(Service::new); // Emit an initial defmt frame so the defmt_to_host_task can drain and verify the path. - debug!("debug service initialized and endpoint registered"); + debug!("debug service initialized"); } diff --git a/debug-service/src/task.rs b/debug-service/src/task.rs index 5d973a4ac..67e7a8636 100644 --- a/debug-service/src/task.rs +++ b/debug-service/src/task.rs @@ -1,28 +1,24 @@ use core::borrow::{Borrow, BorrowMut}; use debug_service_messages::{DebugError, DebugResponse}; -use embedded_services::comms; -use crate::{debug_service_entry, defmt_ring_logger::DEFMT_BUFFER, frame_available, shared_buffer}; +use crate::{debug_service_entry, defmt_ring_logger::DEFMT_BUFFER, frame_available, frame_ready_signal, shared_buffer}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Error { Buffer(embedded_services::buffer::Error), } -pub async fn debug_service(endpoint: comms::Endpoint) { - debug_service_entry(endpoint).await; +pub async fn debug_service() { + debug_service_entry().await; } pub async fn defmt_to_host_task() -> Result { embedded_services::info!("defmt to host task start"); - use crate::debug_service::{host_endpoint_id, response_notify_signal}; - use embedded_services::comms::{self, EndpointID, Internal}; + use crate::debug_service::response_notify_signal; let framed_consumer = DEFMT_BUFFER.framed_consumer(); - let host_ep = host_endpoint_id().await; - // Acquire the staging buffer once; we own it for the task lifetime. let acpi_owned = crate::owned_buffer(); @@ -71,7 +67,7 @@ pub async fn defmt_to_host_task() -> Result { slice.try_into().unwrap() }, }; - let _ = comms::send(EndpointID::Internal(Internal::Debug), host_ep, &msg).await; + frame_ready_signal().signal(Ok(msg)); embedded_services::trace!("sent {} defmt bytes to host", copy_len); } @@ -88,10 +84,7 @@ pub async fn no_avail_to_host_task() -> Result embedded_services::define_static_buffer!(no_avail_acpi_buf, u8, [0u8; 12]); embedded_services::info!("no avail to host task start"); - use crate::debug_service::{host_endpoint_id, no_avail_notify_signal}; - use embedded_services::comms::{self, EndpointID, Internal}; - - let host_ep = host_endpoint_id().await; + use crate::debug_service::no_avail_notify_signal; let acpi_owned = no_avail_acpi_buf::get_mut().expect("defmt staging buffer already initialized elsewhere"); { @@ -106,6 +99,6 @@ pub async fn no_avail_to_host_task() -> Result // Send DEADBEEF if host requests frame but non available loop { no_avail_notify_signal().wait().await; - let _ = comms::send(EndpointID::Internal(Internal::Debug), host_ep, &msg).await; + frame_ready_signal().signal(msg); } } diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index 391ac4814..3cc7092ee 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -107,325 +107,6 @@ pub mod mctp { UnknownEndpointId, } - /// This macro generates the necessary types and impls to support relaying ODP messages to and from the comms system. - /// It takes as input a list of (service name, service ID, comms endpoint ID, request type, result type) tuples and - /// emits the following types: - /// - enum OdpService - a mapping from service name to MCTP endpoint ID - /// - enum HostRequest - an enum containing all the possible request types that were passed into the macro - /// - enum HostResult - an enum containing all the possible result types that were passed into the macro - /// - struct OdpHeader - a type representing the ODP MCTP header. - /// - fn send_to_comms(&comms::Message, impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, - /// a function that takes a received message and sends it to the appropriate service based on its type using the provided send function. - /// - /// Because this macro emits a number of types, it is recommended to invoke it inside a dedicated module. - /// - /// Arguments: - /// $service_name (identifier) - the name that this service will have in the emitted OdpService enum - /// $service_id (u8) - the service ID that will be used in the ODP MCTP header for messages related to this service. - /// $endpoint_id (comms::EndpointID value) - the comms endpoint ID that this service corresponds to. - /// NOTE: due to technical limitations in Rust macros, this must be surrounded with parentheses. - /// $request_type (type implementing SerializableMessage) - the type that represents requests for this service - /// $result_type (type implementing SerializableResult) - the type that represents results for this service - /// - /// Example usage: - /// - /// impl_odp_relay_types!( - /// Battery, 0x08, (comms::EndpointID::Internal(comms::Internal::Battery)), battery_service_messages::AcpiBatteryRequest, battery_service_messages::AcpiBatteryResult; - /// Thermal, 0x09, (comms::EndpointID::Internal(comms::Internal::Thermal)), thermal_service_messages::ThermalRequest, thermal_service_messages::ThermalResult; - /// Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; - /// ); - /// ^ ^ - /// note the above parentheses - these are required - #[deprecated( - note = "This macro is replaced by impl_odp_mctp_relay_handler - services should implement the RelayServiceHandler trait and relay services should be generic over an instance of the RelayHandler trait generated by impl_odp_mctp_relay_handler." - )] - #[macro_export] - macro_rules! impl_odp_mctp_relay_types { - ($($service_name:ident, - $service_id:expr, - ($($endpoint_id:tt)+), - $request_type:ty, - $result_type:ty; - )+) => { - - use bitfield::bitfield; - use core::convert::Infallible; - use mctp_rs::smbus_espi::SmbusEspiMedium; - use mctp_rs::{MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, MctpPacketResult}; - - #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[repr(u8)] - pub(crate) enum OdpService { - $( - $service_name = $service_id, - )+ - } - - impl TryFrom for OdpService { - type Error = embedded_services::relay::mctp::MctpError; - fn try_from(endpoint_id_value: comms::EndpointID) -> Result { - match endpoint_id_value { - $( - $($endpoint_id)+ => Ok(OdpService::$service_name), - )+ - _ => Err(embedded_services::relay::mctp::MctpError::UnknownEndpointId), - } - } - } - - impl OdpService { - pub fn get_endpoint_id(&self) -> comms::EndpointID { - match self { - $( - OdpService::$service_name => $($endpoint_id)+, - )+ - } - } - } - - pub(crate) enum HostRequest { - $( - $service_name($request_type), - )+ - } - - impl HostRequest { - pub(crate) async fn send_to_endpoint(&self, source_endpoint: &comms::Endpoint, destination_endpoint_id: comms::EndpointID) -> Result<(), Infallible> { - match self { - $( - HostRequest::$service_name(request) => source_endpoint.send(destination_endpoint_id, request).await, - )+ - } - } - } - - impl MctpMessageTrait<'_> for HostRequest { - type Header = OdpHeader; - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - $( - HostRequest::$service_name(request) => SerializableMessage::serialize(request, buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " request"))), - )+ - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - Ok(match header.service { - $( - OdpService::$service_name => Self::$service_name( - <$request_type as SerializableMessage>::deserialize(header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " request")))?, - ), - )+ - }) - } - } - - #[derive(Clone)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub(crate) enum HostResult { - $( - $service_name($result_type), - )+ - } - - impl HostResult { - pub(crate) fn discriminant(&self) -> u16 { - match self { - $( - HostResult::$service_name(result) => result.discriminant(), - )+ - } - } - - pub(crate) fn is_ok(&self) -> bool { - match self { - $( - HostResult::$service_name(result) => result.is_ok(), - )+ - } - } - } - - impl MctpMessageTrait<'_> for HostResult { - const MESSAGE_TYPE: u8 = 0x7D; // ODP message type - - type Header = OdpHeader; - - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - match self { - $( - HostResult::$service_name(result) => result - .serialize(buffer) - .map_err(|_| mctp_rs::MctpPacketError::SerializeError(concat!("Failed to serialize ", stringify!($service_name), " result"))), - )+ - } - } - - fn deserialize(header: &Self::Header, buffer: &'_ [u8]) -> MctpPacketResult { - match header.service { - $( - OdpService::$service_name => { - match header.message_type { - OdpMessageType::Request => { - Err(MctpPacketError::CommandParseError(concat!("Received ", stringify!($service_name), " request when expecting result"))) - } - OdpMessageType::Result { is_error } => { - Ok(HostResult::$service_name(<$result_type as SerializableResult>::deserialize(is_error, header.message_id, buffer) - .map_err(|_| MctpPacketError::CommandParseError(concat!("Could not parse ", stringify!($service_name), " result")))?)) - } - } - }, - )+ - } - } - } - - bitfield! { - /// Wire format for ODP MCTP headers. Not user-facing - use OdpHeader instead. - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct OdpHeaderWireFormat(u32); - impl Debug; - impl new; - /// If true, represents a request; otherwise, represents a result - is_request, set_is_request: 25; - - // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - is_datagram, set_is_datagram: 24; - - /// The service ID that this message is related to - /// Note: Error checking is done when you access the field, not when you construct the OdpHeader. Take care when constructing a header. - u8, service_id, set_service_id: 23, 16; - - /// On results, indicates if the result message is an error. Unused on requests. - is_error, set_is_error: 15; - - /// The message type/discriminant - u16, message_id, set_message_id: 14, 0; - } - - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub(crate) enum OdpMessageType { - Request, - Result { is_error: bool }, - } - - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub(crate) struct OdpHeader { - pub message_type: OdpMessageType, - pub is_datagram: bool, // TODO do we even want this bit? I think we just cribbed it off of a different message type, but it's not clear to me that we actually need it... - pub service: OdpService, - pub message_id: u16, - } - - impl From for OdpHeaderWireFormat { - fn from(src: OdpHeader) -> Self { - Self::new( - matches!(src.message_type, OdpMessageType::Request), - src.is_datagram, - src.service.into(), - match src.message_type { - OdpMessageType::Request => false, // unused on requests - OdpMessageType::Result { is_error } => is_error, - }, - src.message_id, - ) - } - } - - impl TryFrom for OdpHeader { - type Error = MctpPacketError; - - fn try_from(src: OdpHeaderWireFormat) -> Result { - let service = OdpService::try_from(src.service_id()) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp service in odp header"))?; - - let message_type = if src.is_request() { - OdpMessageType::Request - } else { - OdpMessageType::Result { - is_error: src.is_error(), - } - }; - - Ok(OdpHeader { - message_type, - is_datagram: src.is_datagram(), - service, - message_id: src.message_id(), - }) - } - } - - impl MctpMessageHeaderTrait for OdpHeader { - fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { - let wire_format = OdpHeaderWireFormat::from(self); - let bytes = wire_format.0.to_be_bytes(); - buffer - .get_mut(0..bytes.len()) - .ok_or(MctpPacketError::SerializeError("buffer too small for odp header"))? - .copy_from_slice(&bytes); - - Ok(bytes.len()) - } - - fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { - let bytes = buffer - .get(0..core::mem::size_of::()) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?; - let raw = u32::from_be_bytes( - bytes - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - ); - - let parsed_wire_format = OdpHeaderWireFormat(raw); - let header = OdpHeader::try_from(parsed_wire_format) - .map_err(|_| MctpPacketError::HeaderParseError("invalid odp header received"))?; - - Ok(( - header, - buffer - .get(core::mem::size_of::()..) - .ok_or(MctpPacketError::HeaderParseError("buffer too small for odp header"))?, - )) - } - } - - /// Attempt to route the provided message to the service that is registered to handle it based on its type. - pub(crate) fn send_to_comms( - message: &comms::Message, - send_fn: impl FnOnce(comms::EndpointID, HostResult) -> Result<(), comms::MailboxDelegateError>, - ) -> Result<(), comms::MailboxDelegateError> { - $( - if let Some(msg) = message.data.get::<$result_type>() { - send_fn( - $($endpoint_id)+, - HostResult::$service_name(*msg), - )?; - Ok(()) - } else - )+ - { - Err(comms::MailboxDelegateError::MessageNotFound) - } - } - }; -} - - #[allow(deprecated)] // Allow reexport until everyone has migrated away - pub use impl_odp_mctp_relay_types; - - /////////////////////////////////////////////////////////////////////////////////////////// - // V2 relay implementation - /// Trait for types that are used by a relay service to relay messages from your service over the wire. /// If you are implementing this trait, you should also implement RelayServiceHandler. /// diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 479059e94..0a7c1d937 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -1,13 +1,11 @@ use core::slice; -use crate::mctp::{HostRequest, HostResult, OdpHeader, OdpMessageType, OdpService}; use embassy_futures::select::select; use embassy_imxrt::espi; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embedded_services::comms as DEPRECATED_comms; -use embedded_services::{GlobalRawMutex, debug, error, info, trace}; +use embedded_services::{GlobalRawMutex, error, info, trace}; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -20,13 +18,6 @@ const OOB_PORT_ID: usize = 1; // Should be as large as the largest possible MCTP packet and its metadata. const ASSEMBLY_BUF_SIZE: usize = 256; -#[derive(Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct LegacyHostResultMessage { - pub source_endpoint: DEPRECATED_comms::EndpointID, - pub message: HostResult, -} - #[derive(Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] struct HostResultMessage { @@ -34,15 +25,6 @@ struct HostResultMessage { - Legacy(LegacyHostResultMessage), - New(HostResultMessage), -} - #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { @@ -50,54 +32,25 @@ pub enum Error { Buffer(embedded_services::buffer::Error), } -pub struct Service { - endpoint: DEPRECATED_comms::Endpoint, - espi: Mutex>, - host_tx_queue: Channel, HOST_TX_QUEUE_SIZE>, +pub struct Service<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { + espi: Mutex>, + host_tx_queue: Channel, HOST_TX_QUEUE_SIZE>, relay_handler: RelayHandler, } -// TODO we're currently transitioning from the comms service to direct async calls to support better testing, reduced code size, and better performance. -// As part of this transition, each service that interacts with the eSPI service needs to migrate to expose a direct async call API and implement -// some additional traits to be able to interface with relay services in the new way. -// -// Until all services have been migrated, we need to support both the old and new methods for interfacing with services. Once migration is complete, -// we can remove all the legacy code that supports the old comms service method of interfacing with the eSPI service. -// -// To ease this transition, we've split this module into four impl blocks - "common", "new", "legacy", and "routing". -// Common code should be largely unimpacted by this transition. The "new" and "legacy" impl blocks provide two different implementations of the same -// core functionality, one for the new direct async call method and one for the old comms service method. The "routing" impl block contains code that -// is responsible for routing messages to the correct implementation based on the type of the incoming message. When migration is complete, all "legacy" -// and "routing" functions should be removed and the "new" functions should be renamed to drop the "new" and migrated to the "common" block. -// -// This approach leads to a bit more code duplication than we'd like during the transition, but should make the final migration a lot simpler and -// less error-prone, and the transition should be relatively brief. -// - -///////// COMMON FUNCTIONS /////////// -impl Service { - // TODO a lot of the input lifetimes here have to be static because we have a dependency on the comms system, which requires - // that everything that talks over it is 'static. Once we eliminate that dependency, we should be able to relax these lifetimes. +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'hw, RelayHandler> { pub async fn init( - service_storage: &'static OnceLock, - mut espi: espi::Espi<'static>, + service_storage: &'hw OnceLock, + mut espi: espi::Espi<'hw>, relay_handler: RelayHandler, - ) -> &'static Self { + ) -> &'hw Self { espi.wait_for_plat_reset().await; - let result = service_storage.get_or_init(|| Service { - endpoint: DEPRECATED_comms::Endpoint::uninit(DEPRECATED_comms::EndpointID::External( - DEPRECATED_comms::External::Host, - )), + service_storage.get_or_init(|| Service { espi: Mutex::new(espi), host_tx_queue: Channel::new(), relay_handler, - }); - - DEPRECATED_comms::register_endpoint(result, &result.endpoint) - .await - .unwrap(); - result + }) } pub(crate) async fn run_service(&self) -> ! { @@ -114,7 +67,7 @@ impl Service { - self.process_response_to_host_routing(&mut espi, host_msg).await + self.process_response_to_host(&mut espi, host_msg).await } } } @@ -128,7 +81,7 @@ impl Service, packet: &[u8]) -> Result<(), embassy_imxrt::espi::Error> { + fn write_to_hw(&self, espi: &mut espi::Espi<'hw>, packet: &[u8]) -> Result<(), embassy_imxrt::espi::Error> { // Send packet via your transport medium // SAFETY: Safe as the access to espi is protected by a mut reference. let dest_slice = unsafe { espi.oob_get_write_buffer(OOB_PORT_ID)? }; @@ -140,7 +93,7 @@ impl Service, + espi: &mut espi::Espi<'hw>, event: Result, ) -> Result<(), Error> { match event { @@ -172,7 +125,7 @@ impl Service::new( @@ -183,7 +136,16 @@ impl Service { trace!("MCTP packet successfully deserialized"); - self.process_request_to_ec_routing(espi, &message, &port_event).await?; + match message.parse_as::() { + Ok((header, body)) => { + self.process_request_to_ec((header, body), espi, &port_event).await?; + } + Err(e) => { + error!("MCTP ODP type malformed: {:?}", e); + espi.complete_port(port_event.port); + return Err(Error::Serialize); + } + } } Ok(None) => { // Partial message, waiting for more packets @@ -220,52 +182,14 @@ impl Service Service { - async fn process_response_to_host_routing( - &self, - espi: &mut espi::Espi<'static>, - response: HostResultMessageMigrationEnum, - ) { - match response { - HostResultMessageMigrationEnum::Legacy(legacy_msg) => { - self.process_response_to_host_legacy(espi, legacy_msg).await - } - HostResultMessageMigrationEnum::New(new_msg) => self.process_response_to_host_new(espi, new_msg).await, - } - } - - async fn process_request_to_ec_routing( - &self, - espi: &mut espi::Espi<'static>, - message: &mctp_rs::MctpMessage<'_, mctp_rs::smbus_espi::SmbusEspiMedium>, - port_event: &espi::PortEvent, - ) -> Result<(), Error> { - if let Ok((header, body)) = message.parse_as::() { - self.process_request_to_ec_legacy((header, body), espi, port_event) - .await - } else { - match message.parse_as::() { - Ok((header, body)) => self.process_request_to_ec_new((header, body), espi, port_event).await, - Err(e) => { - error!("MCTP ODP type malformed: {:?}", e); - espi.complete_port(port_event.port); - Err(Error::Serialize) - } - } - } - } -} - -///////////// NEW FUNCTIONS ///////////// -impl Service { - async fn process_request_to_ec_new( +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'hw, RelayHandler> { + async fn process_request_to_ec( &self, (header, body): ( >::Header, RelayHandler::RequestEnumType, ), - espi: &mut espi::Espi<'static>, + espi: &mut espi::Espi<'hw>, port_event: &espi::PortEvent, ) -> Result<(), Error> { use embedded_services::relay::mctp::RelayHeader; @@ -275,21 +199,17 @@ impl Service, - response: HostResultMessage, - ) { - match self.serialize_packet_from_subsystem_new(espi, response).await { + async fn process_response_to_host(&self, espi: &mut espi::Espi<'hw>, response: HostResultMessage) { + match self.serialize_packet_from_subsystem(espi, response).await { Ok(()) => { trace!("Full packet successfully sent to host!") } @@ -301,9 +221,9 @@ impl Service, + espi: &mut espi::Espi<'hw>, result: HostResultMessage, ) -> Result<(), Error> { use embedded_services::relay::mctp::RelayResponse; @@ -354,136 +274,3 @@ impl Service Service { - async fn process_request_to_ec_legacy( - &self, - (header, body): (OdpHeader, HostRequest), - espi: &mut espi::Espi<'static>, - port_event: &espi::PortEvent, - ) -> Result<(), Error> { - let target_endpoint = header.service.get_endpoint_id(); - trace!( - "Host Request: Service {:?}, Command {:?}", - target_endpoint, header.message_id, - ); - - espi.complete_port(port_event.port); - body.send_to_endpoint(&self.endpoint, target_endpoint) - .await - .expect("result error type is infallible"); - info!("MCTP packet forwarded to service: {:?}", target_endpoint); - - Ok(()) - } - - async fn process_response_to_host_legacy(&self, espi: &mut espi::Espi<'static>, response: LegacyHostResultMessage) { - let source_endpoint = response.source_endpoint; - match self.serialize_packet_from_subsystem_legacy(espi, response).await { - Ok(()) => { - trace!("Full packet successfully sent to host!") - } - Err(e) => { - error!("Packet serialize error {:?}", e); - - self.send_mctp_error_response_legacy(source_endpoint, espi).await; - } - } - } - - async fn send_mctp_error_response_legacy( - &self, - endpoint: DEPRECATED_comms::EndpointID, - espi: &mut espi::Espi<'static>, - ) { - let error_msg = LegacyHostResultMessage { - source_endpoint: endpoint, - message: HostResult::Debug(Err(debug_service_messages::DebugError::UnspecifiedFailure)), - }; - self.serialize_packet_from_subsystem_legacy(espi, error_msg) - .await - .unwrap_or_else(|_| { - error!("Critical error reporting MCTP protocol error to host!"); - }); - } - - async fn serialize_packet_from_subsystem_legacy( - &self, - espi: &mut espi::Espi<'static>, - result: LegacyHostResultMessage, - ) -> Result<(), Error> { - let mut assembly_buf = [0u8; ASSEMBLY_BUF_SIZE]; - let mut mctp_ctx = - mctp_rs::MctpPacketContext::new(mctp_rs::smbus_espi::SmbusEspiMedium, assembly_buf.as_mut_slice()); - - let source_service: OdpService = OdpService::try_from(result.source_endpoint).map_err(|_| Error::Serialize)?; - - let reply_context: mctp_rs::MctpReplyContext = mctp_rs::MctpReplyContext { - source_endpoint_id: mctp_rs::EndpointId::Id(0x80), - destination_endpoint_id: mctp_rs::EndpointId::Id(source_service.into()), // TODO We're currently using this incorrectly - it should be the bus address of the host. Revisit once we have assigned a bus address to the host. - packet_sequence_number: mctp_rs::MctpSequenceNumber::new(0), - message_tag: mctp_rs::MctpMessageTag::try_from(3).map_err(|e| { - error!("serialize_packet_from_subsystem_legacy: {:?}", e); - Error::Serialize - })?, - medium_context: SmbusEspiReplyContext { - destination_slave_address: 1, - source_slave_address: 0, - }, // Medium-specific context - }; - - let header = OdpHeader { - message_type: OdpMessageType::Result { - is_error: !result.message.is_ok(), - }, - is_datagram: false, - service: source_service, - message_id: result.message.discriminant(), - }; - - let mut packet_state = mctp_ctx - .serialize_packet(reply_context, (header, result.message)) - .map_err(|e| { - error!("serialize_packet_from_subsystem_legacy: {:?}", e); - Error::Serialize - })?; - // Send each packet - while let Some(packet_result) = packet_state.next() { - let packet = packet_result.map_err(|e| { - error!("serialize_packet_from_subsystem_legacy: {:?}", e); - Error::Serialize - })?; - // Last byte is PEC, ignore for now - let packet = &packet[..packet.len() - 1]; - trace!("Sending MCTP response: {:?}", packet); - - self.write_to_hw(espi, packet).map_err(|e| { - error!("serialize_packet_from_subsystem_legacy: {:?}", e); - Error::Serialize - })?; - - // Immediately service the packet with the ESPI HAL - let event = espi.wait_for_event().await; - self.process_controller_event(espi, event).await?; - } - Ok(()) - } -} - -// TODO this impl is also a 'legacy function' and needs to go away when we no longer have services communicating with the eSPI service via the comms service -impl DEPRECATED_comms::MailboxDelegate - for Service -{ - fn receive(&self, message: &DEPRECATED_comms::Message) -> Result<(), DEPRECATED_comms::MailboxDelegateError> { - crate::mctp::send_to_comms(message, |source_endpoint, message| { - debug!("Espi service: recvd response"); - self.host_tx_queue - .try_send(HostResultMessageMigrationEnum::Legacy(LegacyHostResultMessage { - source_endpoint, - message, - })) - .map_err(|_| DEPRECATED_comms::MailboxDelegateError::BufferFull) - }) - } -} diff --git a/espi-service/src/lib.rs b/espi-service/src/lib.rs index e8233bc93..c3f65e52d 100644 --- a/espi-service/src/lib.rs +++ b/espi-service/src/lib.rs @@ -22,9 +22,6 @@ #[cfg(not(test))] mod espi_service; -#[cfg(not(test))] -mod mctp; - #[cfg(not(test))] pub mod task; diff --git a/espi-service/src/mctp.rs b/espi-service/src/mctp.rs deleted file mode 100644 index e4760375c..000000000 --- a/espi-service/src/mctp.rs +++ /dev/null @@ -1,25 +0,0 @@ -use embedded_services::{ - comms, - relay::{SerializableMessage, SerializableResult}, -}; - -// TODO we're currently transitioning from the comms service to direct async calls to support better testing, reduced code size, and better performance. -// As part of this transition, each service that interacts with the eSPI service needs to migrate to expose a direct async call API and implement -// some additional traits to be able to interface with relay services in the new way. -// -// Until all services have been migrated, we need to support both the old and new methods for interfacing with services. Once migration is complete, -// we can remove all the legacy code that supports the old comms service method of interfacing with the eSPI service. -// These are the ones that have not been migrated to using the new method. When a service is migrated, remove it here and pass it into -// the new impl macro from the 'application' layer. -// -// You should not add any new types here. Instead, implement the embedded_services::relay::mctp::RelayServiceHandler trait for your service. -// -#[allow(deprecated)] -mod legacy_relay { - use super::*; - use embedded_services::relay::mctp::impl_odp_mctp_relay_types; - impl_odp_mctp_relay_types!( - Debug, 0x0A, (comms::EndpointID::Internal(comms::Internal::Debug)), debug_service_messages::DebugRequest, debug_service_messages::DebugResult; - ); -} -pub(crate) use legacy_relay::*; diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs index ff7f7fe8f..578551c5c 100644 --- a/espi-service/src/task.rs +++ b/espi-service/src/task.rs @@ -1,12 +1,7 @@ use crate::Service; -// TODO: We currently require that the service has lifetime 'static because we still communicate with -// some services over the legacy comms system, which requires that things that interact with it -// have lifetime 'static. Once we've fully transitioned to the direct async call method of interfacing -// between services, we should be able to relax this requirement to just require that the service has -// the same lifetime as the services it's communicating with. -pub async fn espi_service( - espi_service: &'static Service, +pub async fn espi_service<'hw, R: embedded_services::relay::mctp::RelayHandler>( + espi_service: &'hw Service<'hw, R>, ) -> Result { espi_service.run_service().await } diff --git a/examples/std/src/bin/debug.rs b/examples/std/src/bin/debug.rs index 8899d3710..20e927c69 100644 --- a/examples/std/src/bin/debug.rs +++ b/examples/std/src/bin/debug.rs @@ -1,6 +1,5 @@ /// This example is supposed to init a debug service and a mock eSPI service to demonstrate sending defmt messages from the debug service to the eSPI service use embassy_executor::{Executor, Spawner}; -use embedded_services::comms::{Endpoint, EndpointID, External}; use embedded_services::info; use static_cell::StaticCell; @@ -181,7 +180,7 @@ async fn init_task(spawner: Spawner) { #[embassy_executor::task] async fn debug_service() -> ! { - debug_service::task::debug_service(Endpoint::uninit(EndpointID::External(External::Host))).await; + debug_service::task::debug_service().await; unreachable!() } From 451c75f76a460dfd247b16e347d084905895fc4a Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:19:25 -0800 Subject: [PATCH 25/79] power-policy-service: Refactor module structure and renaming (#711) Refactor the power policy service to achieve the following: * Move power policy and type-C code out of `embedded-service` and into their respective service implementations * Introduce `Psu` name instead of `Device`. --- Cargo.lock | 25 +- Cargo.toml | 2 + battery-service-messages/Cargo.toml | 8 +- battery-service/Cargo.toml | 3 + battery-service/src/acpi.rs | 4 +- battery-service/src/context.rs | 8 +- battery-service/src/lib.rs | 5 +- embedded-service/Cargo.toml | 1 - embedded-service/src/lib.rs | 3 - embedded-service/src/power/mod.rs | 3 - embedded-service/src/power/policy/charger.rs | 246 ------- embedded-service/src/power/policy/mod.rs | 161 ----- embedded-service/src/type_c/mod.rs | 71 -- examples/pico-de-gallo/Cargo.lock | 424 ++---------- examples/rt633/Cargo.lock | 623 ++++-------------- examples/rt633/Cargo.toml | 3 + examples/rt685s-evk/Cargo.lock | 23 +- examples/rt685s-evk/Cargo.toml | 3 + examples/rt685s-evk/src/bin/type_c.rs | 57 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 51 +- examples/std/Cargo.lock | 24 +- examples/std/Cargo.toml | 3 + examples/std/src/bin/power_policy.rs | 93 +-- examples/std/src/bin/type_c/basic.rs | 4 +- examples/std/src/bin/type_c/external.rs | 26 +- examples/std/src/bin/type_c/service.rs | 54 +- examples/std/src/bin/type_c/ucsi.rs | 59 +- examples/std/src/bin/type_c/unconstrained.rs | 69 +- .../std/src/lib/type_c/mock_controller.rs | 31 +- power-policy-interface/Cargo.toml | 25 + .../src/capability.rs | 104 ++- .../src/charger.rs | 288 +++++++- power-policy-interface/src/lib.rs | 6 + power-policy-interface/src/psu/event.rs | 55 ++ .../src/psu/mod.rs | 72 +- power-policy-interface/src/service/event.rs | 29 + power-policy-interface/src/service/mod.rs | 21 + power-policy-service/Cargo.toml | 6 + power-policy-service/src/lib.rs | 216 +----- .../src/{ => service}/config.rs | 2 +- .../src/{ => service}/consumer.rs | 35 +- .../src/service/context.rs | 142 ++-- power-policy-service/src/service/mod.rs | 220 +++++++ .../src/{ => service}/provider.rs | 35 +- .../src/{ => service}/task.rs | 16 +- power-policy-service/tests/common/mock.rs | 12 +- power-policy-service/tests/common/mod.rs | 33 +- power-policy-service/tests/consumer.rs | 15 +- time-alarm-service-messages/Cargo.toml | 2 +- type-c-service/Cargo.toml | 7 + type-c-service/src/driver/tps6699x.rs | 33 +- type-c-service/src/lib.rs | 7 +- type-c-service/src/service/controller.rs | 12 +- type-c-service/src/service/mod.rs | 45 +- type-c-service/src/service/port.rs | 14 +- type-c-service/src/service/power.rs | 8 +- type-c-service/src/service/vdm.rs | 2 +- type-c-service/src/task.rs | 20 +- .../src/type_c/comms.rs | 0 .../src/type_c/controller.rs | 11 +- .../src/type_c/event.rs | 2 +- .../src/type_c/external.rs | 13 +- type-c-service/src/type_c/mod.rs | 68 ++ type-c-service/src/wrapper/backing.rs | 142 ++-- type-c-service/src/wrapper/cfu.rs | 15 +- type-c-service/src/wrapper/dp.rs | 7 +- type-c-service/src/wrapper/message.rs | 21 +- type-c-service/src/wrapper/mod.rs | 41 +- type-c-service/src/wrapper/pd.rs | 15 +- type-c-service/src/wrapper/power.rs | 37 +- type-c-service/src/wrapper/proxy.rs | 17 +- type-c-service/src/wrapper/vdm.rs | 20 +- 72 files changed, 1698 insertions(+), 2280 deletions(-) delete mode 100644 embedded-service/src/power/mod.rs delete mode 100644 embedded-service/src/power/policy/charger.rs delete mode 100644 embedded-service/src/power/policy/mod.rs delete mode 100644 embedded-service/src/type_c/mod.rs create mode 100644 power-policy-interface/Cargo.toml rename embedded-service/src/power/policy/flags.rs => power-policy-interface/src/capability.rs (66%) rename {power-policy-service => power-policy-interface}/src/charger.rs (50%) create mode 100644 power-policy-interface/src/lib.rs create mode 100644 power-policy-interface/src/psu/event.rs rename embedded-service/src/power/policy/device.rs => power-policy-interface/src/psu/mod.rs (83%) create mode 100644 power-policy-interface/src/service/event.rs create mode 100644 power-policy-interface/src/service/mod.rs rename power-policy-service/src/{ => service}/config.rs (95%) rename power-policy-service/src/{ => service}/consumer.rs (91%) rename embedded-service/src/power/policy/policy.rs => power-policy-service/src/service/context.rs (56%) create mode 100644 power-policy-service/src/service/mod.rs rename power-policy-service/src/{ => service}/provider.rs (81%) rename power-policy-service/src/{ => service}/task.rs (76%) rename {embedded-service => type-c-service}/src/type_c/comms.rs (100%) rename {embedded-service => type-c-service}/src/type_c/controller.rs (99%) rename {embedded-service => type-c-service}/src/type_c/event.rs (99%) rename {embedded-service => type-c-service}/src/type_c/external.rs (98%) create mode 100644 type-c-service/src/type_c/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 24d0c21b2..1b77a98e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "power-policy-interface", "zerocopy", ] @@ -231,6 +232,7 @@ dependencies = [ "defmt 0.3.100", "embedded-batteries-async", "embedded-services", + "log", "num_enum", ] @@ -1002,7 +1004,6 @@ dependencies = [ "embassy-sync", "embassy-time", "embassy-time-driver", - "embedded-batteries-async", "embedded-cfu-protocol", "embedded-hal-async", "embedded-io", @@ -1839,19 +1840,38 @@ dependencies = [ "log", ] +[[package]] +name = "power-policy-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embassy-futures", + "embassy-sync", + "embedded-batteries-async", + "embedded-services", + "heapless", + "log", + "num_enum", +] + [[package]] name = "power-policy-service" version = "0.1.0" dependencies = [ + "bitfield 0.17.0", "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", + "embedded-batteries-async", "embedded-services", "env_logger", "heapless", "log", + "num_enum", + "power-policy-interface", "static_cell", "tokio", ] @@ -2492,6 +2512,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "bitvec", "cfu-service", "critical-section", "defmt 0.3.100", @@ -2507,6 +2528,8 @@ dependencies = [ "embedded-usb-pd", "heapless", "log", + "power-policy-interface", + "power-policy-service", "static_cell", "tokio", "tps6699x", diff --git a/Cargo.toml b/Cargo.toml index 06f23b96d..eadaac9e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "debug-service", "debug-service-messages", "keyboard-service", + "power-policy-interface", ] exclude = ["examples/*"] @@ -86,6 +87,7 @@ embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb mctp-rs = { git = "https://github.com/dymk/mctp-rs" } num_enum = { version = "0.7.5", default-features = false } portable-atomic = { version = "1.11", default-features = false } +power-policy-interface = { path = "./power-policy-interface" } paste = "1.0.15" fixed = "1.23.1" heapless = "0.8.*" diff --git a/battery-service-messages/Cargo.toml b/battery-service-messages/Cargo.toml index 86d56e089..e771e16f4 100644 --- a/battery-service-messages/Cargo.toml +++ b/battery-service-messages/Cargo.toml @@ -7,6 +7,7 @@ repository.workspace = true [dependencies] defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } embedded-batteries-async.workspace = true embedded-services.workspace = true num_enum.workspace = true @@ -15,4 +16,9 @@ num_enum.workspace = true workspace = true [features] -defmt = ["dep:defmt", "embedded-batteries-async/defmt"] +defmt = [ + "dep:defmt", + "embedded-services/defmt", + "embedded-batteries-async/defmt", +] +log = ["dep:log", "embedded-services/log"] diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 3b3304407..f18b61102 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -24,6 +24,7 @@ log = { workspace = true, optional = true } zerocopy.workspace = true mctp-rs = { workspace = true, features = ["espi"] } heapless.workspace = true +power-policy-interface.workspace = true [features] default = [] @@ -31,6 +32,7 @@ defmt = [ "dep:defmt", "battery-service-messages/defmt", "embedded-services/defmt", + "power-policy-interface/defmt", "embassy-time/defmt", "embassy-sync/defmt", "embedded-batteries-async/defmt", @@ -39,6 +41,7 @@ defmt = [ log = [ "dep:log", "embedded-services/log", + "power-policy-interface/log", "embassy-time/log", "embassy-sync/log", ] diff --git a/battery-service/src/acpi.rs b/battery-service/src/acpi.rs index 910318523..f0656a7db 100644 --- a/battery-service/src/acpi.rs +++ b/battery-service/src/acpi.rs @@ -2,13 +2,15 @@ use core::ops::Deref; use embedded_batteries_async::acpi::{PowerSourceState, PowerUnit}; -use embedded_services::{info, power::policy::PowerCapability, trace}; +use embedded_services::{info, trace}; use battery_service_messages::{ AcpiBatteryResponse, BixFixedStrings, DeviceId, PifFixedStrings, STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, }; +use power_policy_interface::capability::PowerCapability; + use crate::{ AcpiBatteryError, context::PsuState, diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index d02867955..0b8f77169 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -8,8 +8,8 @@ use embassy_sync::mutex::Mutex; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::comms::MailboxDelegateError; -use embedded_services::power::policy::PowerCapability; use embedded_services::{IntrusiveList, debug, error, info, intrusive_list, trace, warn}; +use power_policy_interface::capability::PowerCapability; use core::ops::DerefMut; use core::sync::atomic::AtomicUsize; @@ -519,7 +519,7 @@ impl Context { pub(crate) fn set_power_info( &self, - power_info: &embedded_services::power::policy::CommsData, + power_info: &power_policy_interface::service::event::CommsData, ) -> Result<(), MailboxDelegateError> { let mut guard = self .power_info @@ -529,13 +529,13 @@ impl Context { let psu_state = guard.deref_mut(); match power_info { - embedded_services::power::policy::CommsData::ConsumerDisconnected(_) => { + power_policy_interface::service::event::CommsData::ConsumerDisconnected(_) => { *psu_state = PsuState { psu_connected: false, power_capability: None, } } - embedded_services::power::policy::CommsData::ConsumerConnected(_device_id, power_capability) => { + power_policy_interface::service::event::CommsData::ConsumerConnected(_device_id, power_capability) => { *psu_state = PsuState { psu_connected: true, power_capability: Some(power_capability.capability), diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index 60e7edb26..dd3cf76ae 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -126,7 +126,10 @@ impl comms::MailboxDelegate for Service { self.context.send_event_no_wait(*event).map_err(|e| match e { embassy_sync::channel::TrySendError::Full(_) => comms::MailboxDelegateError::BufferFull, })? - } else if let Some(power_policy_msg) = message.data.get::() { + } else if let Some(power_policy_msg) = message + .data + .get::() + { self.context.set_power_info(&power_policy_msg.data)?; } diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index 5e83b291d..8185264d0 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -22,7 +22,6 @@ document-features.workspace = true embassy-sync.workspace = true embassy-time.workspace = true embassy-futures.workspace = true -embedded-batteries-async.workspace = true embedded-cfu-protocol.workspace = true embedded-hal-async.workspace = true embedded-io-async.workspace = true diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index f20d6e892..c53aba536 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -21,10 +21,8 @@ pub mod hid; pub mod init; pub mod ipc; pub mod keyboard; -pub mod power; pub mod relay; pub mod sync; -pub mod type_c; /// Hidden re-exports used by macros defined in this crate. /// Not part of the public API — do not depend on these directly. @@ -85,5 +83,4 @@ pub async fn init() { comms::init(); activity::init(); keyboard::init(); - power::policy::init(); } diff --git a/embedded-service/src/power/mod.rs b/embedded-service/src/power/mod.rs deleted file mode 100644 index 962fba0b9..000000000 --- a/embedded-service/src/power/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Module for anything power related -#[allow(clippy::module_inception)] -pub mod policy; diff --git a/embedded-service/src/power/policy/charger.rs b/embedded-service/src/power/policy/charger.rs deleted file mode 100644 index d589c66d9..000000000 --- a/embedded-service/src/power/policy/charger.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Charger device struct and controller -use core::{future::Future, ops::DerefMut}; - -use embassy_sync::{channel::Channel, mutex::Mutex}; - -use crate::{ - GlobalRawMutex, intrusive_list, - power::{self, policy::ConsumerPowerCapability}, -}; - -use super::PowerCapability; - -/// Charger controller trait that device drivers may use to integrate with internal messaging system -pub trait ChargeController: embedded_batteries_async::charger::Charger { - /// Type of error returned by the bus - type ChargeControllerError; - - /// Returns with pending events - fn wait_event(&mut self) -> impl Future; - /// Initialize charger hardware, after this returns the charger should be ready to charge - fn init_charger(&mut self) -> impl Future>; - /// Returns if the charger hardware detects if a PSU is attached - fn is_psu_attached(&mut self) -> impl Future>; - /// Called after power policy attaches to a power port. - fn attach_handler( - &mut self, - capability: ConsumerPowerCapability, - ) -> impl Future>; - /// Called after power policy detaches from a power port, either to switch consumers, - /// or because PSU was disconnected. - fn detach_handler(&mut self) -> impl Future>; - /// Called when a charger CheckReady request (PolicyEvent::CheckReady) is sent to the power policy. - /// Upon successful return of this method, the charger is assumed to be powered and ready to communicate, - /// transitioning state from unpowered to powered. - /// - /// If the charger is powered, an Ok(()) does nothing. An Err(_) will put the charger into an - /// unpowered state, meaning another PolicyEvent::CheckReady must be sent to re-establish communications - /// with the charger. Upon successful return, the charger must be re-initialized by sending a - /// `PolicyEvent::InitRequest`. - fn is_ready(&mut self) -> impl Future> { - core::future::ready(Ok(())) - } -} - -/// Charger Device ID new type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ChargerId(pub u8); - -/// PSU state as determined by charger device -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PsuState { - /// Charger detected PSU attached - Attached, - /// Charger detected PSU detached - Detached, -} - -impl From for PsuState { - fn from(value: bool) -> Self { - match value { - true => PsuState::Attached, - false => PsuState::Detached, - } - } -} - -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ChargerEvent { - /// Charger finished initialization sequence - Initialized(PsuState), - /// PSU state changed - PsuStateChange(PsuState), - /// A timeout of some sort was detected - Timeout, - /// An error occured on the bus - BusError, -} - -/// Charger state errors -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ChargerError { - /// Charger received command in an invalid state - InvalidState(State), - /// Charger hardware timed out responding - Timeout, - /// Charger underlying bus error - BusError, -} - -impl From for power::policy::Error { - fn from(value: ChargerError) -> Self { - Self::Charger(value) - } -} - -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PolicyEvent { - /// Request to initialize charger hardware - InitRequest, - /// New power policy detected - PolicyConfiguration(ConsumerPowerCapability), - /// Request to check if the charger hardware is ready to receive communications. - /// For example, if the charger is powered. - CheckReady, -} - -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ChargerResponseData { - /// Command completed - Ack, - /// Charger Unpowered, but we are still Ok - UnpoweredAck, -} - -/// Response for charger requests from policy commands -pub type ChargerResponse = Result; - -/// Current state of the charger -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { - /// Device is unpowered - Unpowered, - /// Device is powered - Powered(PoweredSubstate), -} - -/// Powered state substates -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PoweredSubstate { - /// Device is initializing - Init, - /// PSU is attached and device can charge if desired - PsuAttached, - /// PSU is detached - PsuDetached, -} - -/// Current state of the charger -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InternalState { - /// Charger device state - pub state: State, - /// Current charger capability - pub capability: Option, -} - -/// Channel size for device requests -pub const CHARGER_CHANNEL_SIZE: usize = 1; - -/// Device struct -pub struct Device { - /// Intrusive list node - node: intrusive_list::Node, - /// Device ID - id: ChargerId, - /// Current state of the device - state: Mutex, - /// Channel for requests to the device - commands: Channel, - /// Channel for responses from the device - response: Channel, -} - -impl Device { - /// Create a new device - pub fn new(id: ChargerId) -> Self { - Self { - node: intrusive_list::Node::uninit(), - id, - state: Mutex::new(InternalState { - state: State::Unpowered, - capability: None, - }), - commands: Channel::new(), - response: Channel::new(), - } - } - - /// Get the device ID - pub fn id(&self) -> ChargerId { - self.id - } - - /// Returns the current state of the device - pub async fn state(&self) -> InternalState { - *self.state.lock().await - } - - /// Set the state of the device - pub async fn set_state(&self, new_state: InternalState) { - let mut lock = self.state.lock().await; - let current_state = lock.deref_mut(); - *current_state = new_state; - } - - /// Wait for a command from policy - pub async fn wait_command(&self) -> PolicyEvent { - self.commands.receive().await - } - - /// Send a command to the charger - pub async fn send_command(&self, policy_event: PolicyEvent) { - self.commands.send(policy_event).await - } - - /// Send a response to the power policy - pub async fn send_response(&self, response: ChargerResponse) { - self.response.send(response).await - } - - /// Send a command and wait for a response from the charger - pub async fn execute_command(&self, policy_event: PolicyEvent) -> ChargerResponse { - self.send_command(policy_event).await; - self.response.receive().await - } -} - -impl intrusive_list::NodeContainer for Device { - fn get_node(&self) -> &crate::Node { - &self.node - } -} - -/// Trait for any container that holds a device -pub trait ChargerContainer { - /// Get the underlying device struct - fn get_charger(&self) -> &Device; -} - -impl ChargerContainer for Device { - fn get_charger(&self) -> &Device { - self - } -} diff --git a/embedded-service/src/power/policy/mod.rs b/embedded-service/src/power/policy/mod.rs deleted file mode 100644 index 1539d4dd7..000000000 --- a/embedded-service/src/power/policy/mod.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Power policy related data structures and messages -pub mod charger; -pub mod device; -pub mod flags; -pub mod policy; - -pub use policy::init; - -use crate::power::policy::charger::ChargerError; - -/// Error type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// The requested device does not exist - InvalidDevice, - /// The provide request was denied, contains maximum available power - CannotProvide(Option), - /// The consume request was denied, contains maximum available power - CannotConsume(Option), - /// The device is not in the correct state (expected, actual) - InvalidState(&'static [device::StateKind], device::StateKind), - /// Invalid response - InvalidResponse, - /// Busy, the device cannot respond to the request at this time - Busy, - /// Timeout - Timeout, - /// Bus error - Bus, - /// Charger specific error, underlying error should have more context - Charger(ChargerError), - /// Generic failure - Failed, -} - -/// Device ID new type -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - -/// Amount of power that a device can provider or consume -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PowerCapability { - /// Available voltage in mV - pub voltage_mv: u16, - /// Max available current in mA - pub current_ma: u16, -} - -impl PowerCapability { - /// Calculate maximum power - pub fn max_power_mw(&self) -> u32 { - self.voltage_mv as u32 * self.current_ma as u32 / 1000 - } -} - -impl PartialOrd for PowerCapability { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PowerCapability { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.max_power_mw().cmp(&other.max_power_mw()) - } -} - -/// Power capability with consumer flags -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ConsumerPowerCapability { - /// Power capability - pub capability: PowerCapability, - /// Consumer flags - pub flags: flags::Consumer, -} - -impl From for ConsumerPowerCapability { - fn from(capability: PowerCapability) -> Self { - Self { - capability, - flags: flags::Consumer::none(), - } - } -} - -/// Power capability with provider flags -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ProviderPowerCapability { - /// Power capability - pub capability: PowerCapability, - /// Provider flags - pub flags: flags::Provider, -} - -impl From for ProviderPowerCapability { - fn from(capability: PowerCapability) -> Self { - Self { - capability, - flags: flags::Provider::none(), - } - } -} - -/// Combined power capability with flags enum -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PowerCapabilityFlags { - /// Consumer flags - Consumer(ConsumerPowerCapability), - /// Provider flags - Provider(ProviderPowerCapability), -} - -/// Unconstrained state information -#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct UnconstrainedState { - /// Unconstrained state - pub unconstrained: bool, - /// Available unconstrained devices - pub available: usize, -} - -impl UnconstrainedState { - /// Create a new unconstrained state - pub fn new(unconstrained: bool, available: usize) -> Self { - Self { - unconstrained, - available, - } - } -} - -/// Data to send with the comms service -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CommsData { - /// Consumer disconnected - ConsumerDisconnected(DeviceId), - /// Consumer connected - ConsumerConnected(DeviceId, ConsumerPowerCapability), - /// Provider disconnected - ProviderDisconnected(DeviceId), - /// Provider connected - ProviderConnected(DeviceId, ProviderPowerCapability), - /// Unconstrained state changed - Unconstrained(UnconstrainedState), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Message to send with the comms service -pub struct CommsMessage { - /// Message data - pub data: CommsData, -} diff --git a/embedded-service/src/type_c/mod.rs b/embedded-service/src/type_c/mod.rs deleted file mode 100644 index 2943acddb..000000000 --- a/embedded-service/src/type_c/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Type-C service -use embedded_usb_pd::pdo::{Common, Contract}; -use embedded_usb_pd::type_c; - -use crate::error; -use crate::power::policy; - -pub mod comms; -pub mod controller; -pub mod event; -pub mod external; - -/// Controller ID -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerId(pub u8); - -/// Length of the Other VDM data -pub const OTHER_VDM_LEN: usize = 29; -/// Length of the Attention VDM data -pub const ATTN_VDM_LEN: usize = 9; - -impl TryFrom for policy::PowerCapability { - type Error = (); - - fn try_from(contract: Contract) -> Result { - Ok(policy::PowerCapability { - voltage_mv: contract.pdo.max_voltage_mv(), - current_ma: contract.operating_current_ma().ok_or(())?, - }) - } -} - -impl From for policy::PowerCapability { - fn from(current: type_c::Current) -> Self { - policy::PowerCapability { - voltage_mv: 5000, - // Assume higher power for now - current_ma: current.to_ma(false), - } - } -} - -/// Type-C USB2 power capability 5V@500mA -pub const POWER_CAPABILITY_USB_DEFAULT_USB2: policy::PowerCapability = policy::PowerCapability { - voltage_mv: 5000, - current_ma: 500, -}; - -/// Type-C USB3 power capability 5V@900mA -pub const POWER_CAPABILITY_USB_DEFAULT_USB3: policy::PowerCapability = policy::PowerCapability { - voltage_mv: 5000, - current_ma: 900, -}; - -/// Type-C power capability 5V@1.5A -pub const POWER_CAPABILITY_5V_1A5: policy::PowerCapability = policy::PowerCapability { - voltage_mv: 5000, - current_ma: 1500, -}; - -/// Type-C power capability 5V@3A -pub const POWER_CAPABILITY_5V_3A0: policy::PowerCapability = policy::PowerCapability { - voltage_mv: 5000, - current_ma: 3000, -}; - -/// Newtype to help clarify arguments to port status commands -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Cached(pub bool); diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index dc595eb54..f8e429c6b 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -73,12 +61,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - [[package]] name = "aquamarine" version = "0.6.0" @@ -86,56 +68,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" dependencies = [ "include_dir", - "itertools 0.10.5", + "itertools", "proc-macro-error2", "proc-macro2", "quote", "syn", ] -[[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] -name = "askama" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" -dependencies = [ - "askama_derive", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "syn", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", - "winnow 0.7.14", -] - [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -181,6 +120,7 @@ dependencies = [ "heapless 0.8.0", "log", "mctp-rs", + "power-policy-interface", "zerocopy", ] @@ -255,9 +195,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -273,9 +213,9 @@ dependencies = [ [[package]] name = "bq40z50-rx" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" +checksum = "09b6faf600295f12c3fb99b45266bc9140af5c344b08f2705bc06bfa0e8b549e" dependencies = [ "device-driver", "embassy-time", @@ -293,15 +233,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.54" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -328,15 +268,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "cordyceps" version = "0.3.4" @@ -401,55 +332,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" -[[package]] -name = "dd-manifest-tree" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" -dependencies = [ - "yaml-rust2", -] - [[package]] name = "device-driver" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ - "device-driver-macros", "embedded-io 0.6.1", "embedded-io-async", ] -[[package]] -name = "device-driver-generation" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3935aec9cf5bb2ab927f59ca69faecf976190390b0ce34c6023889e9041040c0" -dependencies = [ - "anyhow", - "askama", - "bitvec", - "convert_case", - "dd-manifest-tree", - "itertools 0.14.0", - "kdl", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "device-driver-macros" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdc68ed515c4eddff2e95371185b4becba066085bf36d50f07f09782af98e17" -dependencies = [ - "device-driver-generation", - "proc-macro2", - "syn", -] - [[package]] name = "document-features" version = "0.2.12" @@ -628,7 +520,6 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", "embedded-cfu-protocol", "embedded-hal-async", "embedded-io 0.6.1", @@ -661,20 +552,11 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", @@ -682,9 +564,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -718,9 +600,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "funty" @@ -730,15 +612,15 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "generator" @@ -773,24 +655,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown", -] - [[package]] name = "heapless" version = "0.7.17" @@ -865,26 +729,11 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "b3e3d65f018c6ae946ab16e80944b97096ed73c35b221d1c478a6c81d8f57940" dependencies = [ "jiff-static", "log", @@ -895,26 +744,15 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "a17c2b211d863c7fde02cbea8a3c1a439b98e109286554f2860bdded7ff83818" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "kdl" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a29e7b50079ff44549f68c0becb1c73d7f6de2a4ea952da77966daf3d4761e" -dependencies = [ - "miette", - "num", - "winnow 0.6.24", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -923,9 +761,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "linux-raw-sys" @@ -1012,19 +850,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "unicode-width", -] +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mycelium-bitfield" @@ -1056,70 +884,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1187,12 +951,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - [[package]] name = "pico-de-gallo" version = "0.1.0" @@ -1277,15 +1035,15 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -1344,6 +1102,20 @@ dependencies = [ "serde", ] +[[package]] +name = "power-policy-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "embassy-futures", + "embassy-sync", + "embedded-batteries-async", + "embedded-services", + "heapless 0.8.0", + "log", + "num_enum", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1391,9 +1163,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1403,9 +1175,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1414,15 +1186,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc_version" @@ -1524,19 +1290,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -1554,9 +1307,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1627,9 +1380,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1767,21 +1520,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unty" @@ -1813,12 +1554,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "void" version = "1.0.2" @@ -1997,24 +1732,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - [[package]] name = "wyz" version = "0.5.1" @@ -2024,39 +1741,22 @@ dependencies = [ "tap", ] -[[package]] -name = "yaml-rust2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink", -] - [[package]] name = "zerocopy" -version = "0.8.36" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.36" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", "syn", ] - -[[package]] -name = "zmij" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 4fb0c9dd6..517cec0af 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -2,24 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - [[package]] name = "aquamarine" version = "0.6.0" @@ -31,50 +13,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] -name = "askama" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" -dependencies = [ - "askama_derive", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "syn 2.0.104", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", - "winnow 0.7.12", + "syn", ] [[package]] @@ -85,9 +24,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "az" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" [[package]] name = "bare-metal" @@ -107,12 +46,13 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", "heapless", "mctp-rs", + "power-policy-interface", "zerocopy", ] @@ -121,7 +61,7 @@ name = "battery-service-messages" version = "0.1.0" dependencies = [ "defmt 0.3.100", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-services", "num_enum", ] @@ -163,22 +103,22 @@ checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" [[package]] name = "bitfield" -version = "0.19.1" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db1bcd90f88eabbf0cadbfb87a45bceeaebcd3b4bc9e43da379cd2ef0162590d" +checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.19.1" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3787a07661997bfc05dd3431e379c0188573f78857080cf682e1393ab8e4d64c" +checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -189,7 +129,7 @@ checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -200,7 +140,7 @@ checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -211,9 +151,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -229,23 +169,23 @@ dependencies = [ [[package]] name = "bq25773" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/bq25773#20bc26219b5372bc6146cdc509c21f6d43e257b3" +version = "0.1.1" +source = "git+https://github.com/OpenDevicePartnership/bq25773#0ca102dec24a617df5762cf0bf4217fd387d5c63" dependencies = [ "device-driver", - "embedded-batteries-async 0.2.1", + "embedded-batteries-async", "embedded-hal 1.0.0", "embedded-hal-async", ] [[package]] name = "bq40z50-rx" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" +checksum = "09b6faf600295f12c3fb99b45266bc9140af5c344b08f2705bc06bfa0e8b549e" dependencies = [ "device-driver", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-hal 1.0.0", "embedded-hal-async", "smbus-pec", @@ -253,9 +193,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -265,18 +205,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "convert_case" -version = "0.6.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cortex-m" @@ -308,7 +239,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -344,7 +275,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn", ] [[package]] @@ -355,16 +286,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", -] - -[[package]] -name = "dd-manifest-tree" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" -dependencies = [ - "yaml-rust2", + "syn", ] [[package]] @@ -405,7 +327,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -414,7 +336,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -429,49 +351,19 @@ dependencies = [ [[package]] name = "device-driver" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1298272ea07037196af2fe8d1eb50792206f45476d79eefa435432b9323cf488" +checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ - "device-driver-macros", "embedded-io", "embedded-io-async", ] -[[package]] -name = "device-driver-generation" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86a17ed060a6119daeb05ad5596ac3bd945f7ab2213cc6260bf6a7623e73da1" -dependencies = [ - "anyhow", - "askama", - "bitvec", - "convert_case", - "dd-manifest-tree", - "itertools 0.14.0", - "kdl", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "device-driver-macros" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c29238099c66bf44098efaa772cae6f47d632aebb7ade8d3087bd565e8fae0" -dependencies = [ - "device-driver-generation", - "proc-macro2", - "syn 2.0.104", -] - [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -523,7 +415,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -645,7 +537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" dependencies = [ "bitfield-struct 0.10.1", - "bitflags 2.9.1", + "bitflags 2.11.0", "defmt 0.3.100", "embedded-hal 1.0.0", "zerocopy", @@ -653,28 +545,17 @@ dependencies = [ [[package]] name = "embedded-batteries" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8996d7168535579180a0eead82efaba718ebd598782f986bfd635458259df2" +checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" dependencies = [ - "bitfield-struct 0.10.1", - "bitflags 2.9.1", + "bitfield-struct 0.12.1", + "bitflags 2.11.0", "defmt 0.3.100", "embedded-hal 1.0.0", "zerocopy", ] -[[package]] -name = "embedded-batteries-async" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" -dependencies = [ - "bitfield-struct 0.10.1", - "embedded-batteries 0.2.1", - "embedded-hal 1.0.0", -] - [[package]] name = "embedded-batteries-async" version = "0.3.4" @@ -683,14 +564,14 @@ checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" dependencies = [ "bitfield-struct 0.12.1", "defmt 0.3.100", - "embedded-batteries 0.3.1", + "embedded-batteries 0.3.4", "embedded-hal 1.0.0", ] [[package]] name = "embedded-cfu-protocol" version = "0.2.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" +source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" dependencies = [ "defmt 0.3.100", "embedded-io-async", @@ -766,7 +647,7 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitflags 2.9.1", + "bitflags 2.11.0", "bitvec", "cfg-if", "cortex-m", @@ -777,7 +658,6 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async 0.3.4", "embedded-cfu-protocol", "embedded-hal-async", "embedded-io", @@ -814,27 +694,18 @@ source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce dependencies = [ "aquamarine", "bincode", - "bitfield 0.19.1", + "bitfield 0.19.4", "defmt 0.3.100", "embedded-hal-async", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#9805f13c044b0e314d415410c57a8a59a40eabeb" dependencies = [ "bit-register", - "bitflags 2.9.1", + "bitflags 2.11.0", "num-traits", "num_enum", "static_assertions", @@ -864,9 +735,9 @@ dependencies = [ [[package]] name = "fixed" -version = "1.29.0" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +checksum = "c566da967934c6c7ee0458a9773de9b2a685bd2ce26a3b28ddfc740e640182f5" dependencies = [ "az", "bytemuck", @@ -888,9 +759,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -902,9 +773,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -912,61 +783,61 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", "futures-sink", "futures-task", "pin-project-lite", - "pin-utils", ] [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -978,24 +849,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown", -] - [[package]] name = "heapless" version = "0.8.0" @@ -1008,9 +861,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "ident_case" @@ -1055,38 +908,11 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "kdl" -version = "6.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661358400b02cbbf1fbd05f0a483335490e8a6bd1867620f2eeb78f304a22f" -dependencies = [ - "miette", - "num", - "thiserror 1.0.69", - "winnow 0.6.24", -] - [[package]] name = "litrs" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "mctp-rs" @@ -1099,35 +925,7 @@ dependencies = [ "espi-device", "num_enum", "smbus-pec", - "thiserror 2.0.16", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "thiserror", ] [[package]] @@ -1180,70 +978,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1271,15 +1005,9 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - [[package]] name = "panic-probe" version = "0.3.2" @@ -1296,12 +1024,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1309,16 +1031,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "portable-atomic" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +name = "power-policy-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embassy-futures", + "embassy-sync", + "embedded-batteries-async", + "embedded-services", + "heapless", + "num_enum", +] [[package]] name = "proc-macro-error-attr2" @@ -1339,23 +1069,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1404,23 +1134,18 @@ dependencies = [ "embassy-imxrt", "embassy-sync", "embassy-time", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-hal-async", "embedded-services", "espi-service", "futures", "mimxrt600-fcb", "panic-probe", + "power-policy-interface", "rand", "static_cell", ] -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.2.3" @@ -1436,12 +1161,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "semver" version = "0.9.0" @@ -1459,34 +1178,32 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "serde_derive", ] [[package]] -name = "serde_json" -version = "1.0.141" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1500,9 +1217,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -1532,32 +1249,21 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subenum" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5d5dfb8556dd04017db5e318bbeac8ab2b0c67b76bf197bfb79e9b29f18ecf" +checksum = "ec3d08fe7078c57309d5c3d938e50eba95ba1d33b9c3a101a8465fc6861a5416" dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "syn", ] [[package]] name = "syn" -version = "2.0.104" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1582,42 +1288,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1634,27 +1320,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unty" @@ -1674,12 +1348,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "void" version = "1.0.2" @@ -1695,24 +1363,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "winnow" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" -dependencies = [ - "memchr", -] - [[package]] name = "wyz" version = "0.5.1" @@ -1722,33 +1372,22 @@ dependencies = [ "tap", ] -[[package]] -name = "yaml-rust2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink", -] - [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] diff --git a/examples/rt633/Cargo.toml b/examples/rt633/Cargo.toml index 64a312b82..cd9682e82 100644 --- a/examples/rt633/Cargo.toml +++ b/examples/rt633/Cargo.toml @@ -49,6 +49,9 @@ mimxrt600-fcb = "0.2.0" rand = { version = "0.8.5", default-features = false } espi-service = { path = "../../espi-service", features = ["defmt"] } embedded-services = { path = "../../embedded-service", features = ["defmt"] } +power-policy-interface = { path = "../../power-policy-interface", features = [ + "defmt", +] } embedded-batteries-async = { version = "0.3", features = ["defmt"] } bq25773 = { git = "https://github.com/OpenDevicePartnership/bq25773" } diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 333705b88..819c5ac34 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -735,7 +735,6 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", "embedded-cfu-protocol", "embedded-hal-async", "embedded-io", @@ -1303,16 +1302,34 @@ dependencies = [ "embedded-hal-async", ] +[[package]] +name = "power-policy-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "defmt 0.3.100", + "embassy-futures", + "embassy-sync", + "embedded-batteries-async", + "embedded-services", + "heapless", + "num_enum", +] + [[package]] name = "power-policy-service" version = "0.1.0" dependencies = [ + "bitfield 0.17.0", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", + "embedded-batteries-async", "embedded-services", "heapless", + "num_enum", + "power-policy-interface", ] [[package]] @@ -1395,6 +1412,7 @@ dependencies = [ "panic-probe", "platform-service", "power-button-service", + "power-policy-interface", "power-policy-service", "static_cell", "time-alarm-service", @@ -1651,6 +1669,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "bitvec", "cfu-service", "defmt 0.3.100", "embassy-futures", @@ -1663,6 +1682,8 @@ dependencies = [ "embedded-services", "embedded-usb-pd", "heapless", + "power-policy-interface", + "power-policy-service", "tps6699x", ] diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 2f3cd6304..4c5310c5d 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -54,6 +54,9 @@ embedded-services = { path = "../../embedded-service", features = ["defmt"] } power-button-service = { path = "../../power-button-service", features = [ "defmt", ] } +power-policy-interface = { path = "../../power-policy-interface", features = [ + "defmt", +] } power-policy-service = { path = "../../power-policy-service", features = [ "defmt", ] } diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 637021f32..e73a71f75 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -15,16 +15,16 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay, Timer}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; -use embedded_services::power::policy::{DeviceId as PowerId, policy}; -use embedded_services::type_c::{Cached, ControllerId}; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; -use power_policy_service::PowerPolicy; +use power_policy_interface::psu::DeviceId as PowerId; +use power_policy_service::service::Service as PowerPolicyService; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; +use type_c_service::type_c::{Cached, ControllerId}; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -58,8 +58,8 @@ type Wrapper<'a> = ControllerWrapper< 'a, GlobalRawMutex, Tps6699xMutex<'a>, - DynamicSender<'a, policy::RequestData>, - DynamicReceiver<'a, policy::RequestData>, + DynamicSender<'a, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'a, power_policy_interface::psu::event::RequestData>, Validator, >; type Controller<'a> = tps6699x::controller::Controller>; @@ -81,14 +81,14 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_service_task( - service: &'static PowerPolicy< + service: &'static PowerPolicyService< 'static, Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { Timer::after_millis(100).await; // Give some time for other tasks to start - power_policy_service::task::task(service) + power_policy_service::service::task::task(service) .await .expect("Failed to start power policy service task"); } @@ -97,9 +97,9 @@ async fn power_policy_service_task( async fn type_c_service_task( service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static policy::Context< + power_policy_context: &'static power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, cfu_client: &'static CfuClient, ) { @@ -149,26 +149,26 @@ async fn main(spawner: Spawner) { // Create power policy service static POWER_SERVICE_CONTEXT: StaticCell< - policy::Context< + power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); static POWER_SERVICE: StaticCell< - power_policy_service::PowerPolicy< + power_policy_service::service::Service< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( power_service_context, - power_policy_service::config::Config::default(), + power_policy_service::service::config::Config::default(), )); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); @@ -188,12 +188,14 @@ async fn main(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = + StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL1: StaticCell> = + StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); @@ -202,8 +204,8 @@ async fn main(spawner: Spawner) { ReferencedStorage< TPS66994_NUM_PORTS, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced = REFERENCED.init( @@ -225,7 +227,7 @@ async fn main(spawner: Spawner) { // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -247,7 +249,12 @@ async fn main(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], power_service_context, cfu_client)); + spawner.must_spawn(type_c_service_task( + type_c_service, + [wrapper], + power_service_context, + cfu_client, + )); info!("Spawining power policy task"); spawner.must_spawn(power_policy_service_task(power_service)); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 8a4d143d0..f0c5a9e6b 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -18,17 +18,16 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::power::policy::DeviceId as PowerId; -use embedded_services::power::policy::policy; -use embedded_services::type_c::ControllerId; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; -use power_policy_service::PowerPolicy; +use power_policy_interface::psu::DeviceId as PowerId; +use power_policy_service::service::Service as PowerPolicyService; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; +use type_c_service::type_c::ControllerId; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -55,8 +54,8 @@ type Wrapper<'a> = ControllerWrapper< 'a, GlobalRawMutex, Tps6699xMutex<'a>, - DynamicSender<'a, policy::RequestData>, - DynamicReceiver<'a, policy::RequestData>, + DynamicSender<'a, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'a, power_policy_interface::psu::event::RequestData>, Validator, >; type Controller<'a> = tps6699x::controller::Controller>; @@ -166,14 +165,14 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_service_task( - service: &'static PowerPolicy< + service: &'static PowerPolicyService< 'static, Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { Timer::after_millis(100).await; // Give some time for other tasks to start - power_policy_service::task::task(service) + power_policy_service::service::task::task(service) .await .expect("Failed to start power policy service task"); } @@ -182,9 +181,9 @@ async fn power_policy_service_task( async fn type_c_service_task( service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static policy::Context< + power_policy_context: &'static power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, cfu_client: &'static CfuClient, ) { @@ -234,26 +233,26 @@ async fn main(spawner: Spawner) { // Create power policy service static POWER_SERVICE_CONTEXT: StaticCell< - policy::Context< + power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); static POWER_SERVICE: StaticCell< - power_policy_service::PowerPolicy< + power_policy_service::service::Service< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( power_service_context, - power_policy_service::config::Config::default(), + power_policy_service::service::config::Config::default(), )); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); @@ -273,12 +272,14 @@ async fn main(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = + StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL1: StaticCell> = + StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); @@ -287,8 +288,8 @@ async fn main(spawner: Spawner) { ReferencedStorage< TPS66994_NUM_PORTS, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced = REFERENCED.init( @@ -310,7 +311,7 @@ async fn main(spawner: Spawner) { // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index f2659ecf2..92cb2bfec 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -166,6 +166,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "power-policy-interface", "zerocopy", ] @@ -796,7 +797,6 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", "embedded-cfu-protocol", "embedded-hal-async", "embedded-io", @@ -1384,16 +1384,34 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "power-policy-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "embassy-futures", + "embassy-sync", + "embedded-batteries-async", + "embedded-services", + "heapless", + "log", + "num_enum", +] + [[package]] name = "power-policy-service" version = "0.1.0" dependencies = [ + "bitfield 0.17.0", "embassy-futures", "embassy-sync", "embassy-time", + "embedded-batteries-async", "embedded-services", "heapless", "log", + "num_enum", + "power-policy-interface", ] [[package]] @@ -1649,6 +1667,7 @@ dependencies = [ "env_logger", "heapless", "log", + "power-policy-interface", "power-policy-service", "static_cell", "thermal-service", @@ -1865,6 +1884,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", + "bitvec", "cfu-service", "embassy-futures", "embassy-sync", @@ -1877,6 +1897,8 @@ dependencies = [ "embedded-usb-pd", "heapless", "log", + "power-policy-interface", + "power-policy-service", "tps6699x", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index ef102cee3..8c661ca95 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -29,6 +29,9 @@ embedded-services = { path = "../../embedded-service", features = ["log"] } power-policy-service = { path = "../../power-policy-service", features = [ "log", ] } +power-policy-interface = { path = "../../power-policy-interface", features = [ + "log", +] } cfu-service = { path = "../../cfu-service", features = ["log"] } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 50d0030a1..f6150926a 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -6,18 +6,12 @@ use embassy_sync::{ pubsub::PubSubChannel, }; use embassy_time::{self as _, Timer}; -use embedded_services::{ - GlobalRawMutex, - broadcaster::immediate as broadcaster, - power::{ - self, - policy::{ - self, ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability, device::DeviceTrait, flags, - }, - }, -}; +use embedded_services::{GlobalRawMutex, broadcaster::immediate as broadcaster}; use log::*; -use power_policy_service::PowerPolicy; +use power_policy_interface::capability::{ + ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability, +}; +use power_policy_interface::psu::{Error, Psu}; use static_cell::StaticCell; const LOW_POWER: PowerCapability = PowerCapability { @@ -30,32 +24,36 @@ const HIGH_POWER: PowerCapability = PowerCapability { current_ma: 3000, }; -const DEVICE0_ID: policy::DeviceId = policy::DeviceId(0); -const DEVICE1_ID: policy::DeviceId = policy::DeviceId(1); +const DEVICE0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); +const DEVICE1_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(1); const PER_CALL_DELAY_MS: u64 = 1000; struct ExampleDevice<'a> { - sender: channel::DynamicSender<'a, policy::policy::RequestData>, + sender: channel::DynamicSender<'a, power_policy_interface::psu::event::RequestData>, } impl<'a> ExampleDevice<'a> { - fn new(sender: channel::DynamicSender<'a, policy::policy::RequestData>) -> Self { + fn new(sender: channel::DynamicSender<'a, power_policy_interface::psu::event::RequestData>) -> Self { Self { sender } } pub async fn simulate_attach(&mut self) { - self.sender.send(policy::policy::RequestData::Attached).await; + self.sender + .send(power_policy_interface::psu::event::RequestData::Attached) + .await; } pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option) { self.sender - .send(policy::policy::RequestData::UpdatedConsumerCapability(capability)) + .send(power_policy_interface::psu::event::RequestData::UpdatedConsumerCapability(capability)) .await; } pub async fn simulate_detach(&mut self) { - self.sender.send(policy::policy::RequestData::Detached).await; + self.sender + .send(power_policy_interface::psu::event::RequestData::Detached) + .await; } pub async fn simulate_update_requested_provider_power_capability( @@ -63,12 +61,12 @@ impl<'a> ExampleDevice<'a> { capability: Option, ) { self.sender - .send(policy::policy::RequestData::RequestedProviderCapability(capability)) + .send(power_policy_interface::psu::event::RequestData::RequestedProviderCapability(capability)) .await } } -impl DeviceTrait for ExampleDevice<'_> { +impl Psu for ExampleDevice<'_> { async fn disconnect(&mut self) -> Result<(), Error> { debug!("ExampleDevice disconnect"); Ok(()) @@ -90,61 +88,63 @@ async fn run(spawner: Spawner) { embedded_services::init().await; info!("Creating device 0"); - static DEVICE0_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + static DEVICE0_EVENT_CHANNEL: StaticCell> = + StaticCell::new(); let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); static DEVICE0: StaticCell> = StaticCell::new(); let device0 = DEVICE0.init(Mutex::new(ExampleDevice::new(device0_event_channel.dyn_sender()))); static DEVICE0_REGISTRATION: StaticCell< - policy::device::Device< + power_policy_interface::psu::RegistrationEntry< 'static, Mutex, - channel::DynamicReceiver<'static, policy::policy::RequestData>, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let device0_registration = DEVICE0_REGISTRATION.init(policy::device::Device::new( + let device0_registration = DEVICE0_REGISTRATION.init(power_policy_interface::psu::RegistrationEntry::new( DEVICE0_ID, device0, device0_event_channel.dyn_receiver(), )); info!("Creating device 1"); - static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + static DEVICE1_EVENT_CHANNEL: StaticCell> = + StaticCell::new(); let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); static DEVICE1: StaticCell> = StaticCell::new(); let device1 = DEVICE1.init(Mutex::new(ExampleDevice::new(device1_event_channel.dyn_sender()))); static DEVICE1_REGISTRATION: StaticCell< - policy::device::Device< + power_policy_interface::psu::RegistrationEntry< 'static, Mutex, - channel::DynamicReceiver<'static, policy::policy::RequestData>, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let device1_registration = DEVICE1_REGISTRATION.init(policy::device::Device::new( + let device1_registration = DEVICE1_REGISTRATION.init(power_policy_interface::psu::RegistrationEntry::new( DEVICE1_ID, device1, device1_event_channel.dyn_receiver(), )); static SERVICE_CONTEXT: StaticCell< - power::policy::policy::Context< + power_policy_service::service::context::Context< Mutex>, - channel::DynamicReceiver<'static, policy::policy::RequestData>, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let service_context = SERVICE_CONTEXT.init(power::policy::policy::Context::new()); + let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - service_context.register_device(device0_registration).unwrap(); - service_context.register_device(device1_registration).unwrap(); + service_context.register_psu(device0_registration).unwrap(); + service_context.register_psu(device1_registration).unwrap(); static SERVICE: StaticCell< - power_policy_service::PowerPolicy< + power_policy_service::service::Service< Mutex>, - channel::DynamicReceiver<'static, policy::policy::RequestData>, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let service = SERVICE.init(power_policy_service::PowerPolicy::new( + let service = SERVICE.init(power_policy_service::service::Service::new( service_context, - power_policy_service::config::Config::default(), + power_policy_service::service::config::Config::default(), )); spawner.must_spawn(power_policy_task(service)); @@ -157,7 +157,7 @@ async fn run(spawner: Spawner) { dev0.simulate_attach().await; dev0.simulate_update_consumer_power_capability(Some(ConsumerPowerCapability { capability: LOW_POWER, - flags: flags::Consumer::none().with_unconstrained_power(), + flags: ConsumerFlags::none().with_unconstrained_power(), })) .await; } @@ -272,19 +272,22 @@ async fn run(spawner: Spawner) { #[embassy_executor::task] async fn receiver_task( - service: &'static power_policy_service::PowerPolicy< + service: &'static power_policy_service::service::Service< 'static, Mutex>, - channel::DynamicReceiver<'static, policy::policy::RequestData>, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { - static CHANNEL: StaticCell> = StaticCell::new(); + static CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); let channel = CHANNEL.init(PubSubChannel::new()); let publisher = channel.dyn_immediate_publisher(); let mut subscriber = channel.dyn_subscriber().unwrap(); - static RECEIVER: StaticCell> = StaticCell::new(); + static RECEIVER: StaticCell> = + StaticCell::new(); let receiver = RECEIVER.init(broadcaster::Receiver::new(publisher)); service.context.register_message_receiver(receiver).unwrap(); @@ -303,13 +306,13 @@ async fn receiver_task( #[embassy_executor::task] async fn power_policy_task( - power_policy: &'static PowerPolicy< + power_policy: &'static power_policy_service::service::Service< 'static, Mutex>, - channel::DynamicReceiver<'static, policy::policy::RequestData>, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { - power_policy_service::task::task(power_policy).await.unwrap(); + power_policy_service::service::task::task(power_policy).await.unwrap(); } fn main() { diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index c1d32842d..1bdbb604d 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -2,19 +2,19 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; use embedded_services::IntrusiveList; -use embedded_services::type_c::{Cached, ControllerId, controller}; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; +use type_c_service::type_c::{Cached, ControllerId, controller}; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); mod test_controller { - use embedded_services::type_c::controller::{ControllerStatus, PortStatus}; use embedded_usb_pd::ucsi; + use type_c_service::type_c::controller::{ControllerStatus, PortStatus}; use super::*; diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs index 7ea0f5eac..4804731c8 100644 --- a/examples/std/src/bin/type_c/external.rs +++ b/examples/std/src/bin/type_c/external.rs @@ -4,23 +4,19 @@ use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::power::policy::policy; -use embedded_services::type_c::controller; -use embedded_services::{ - GlobalRawMutex, IntrusiveList, power, - type_c::{Cached, ControllerId, controller::Context}, -}; +use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; use static_cell::StaticCell; use std_examples::type_c::mock_controller::{self, Wrapper}; use type_c_service::service::{Service, config::Config}; +use type_c_service::type_c::{Cached, ControllerId, controller, controller::Context}; use type_c_service::wrapper::backing::Storage; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); +const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); #[embassy_executor::task] async fn controller_task(wrapper: &'static Wrapper<'static>) { @@ -105,8 +101,9 @@ async fn service_task( info!("Starting type-c task"); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell> = - StaticCell::new(); + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); @@ -153,7 +150,8 @@ fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper< .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL: StaticCell> = + StaticCell::new(); let policy_channel = POLICY_CHANNEL.init(Channel::new()); let policy_sender = policy_channel.dyn_sender(); @@ -163,8 +161,8 @@ fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper< type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced = REFERENCED.init( @@ -193,8 +191,8 @@ fn main() { static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); let wrapper = create_wrapper(context); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index e6647778a..189f6ad67 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,21 +5,19 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::power::policy::policy; -use embedded_services::power::{self}; -use embedded_services::type_c::ControllerId; -use embedded_services::type_c::controller::Context; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; -use power_policy_service::PowerPolicy; +use power_policy_service::service::Service as PowerPolicyService; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; use type_c_service::service::Service; use type_c_service::service::config::Config; +use type_c_service::type_c::controller::Context; +use type_c_service::type_c::{ControllerId, power_capability_from_current}; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::message::*; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -27,7 +25,7 @@ use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); +const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); const DELAY_MS: u64 = 1000; #[embassy_executor::task] @@ -65,33 +63,34 @@ async fn task(spawner: Spawner) { // Create power policy service static POWER_SERVICE_CONTEXT: StaticCell< - power::policy::policy::Context< + power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power::policy::policy::Context::new()); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); let (wrapper, controller, state) = create_wrapper(controller_context); static POWER_SERVICE: StaticCell< - power_policy_service::PowerPolicy< + power_policy_service::service::Service< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( power_service_context, - power_policy_service::config::Config::default(), + power_policy_service::service::config::Config::default(), )); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell> = - StaticCell::new(); + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); @@ -125,7 +124,9 @@ async fn task(spawner: Spawner) { Timer::after_millis(1000).await; info!("Simulating connection"); - state.connect_sink(Current::UsbDefault.into(), false).await; + state + .connect_sink(power_capability_from_current(Current::UsbDefault), false) + .await; Timer::after_millis(DELAY_MS).await; info!("Simulating PD alert"); @@ -147,13 +148,13 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_service_task( - service: &'static PowerPolicy< + service: &'static PowerPolicyService< 'static, Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { - power_policy_service::task::task(service) + power_policy_service::service::task::task(service) .await .expect("Failed to start power policy service task"); } @@ -162,9 +163,9 @@ async fn power_policy_service_task( async fn type_c_service_task( service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static policy::Context< + power_policy_context: &'static power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, cfu_client: &'static CfuClient, ) { @@ -198,7 +199,8 @@ fn create_wrapper( .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL: StaticCell> = + StaticCell::new(); let policy_channel = POLICY_CHANNEL.init(Channel::new()); let policy_sender = policy_channel.dyn_sender(); @@ -208,8 +210,8 @@ fn create_wrapper( type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced = REFERENCED.init( diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 475940609..7bb5c144c 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -7,11 +7,6 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; -use embedded_services::power::policy::PowerCapability; -use embedded_services::power::policy::policy; -use embedded_services::type_c::ControllerId; -use embedded_services::type_c::controller::Context; -use embedded_services::type_c::external::UcsiResponseResult; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -19,11 +14,15 @@ use embedded_usb_pd::ucsi::ppm::get_capability::ResponseData as UcsiCapabilities use embedded_usb_pd::ucsi::ppm::set_notification_enable::NotificationEnable; use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; -use power_policy_service::PowerPolicy; +use power_policy_interface::capability::PowerCapability; +use power_policy_service::service::Service as PowerPolicyService; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; use type_c_service::service::config::Config; +use type_c_service::type_c::ControllerId; +use type_c_service::type_c::controller::Context; +use type_c_service::type_c::external::UcsiResponseResult; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -31,9 +30,9 @@ const NUM_PD_CONTROLLERS: usize = 2; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: embedded_services::power::policy::DeviceId = embedded_services::power::policy::DeviceId(0); +const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER1_ID: embedded_services::power::policy::DeviceId = embedded_services::power::policy::DeviceId(1); +const POWER1_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(1); const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; @@ -178,13 +177,13 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_service_task( - service: &'static PowerPolicy< + service: &'static PowerPolicyService< 'static, Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { - power_policy_service::task::task(service) + power_policy_service::service::task::task(service) .await .expect("Failed to start power policy service task"); } @@ -193,9 +192,9 @@ async fn power_policy_service_task( async fn type_c_service_task( service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static policy::Context< + power_policy_context: &'static power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, cfu_client: &'static CfuClient, ) { @@ -211,26 +210,26 @@ async fn task(spawner: Spawner) { // Create power policy service static POWER_SERVICE_CONTEXT: StaticCell< - policy::Context< + power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); static POWER_SERVICE: StaticCell< - power_policy_service::PowerPolicy< + power_policy_service::service::Service< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( power_service_context, - power_policy_service::config::Config::default(), + power_policy_service::service::config::Config::default(), )); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); @@ -246,7 +245,8 @@ async fn task(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = + StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); @@ -255,8 +255,8 @@ async fn task(spawner: Spawner) { type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced0 = REFERENCED0.init( @@ -285,7 +285,8 @@ async fn task(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL1: StaticCell> = + StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); @@ -294,8 +295,8 @@ async fn task(spawner: Spawner) { type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced1 = REFERENCED1.init( @@ -317,7 +318,7 @@ async fn task(spawner: Spawner) { // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index c8ccf0bb6..5913bbbcc 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -8,17 +8,15 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::power::policy::PowerCapability; -use embedded_services::power::policy::policy; -use embedded_services::power::{self}; -use embedded_services::type_c::ControllerId; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; -use power_policy_service::PowerPolicy; +use power_policy_interface::capability::PowerCapability; +use power_policy_service::service::Service as PowerPolicyService; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; +use type_c_service::type_c::ControllerId; const NUM_PD_CONTROLLERS: usize = 3; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; @@ -26,17 +24,17 @@ use type_c_service::wrapper::proxy::PowerProxyDevice; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power::policy::DeviceId = power::policy::DeviceId(0); +const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); const CFU0_ID: u8 = 0x00; const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER1_ID: power::policy::DeviceId = power::policy::DeviceId(1); +const POWER1_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(1); const CFU1_ID: u8 = 0x01; const CONTROLLER2_ID: ControllerId = ControllerId(2); const PORT2_ID: GlobalPortId = GlobalPortId(2); -const POWER2_ID: power::policy::DeviceId = power::policy::DeviceId(2); +const POWER2_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(2); const CFU2_ID: u8 = 0x02; const DELAY_MS: u64 = 1000; @@ -56,29 +54,29 @@ async fn task(spawner: Spawner) { // Create power policy service static POWER_SERVICE_CONTEXT: StaticCell< - policy::Context< + power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(policy::Context::new()); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); static POWER_SERVICE: StaticCell< - power_policy_service::PowerPolicy< + power_policy_service::service::Service< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::PowerPolicy::new( + let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( power_service_context, - power_policy_service::config::Config::default(), + power_policy_service::service::config::Config::default(), )); - static CONTEXT: StaticCell = StaticCell::new(); - let context = CONTEXT.init(embedded_services::type_c::controller::Context::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); @@ -91,7 +89,8 @@ async fn task(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = + StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); @@ -100,8 +99,8 @@ async fn task(spawner: Spawner) { ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced0 = REFERENCED0.init( @@ -134,7 +133,8 @@ async fn task(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL1: StaticCell> = + StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); @@ -143,8 +143,8 @@ async fn task(spawner: Spawner) { ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced1 = REFERENCED1.init( @@ -177,7 +177,8 @@ async fn task(spawner: Spawner) { .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); + static POLICY_CHANNEL2: StaticCell> = + StaticCell::new(); let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); let policy_sender2 = policy_channel2.dyn_sender(); let policy_receiver2 = policy_channel2.dyn_receiver(); @@ -186,8 +187,8 @@ async fn task(spawner: Spawner) { ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, policy::RequestData>, - DynamicReceiver<'_, policy::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::RequestData>, + DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, >, > = StaticCell::new(); let referenced2 = REFERENCED2.init( @@ -214,7 +215,7 @@ async fn task(spawner: Spawner) { // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -298,13 +299,13 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_service_task( - service: &'static PowerPolicy< + service: &'static PowerPolicyService< 'static, Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, ) { - power_policy_service::task::task(service) + power_policy_service::service::task::task(service) .await .expect("Failed to start power policy service task"); } @@ -313,9 +314,9 @@ async fn power_policy_service_task( async fn type_c_service_task( service: &'static Service<'static>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static policy::Context< + power_policy_context: &'static power_policy_service::service::context::Context< Mutex>, - DynamicReceiver<'static, policy::RequestData>, + DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, >, cfu_client: &'static CfuClient, ) { diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index a3cda41be..45dd0478a 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -1,22 +1,22 @@ use embassy_sync::{channel, mutex::Mutex, signal::Signal}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOfferResponse, HostToken}; -use embedded_services::{ - GlobalRawMutex, - power::policy::{PowerCapability, policy}, - type_c::{ - controller::{ - AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, - RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, - }, - event::PortEvent, - }, -}; +use embedded_services::GlobalRawMutex; use embedded_usb_pd::{Error, ado::Ado}; use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; use log::{debug, info, trace}; +use power_policy_interface::capability::PowerCapability; +use type_c_service::type_c::{ + controller::{ + AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, + RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, + }, + event::PortEvent, + power_capability_from_current, +}; + pub struct ControllerState { events: Signal, status: Mutex, @@ -77,7 +77,8 @@ impl ControllerState { /// Simulate a debug accessory source connecting pub async fn connect_debug_accessory_source(&self, current: Current) { - self.connect(PowerRole::Source, current.into(), true, false).await; + self.connect(PowerRole::Source, power_capability_from_current(current), true, false) + .await; } /// Simulate a PD alert @@ -115,7 +116,7 @@ impl<'a> Controller<'a> { } } -impl embedded_services::type_c::controller::Controller for Controller<'_> { +impl type_c_service::type_c::controller::Controller for Controller<'_> { type BusError = (); async fn wait_port_event(&mut self) -> Result<(), Error> { @@ -341,7 +342,7 @@ pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< 'a, GlobalRawMutex, Mutex>, - channel::DynamicSender<'a, policy::RequestData>, - channel::DynamicReceiver<'a, policy::RequestData>, + channel::DynamicSender<'a, power_policy_interface::psu::event::RequestData>, + channel::DynamicReceiver<'a, power_policy_interface::psu::event::RequestData>, Validator, >; diff --git a/power-policy-interface/Cargo.toml b/power-policy-interface/Cargo.toml new file mode 100644 index 000000000..81935f916 --- /dev/null +++ b/power-policy-interface/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "power-policy-interface" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embassy-sync.workspace = true +embedded-services.workspace = true +embassy-futures.workspace = true +num_enum.workspace = true +bitfield.workspace = true +log = { workspace = true, optional = true } +heapless.workspace = true +embedded-batteries-async.workspace = true + +[features] +default = [] +defmt = ["dep:defmt", "embedded-services/defmt", "embassy-sync/defmt"] +log = ["dep:log", "embedded-services/log", "embassy-sync/log"] diff --git a/embedded-service/src/power/policy/flags.rs b/power-policy-interface/src/capability.rs similarity index 66% rename from embedded-service/src/power/policy/flags.rs rename to power-policy-interface/src/capability.rs index b9fbcc4d7..d53698b16 100644 --- a/embedded-service/src/power/policy/flags.rs +++ b/power-policy-interface/src/capability.rs @@ -1,8 +1,84 @@ -//! Consumer and provider flags, these are used to signal additional information about a consumer/provider request - +//! Power capability definitions and related flags use bitfield::bitfield; use num_enum::{IntoPrimitive, TryFromPrimitive}; +/// Amount of power that a device can provider or consume +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PowerCapability { + /// Available voltage in mV + pub voltage_mv: u16, + /// Max available current in mA + pub current_ma: u16, +} + +impl PowerCapability { + /// Calculate maximum power + pub fn max_power_mw(&self) -> u32 { + self.voltage_mv as u32 * self.current_ma as u32 / 1000 + } +} + +impl PartialOrd for PowerCapability { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PowerCapability { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.max_power_mw().cmp(&other.max_power_mw()) + } +} + +/// Power capability with consumer flags +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ConsumerPowerCapability { + /// Power capability + pub capability: PowerCapability, + /// Consumer flags + pub flags: ConsumerFlags, +} + +impl From for ConsumerPowerCapability { + fn from(capability: PowerCapability) -> Self { + Self { + capability, + flags: ConsumerFlags::none(), + } + } +} + +/// Power capability with provider flags +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ProviderPowerCapability { + /// Power capability + pub capability: PowerCapability, + /// Provider flags + pub flags: ProviderFlags, +} + +impl From for ProviderPowerCapability { + fn from(capability: PowerCapability) -> Self { + Self { + capability, + flags: ProviderFlags::none(), + } + } +} + +/// Combined power capability with flags enum +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PowerCapabilityFlags { + /// Consumer flags + Consumer(ConsumerPowerCapability), + /// Provider flags + Provider(ProviderPowerCapability), +} + /// PSU type #[derive(Copy, Clone, Debug, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)] #[num_enum(error_type(name = InvalidPsuType, constructor = InvalidPsuType))] @@ -37,7 +113,7 @@ bitfield! { /// Raw consumer flags bit field #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct ConsumerRaw(u32); + struct ConsumerFlagsRaw(u32); impl Debug; /// Unconstrained power, indicates that we are drawing power from something like an outlet and not a limited source like a battery pub bool, unconstrained_power, set_unconstrained_power: 0; @@ -48,12 +124,12 @@ bitfield! { /// Type safe wrapper for consumer flags #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Consumer(ConsumerRaw); +pub struct ConsumerFlags(ConsumerFlagsRaw); -impl Consumer { +impl ConsumerFlags { /// Create a new consumer with no flags set pub const fn none() -> Self { - Self(ConsumerRaw(0)) + Self(ConsumerFlagsRaw(0)) } /// Builder method to set the unconstrained power flag @@ -102,9 +178,9 @@ bitfield! { /// Type safe wrapper for provider flags #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Provider(ProviderRaw); +pub struct ProviderFlags(ProviderRaw); -impl Provider { +impl ProviderFlags { /// Create a new provider with no flags set pub const fn none() -> Self { Self(ProviderRaw(0)) @@ -158,24 +234,24 @@ mod tests { } #[test] - fn test_consumer_unconstrained() { - let mut consumer = Consumer::none().with_unconstrained_power(); + fn test_consumer_flags_unconstrained() { + let mut consumer = ConsumerFlags::none().with_unconstrained_power(); assert_eq!(consumer.0.0, 0x1); consumer.set_unconstrained_power(false); assert_eq!(consumer.0.0, 0x0); } #[test] - fn test_consumer_psu_type() { - let mut consumer = Consumer::none().with_psu_type(PsuType::TypeC); + fn test_consumer_flags_psu_type() { + let mut consumer = ConsumerFlags::none().with_psu_type(PsuType::TypeC); assert_eq!(consumer.0.0, 0x100); consumer.set_psu_type(PsuType::Unknown); assert_eq!(consumer.0.0, 0x0); } #[test] - fn test_provider_psu_type() { - let mut provider = Provider::none().with_psu_type(PsuType::TypeC); + fn test_provider_flags_psu_type() { + let mut provider = ProviderFlags::none().with_psu_type(PsuType::TypeC); assert_eq!(provider.0.0, 0x100); provider.set_psu_type(PsuType::Unknown); assert_eq!(provider.0.0, 0x0); diff --git a/power-policy-service/src/charger.rs b/power-policy-interface/src/charger.rs similarity index 50% rename from power-policy-service/src/charger.rs rename to power-policy-interface/src/charger.rs index 72e8a292f..f338ca66b 100644 --- a/power-policy-service/src/charger.rs +++ b/power-policy-interface/src/charger.rs @@ -1,39 +1,271 @@ -use embassy_sync::mutex::Mutex; -use embedded_services::GlobalRawMutex; +//! Charger device struct and controller +use core::{future::Future, ops::DerefMut}; use embassy_futures::select::select; -use embedded_services::{ - debug, error, info, - power::policy::charger::{ - self, ChargeController, ChargerEvent, ChargerResponse, InternalState, PolicyEvent, PoweredSubstate, State, - }, - trace, warn, -}; +use embassy_sync::{channel::Channel, mutex::Mutex}; +use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, trace, warn}; + +use crate::capability::{ConsumerPowerCapability, PowerCapability}; + +/// Charger controller trait that device drivers may use to integrate with internal messaging system +pub trait ChargeController: embedded_batteries_async::charger::Charger { + /// Type of error returned by the bus + type ChargeControllerError; + + /// Returns with pending events + fn wait_event(&mut self) -> impl Future; + /// Initialize charger hardware, after this returns the charger should be ready to charge + fn init_charger(&mut self) -> impl Future>; + /// Returns if the charger hardware detects if a PSU is attached + fn is_psu_attached(&mut self) -> impl Future>; + /// Called after power policy attaches to a power port. + fn attach_handler( + &mut self, + capability: ConsumerPowerCapability, + ) -> impl Future>; + /// Called after power policy detaches from a power port, either to switch consumers, + /// or because PSU was disconnected. + fn detach_handler(&mut self) -> impl Future>; + /// Called when a charger CheckReady request (PolicyEvent::CheckReady) is sent to the power policy. + /// Upon successful return of this method, the charger is assumed to be powered and ready to communicate, + /// transitioning state from unpowered to powered. + /// + /// If the charger is powered, an Ok(()) does nothing. An Err(_) will put the charger into an + /// unpowered state, meaning another PolicyEvent::CheckReady must be sent to re-establish communications + /// with the charger. Upon successful return, the charger must be re-initialized by sending a + /// `PolicyEvent::InitRequest`. + fn is_ready(&mut self) -> impl Future> { + core::future::ready(Ok(())) + } +} + +/// Charger Device ID new type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ChargerId(pub u8); + +/// PSU state as determined by charger device +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PsuState { + /// Charger detected PSU attached + Attached, + /// Charger detected PSU detached + Detached, +} + +impl From for PsuState { + fn from(value: bool) -> Self { + match value { + true => PsuState::Attached, + false => PsuState::Detached, + } + } +} + +/// Data for a device request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ChargerEvent { + /// Charger finished initialization sequence + Initialized(PsuState), + /// PSU state changed + PsuStateChange(PsuState), + /// A timeout of some sort was detected + Timeout, + /// An error occured on the bus + BusError, +} + +/// Charger state errors +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ChargerError { + /// Charger received command in an invalid state + InvalidState(State), + /// Charger hardware timed out responding + Timeout, + /// Charger underlying bus error + BusError, +} + +impl From for crate::psu::Error { + fn from(value: ChargerError) -> Self { + Self::Charger(value) + } +} + +/// Data for a device request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PolicyEvent { + /// Request to initialize charger hardware + InitRequest, + /// New power policy detected + PolicyConfiguration(ConsumerPowerCapability), + /// Request to check if the charger hardware is ready to receive communications. + /// For example, if the charger is powered. + CheckReady, +} + +/// Data for a device request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ChargerResponseData { + /// Command completed + Ack, + /// Charger Unpowered, but we are still Ok + UnpoweredAck, +} + +/// Response for charger requests from policy commands +pub type ChargerResponse = Result; + +/// Current state of the charger +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + /// Device is unpowered + Unpowered, + /// Device is powered + Powered(PoweredSubstate), +} + +/// Powered state substates +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PoweredSubstate { + /// Device is initializing + Init, + /// PSU is attached and device can charge if desired + PsuAttached, + /// PSU is detached + PsuDetached, +} + +/// Current state of the charger +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InternalState { + /// Charger device state + pub state: State, + /// Current charger capability + pub capability: Option, +} + +/// Channel size for device requests +pub const CHARGER_CHANNEL_SIZE: usize = 1; + +/// Device struct +pub struct Device { + /// Intrusive list node + node: intrusive_list::Node, + /// Device ID + id: ChargerId, + /// Current state of the device + state: Mutex, + /// Channel for requests to the device + commands: Channel, + /// Channel for responses from the device + response: Channel, +} + +impl Device { + /// Create a new device + pub fn new(id: ChargerId) -> Self { + Self { + node: intrusive_list::Node::uninit(), + id, + state: Mutex::new(InternalState { + state: State::Unpowered, + capability: None, + }), + commands: Channel::new(), + response: Channel::new(), + } + } + + /// Get the device ID + pub fn id(&self) -> ChargerId { + self.id + } + + /// Returns the current state of the device + pub async fn state(&self) -> InternalState { + *self.state.lock().await + } + + /// Set the state of the device + pub async fn set_state(&self, new_state: InternalState) { + let mut lock = self.state.lock().await; + let current_state = lock.deref_mut(); + *current_state = new_state; + } + + /// Wait for a command from policy + pub async fn wait_command(&self) -> PolicyEvent { + self.commands.receive().await + } + + /// Send a command to the charger + pub async fn send_command(&self, policy_event: PolicyEvent) { + self.commands.send(policy_event).await + } + + /// Send a response to the power policy + pub async fn send_response(&self, response: ChargerResponse) { + self.response.send(response).await + } + + /// Send a command and wait for a response from the charger + pub async fn execute_command(&self, policy_event: PolicyEvent) -> ChargerResponse { + self.send_command(policy_event).await; + self.response.receive().await + } +} + +impl intrusive_list::NodeContainer for Device { + fn get_node(&self) -> &intrusive_list::Node { + &self.node + } +} + +/// Trait for any container that holds a device +pub trait ChargerContainer { + /// Get the underlying device struct + fn get_charger(&self) -> &Device; +} + +impl ChargerContainer for Device { + fn get_charger(&self) -> &Device { + self + } +} pub struct Wrapper<'a, C: ChargeController> where - charger::ChargerError: From<::ChargeControllerError>, + ChargerError: From<::ChargeControllerError>, { - charger_policy_state: &'a charger::Device, + charger_policy_state: &'a Device, controller: Mutex, } impl<'a, C: ChargeController> Wrapper<'a, C> where - charger::ChargerError: From<::ChargeControllerError>, + ChargerError: From<::ChargeControllerError>, { - pub fn new(charger_policy_state: &'a charger::Device, controller: C) -> Self { + pub fn new(charger_policy_state: &'a Device, controller: C) -> Self { Self { charger_policy_state, controller: Mutex::new(controller), } } - pub async fn get_state(&self) -> charger::InternalState { + pub async fn get_state(&self) -> InternalState { self.charger_policy_state.state().await } - pub async fn set_state(&self, new_state: charger::InternalState) { + pub async fn set_state(&self, new_state: InternalState) { self.charger_policy_state.set_state(new_state).await } @@ -50,8 +282,8 @@ where ChargerEvent::Initialized(psu_state) => { self.set_state(InternalState { state: match psu_state { - charger::PsuState::Attached => State::Powered(PoweredSubstate::PsuAttached), - charger::PsuState::Detached => State::Powered(PoweredSubstate::PsuDetached), + PsuState::Attached => State::Powered(PoweredSubstate::PsuAttached), + PsuState::Detached => State::Powered(PoweredSubstate::PsuDetached), }, capability: state.capability, }) @@ -61,7 +293,7 @@ where _ => (), }, PoweredSubstate::PsuAttached => match event { - ChargerEvent::PsuStateChange(charger::PsuState::Detached) => { + ChargerEvent::PsuStateChange(PsuState::Detached) => { self.set_state(InternalState { state: State::Powered(PoweredSubstate::PsuDetached), capability: state.capability, @@ -78,7 +310,7 @@ where _ => (), }, PoweredSubstate::PsuDetached => match event { - ChargerEvent::PsuStateChange(charger::PsuState::Attached) => { + ChargerEvent::PsuStateChange(PsuState::Attached) => { self.set_state(InternalState { state: State::Powered(PoweredSubstate::PsuAttached), capability: state.capability, @@ -108,7 +340,7 @@ where PolicyEvent::InitRequest => { if state.state == State::Unpowered { error!("Charger received request to initialize but it's unpowered!"); - Err(charger::ChargerError::InvalidState(State::Unpowered)) + Err(ChargerError::InvalidState(State::Unpowered)) } else { if state.state == State::Powered(PoweredSubstate::Init) { info!("Charger received request to initialize."); @@ -120,7 +352,7 @@ where error!("Charger failed initialzation sequence."); Err(err.into()) } else { - Ok(charger::ChargerResponseData::Ack) + Ok(ChargerResponseData::Ack) } } } @@ -131,14 +363,12 @@ where // from completing it's connect_consumer() call, as there might be cases where we don't want // chargers to be powered or the charger can't be powered. error!("Charger detected new power policy configuration but it's unpowered!"); - Ok(charger::ChargerResponseData::UnpoweredAck) + Ok(ChargerResponseData::UnpoweredAck) } State::Powered(substate) => match substate { PoweredSubstate::Init => { error!("Charger detected new power policy configuration but charger is still initializing."); - Err(charger::ChargerError::InvalidState(State::Powered( - PoweredSubstate::Init, - ))) + Err(ChargerError::InvalidState(State::Powered(PoweredSubstate::Init))) } PoweredSubstate::PsuAttached | PoweredSubstate::PsuDetached => { if power_capability.capability.current_ma == 0 { @@ -160,7 +390,7 @@ where capability: None, }) .await; - Ok(charger::ChargerResponseData::Ack) + Ok(ChargerResponseData::Ack) } } else { // Policy detected an attach @@ -181,7 +411,7 @@ where capability: Some(power_capability.capability), }) .await; - Ok(charger::ChargerResponseData::Ack) + Ok(ChargerResponseData::Ack) } } } @@ -201,7 +431,7 @@ where .await; Err(e.into()) } else { - Ok(charger::ChargerResponseData::Ack) + Ok(ChargerResponseData::Ack) } } State::Unpowered => { @@ -213,7 +443,7 @@ where capability: None, }) .await; - Ok(charger::ChargerResponseData::Ack) + Ok(ChargerResponseData::Ack) } } } diff --git a/power-policy-interface/src/lib.rs b/power-policy-interface/src/lib.rs new file mode 100644 index 000000000..41be38249 --- /dev/null +++ b/power-policy-interface/src/lib.rs @@ -0,0 +1,6 @@ +#![no_std] + +pub mod capability; +pub mod charger; +pub mod psu; +pub mod service; diff --git a/power-policy-interface/src/psu/event.rs b/power-policy-interface/src/psu/event.rs new file mode 100644 index 000000000..9ac19ede4 --- /dev/null +++ b/power-policy-interface/src/psu/event.rs @@ -0,0 +1,55 @@ +//! Messages originating from a PSU +use crate::capability::{ConsumerPowerCapability, ProviderPowerCapability}; + +/// Data for a power policy request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RequestData { + /// Notify that a device has attached + Attached, + /// Notify that available power for consumption has changed + UpdatedConsumerCapability(Option), + /// Request the given amount of power to provider + RequestedProviderCapability(Option), + /// Notify that a device cannot consume or provide power anymore + Disconnected, + /// Notify that a device has detached + Detached, +} + +/// Request to the power policy service +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Request { + /// Device that sent this request + pub id: super::DeviceId, + /// Request data + pub data: RequestData, +} + +/// Data for a power policy response +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ResponseData { + /// The request was completed successfully + Complete, +} + +impl ResponseData { + /// Returns an InvalidResponse error if the response is not complete + pub fn complete_or_err(self) -> Result<(), super::Error> { + match self { + ResponseData::Complete => Ok(()), + } + } +} + +/// Response from the power policy service +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Response { + /// Target device + pub id: super::DeviceId, + /// Response data + pub data: ResponseData, +} diff --git a/embedded-service/src/power/policy/device.rs b/power-policy-interface/src/psu/mod.rs similarity index 83% rename from embedded-service/src/power/policy/device.rs rename to power-policy-interface/src/psu/mod.rs index b93d5a393..8f045559e 100644 --- a/embedded-service/src/power/policy/device.rs +++ b/power-policy-interface/src/psu/mod.rs @@ -1,12 +1,43 @@ //! Device struct and methods use embassy_sync::mutex::Mutex; -use super::{DeviceId, Error}; -use crate::event::Receiver; -use crate::power::policy::policy::RequestData; -use crate::power::policy::{ConsumerPowerCapability, ProviderPowerCapability}; -use crate::sync::Lockable; -use crate::{GlobalRawMutex, intrusive_list}; +use crate::capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}; +use embedded_services::event::Receiver; +use embedded_services::sync::Lockable; +use embedded_services::{GlobalRawMutex, intrusive_list}; + +pub mod event; + +/// Error type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The requested device does not exist + InvalidDevice, + /// The provide request was denied, contains maximum available power + CannotProvide(Option), + /// The consume request was denied, contains maximum available power + CannotConsume(Option), + /// The device is not in the correct state (expected, actual) + InvalidState(&'static [StateKind], StateKind), + /// Invalid response + InvalidResponse, + /// Busy, the device cannot respond to the request at this time + Busy, + /// Timeout + Timeout, + /// Bus error + Bus, + /// Charger specific error, underlying error should have more context + Charger(crate::charger::ChargerError), + /// Generic failure + Failed, +} + +/// Device ID new type +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DeviceId(pub u8); /// Most basic device states #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -254,7 +285,7 @@ pub struct Response { } /// Trait for PSU devices -pub trait DeviceTrait { +pub trait Psu { /// Disconnect power from this device fn disconnect(&mut self) -> impl Future>; /// Connect this device to provide power to an external connection @@ -264,9 +295,9 @@ pub trait DeviceTrait { } /// PSU registration struct -pub struct Device<'a, D: Lockable, R: Receiver> +pub struct RegistrationEntry<'a, D: Lockable, R: Receiver> where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Intrusive list node node: intrusive_list::Node, @@ -280,9 +311,9 @@ where pub receiver: Mutex, } -impl<'a, D: Lockable, R: Receiver> Device<'a, D, R> +impl<'a, D: Lockable, R: Receiver> RegistrationEntry<'a, D, R> where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Create a new device pub fn new(id: DeviceId, device: &'a D, receiver: R) -> Self { @@ -333,29 +364,30 @@ where } } -impl + 'static> intrusive_list::NodeContainer for Device<'static, D, R> +impl + 'static> intrusive_list::NodeContainer + for RegistrationEntry<'static, D, R> where - D::Inner: DeviceTrait, + D::Inner: Psu, { - fn get_node(&self) -> &crate::Node { + fn get_node(&self) -> &intrusive_list::Node { &self.node } } /// Trait for any container that holds a device -pub trait DeviceContainer> +pub trait PsuContainer> where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Get the underlying device struct - fn get_power_policy_device(&self) -> &Device<'_, D, R>; + fn get_power_policy_device(&self) -> &RegistrationEntry<'_, D, R>; } -impl> DeviceContainer for Device<'_, D, R> +impl> PsuContainer for RegistrationEntry<'_, D, R> where - D::Inner: DeviceTrait, + D::Inner: Psu, { - fn get_power_policy_device(&self) -> &Device<'_, D, R> { + fn get_power_policy_device(&self) -> &RegistrationEntry<'_, D, R> { self } } diff --git a/power-policy-interface/src/service/event.rs b/power-policy-interface/src/service/event.rs new file mode 100644 index 000000000..ca09844d0 --- /dev/null +++ b/power-policy-interface/src/service/event.rs @@ -0,0 +1,29 @@ +use crate::{ + capability::{ConsumerPowerCapability, ProviderPowerCapability}, + psu::DeviceId, + service::UnconstrainedState, +}; + +/// Data to send with the comms service +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CommsData { + /// Consumer disconnected + ConsumerDisconnected(DeviceId), + /// Consumer connected + ConsumerConnected(DeviceId, ConsumerPowerCapability), + /// Provider disconnected + ProviderDisconnected(DeviceId), + /// Provider connected + ProviderConnected(DeviceId, ProviderPowerCapability), + /// Unconstrained state changed + Unconstrained(UnconstrainedState), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Message to send with the comms service +pub struct CommsMessage { + /// Message data + pub data: CommsData, +} diff --git a/power-policy-interface/src/service/mod.rs b/power-policy-interface/src/service/mod.rs new file mode 100644 index 000000000..a87dfe0b9 --- /dev/null +++ b/power-policy-interface/src/service/mod.rs @@ -0,0 +1,21 @@ +pub mod event; + +/// Unconstrained state information +#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UnconstrainedState { + /// Unconstrained state + pub unconstrained: bool, + /// Available unconstrained devices + pub available: usize, +} + +impl UnconstrainedState { + /// Create a new unconstrained state + pub fn new(unconstrained: bool, available: usize) -> Self { + Self { + unconstrained, + available, + } + } +} diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 9be53aed5..404b150f5 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -16,8 +16,12 @@ embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true embedded-services.workspace = true +embedded-batteries-async.workspace = true +num_enum.workspace = true +bitfield.workspace = true log = { workspace = true, optional = true } heapless.workspace = true +power-policy-interface.workspace = true [dev-dependencies] static_cell.workspace = true @@ -32,12 +36,14 @@ default = [] defmt = [ "dep:defmt", "embedded-services/defmt", + "power-policy-interface/defmt", "embassy-time/defmt", "embassy-sync/defmt", ] log = [ "dep:log", "embedded-services/log", + "power-policy-interface/log", "embassy-time/log", "embassy-sync/log", ] diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index f583f8aff..de4e4958a 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -1,216 +1,2 @@ #![no_std] -use core::ops::DerefMut; -use embassy_sync::mutex::Mutex; -use embedded_services::GlobalRawMutex; -use embedded_services::event::Receiver; -use embedded_services::power::policy::device::{Device, DeviceTrait, State}; -use embedded_services::power::policy::policy::RequestData; -use embedded_services::power::policy::{policy, *}; -use embedded_services::sync::Lockable; -use embedded_services::{comms, error, info}; - -pub mod config; -pub mod consumer; -pub mod provider; -pub mod task; - -pub use config::Config; -pub mod charger; - -const MAX_CONNECTED_PROVIDERS: usize = 4; - -#[derive(Clone, Default)] -struct InternalState { - /// Current consumer state, if any - current_consumer_state: Option, - /// Current provider global state - current_provider_state: provider::State, - /// System unconstrained power - unconstrained: UnconstrainedState, - /// Connected providers - connected_providers: heapless::FnvIndexSet, -} - -/// Power policy state -pub struct PowerPolicy<'a, D: Lockable, R: Receiver> -where - D::Inner: DeviceTrait, -{ - /// Power policy context - pub context: &'a policy::Context, - /// State - state: Mutex, - /// Comms endpoint - tp: comms::Endpoint, - /// Config - config: config::Config, -} - -impl<'a, D: Lockable + 'static, R: Receiver + 'static> PowerPolicy<'a, D, R> -where - D::Inner: DeviceTrait, -{ - /// Create a new power policy - pub fn new(context: &'a policy::Context, config: config::Config) -> Self { - Self { - context, - state: Mutex::new(InternalState::default()), - tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Power)), - config, - } - } - - async fn process_notify_attach(&self, device: &Device<'_, D, R>) { - if let Err(e) = device.state.lock().await.attach() { - error!("Device{}: Invalid state for attach: {:#?}", device.id().0, e); - } - } - - async fn process_notify_detach(&self, device: &Device<'_, D, R>) -> Result<(), Error> { - device.state.lock().await.detach(); - self.update_current_consumer().await - } - - async fn process_notify_consumer_power_capability( - &self, - device: &Device<'_, D, R>, - capability: Option, - ) -> Result<(), Error> { - if let Err(e) = device.state.lock().await.update_consumer_power_capability(capability) { - error!( - "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", - device.id().0, - e, - ); - } - - self.update_current_consumer().await - } - - async fn process_request_provider_power_capabilities( - &self, - device: &Device<'_, D, R>, - capability: Option, - ) -> Result<(), Error> { - if let Err(e) = device - .state - .lock() - .await - .update_requested_provider_power_capability(capability) - { - error!( - "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", - device.id().0, - e, - ); - } - - self.connect_provider(device.id()).await - } - - async fn process_notify_disconnect(&self, device: &Device<'_, D, R>) -> Result<(), Error> { - if let Err(e) = device.state.lock().await.disconnect(true) { - error!( - "Device{}: Invalid state for notify disconnect, catching up: {:#?}", - device.id().0, - e, - ); - } - - if self - .state - .lock() - .await - .current_consumer_state - .is_some_and(|current| current.device_id == device.id()) - { - info!("Device{}: Connected consumer disconnected", device.id().0); - self.disconnect_chargers().await?; - - self.comms_notify(CommsMessage { - data: CommsData::ConsumerDisconnected(device.id()), - }) - .await; - } - - self.remove_connected_provider(device.id()).await; - self.update_current_consumer().await?; - Ok(()) - } - - /// Send a notification with the comms service - async fn comms_notify(&self, message: CommsMessage) { - self.context.broadcast_message(message).await; - let _ = self - .tp - .send(comms::EndpointID::Internal(comms::Internal::Battery), &message) - .await; - } - - /// Common logic for when a provider is disconnected - /// - /// Returns true if the device was operating as a provider - async fn remove_connected_provider(&self, device_id: DeviceId) -> bool { - if self.state.lock().await.connected_providers.remove(&device_id) { - self.comms_notify(CommsMessage { - data: CommsData::ProviderDisconnected(device_id), - }) - .await; - true - } else { - false - } - } - - async fn wait_request(&self) -> policy::Request { - self.context.wait_request().await - } - - async fn process_request(&self, request: policy::Request) -> Result<(), Error> { - let device = self.context.get_device(request.id)?; - - match request.data { - policy::RequestData::Attached => { - info!("Received notify attached from device {}", device.id().0); - self.process_notify_attach(device).await; - Ok(()) - } - policy::RequestData::Detached => { - info!("Received notify detached from device {}", device.id().0); - self.process_notify_detach(device).await - } - policy::RequestData::UpdatedConsumerCapability(capability) => { - info!( - "Device{}: Received notify consumer capability: {:#?}", - device.id().0, - capability, - ); - self.process_notify_consumer_power_capability(device, capability).await - } - policy::RequestData::RequestedProviderCapability(capability) => { - info!( - "Device{}: Received request provider capability: {:#?}", - device.id().0, - capability, - ); - self.process_request_provider_power_capabilities(device, capability) - .await - } - policy::RequestData::Disconnected => { - info!("Received notify disconnect from device {}", device.id().0); - self.process_notify_disconnect(device).await - } - } - } - - /// Top-level event loop function - pub async fn process(&self) -> Result<(), Error> { - let request = self.wait_request().await; - self.process_request(request).await - } -} - -impl + 'static> comms::MailboxDelegate for PowerPolicy<'_, D, R> where - D::Inner: DeviceTrait -{ -} +pub mod service; diff --git a/power-policy-service/src/config.rs b/power-policy-service/src/service/config.rs similarity index 95% rename from power-policy-service/src/config.rs rename to power-policy-service/src/service/config.rs index 4ee3fce96..854739e6d 100644 --- a/power-policy-service/src/config.rs +++ b/power-policy-service/src/service/config.rs @@ -1,6 +1,6 @@ //! Configuration types for the power policy service -use embedded_services::power::policy::PowerCapability; +use power_policy_interface::capability::PowerCapability; #[derive(Clone, Copy)] pub struct Config { diff --git a/power-policy-service/src/consumer.rs b/power-policy-service/src/service/consumer.rs similarity index 91% rename from power-policy-service/src/consumer.rs rename to power-policy-service/src/service/consumer.rs index 58dd8a20b..9210011d8 100644 --- a/power-policy-service/src/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -1,10 +1,13 @@ use core::cmp::Ordering; -use embedded_services::debug; -use embedded_services::power::policy::charger::Device as ChargerDevice; -use embedded_services::power::policy::charger::PolicyEvent; +use core::ops::DerefMut; +use embedded_services::{debug, error}; use super::*; +use power_policy_interface::capability::ConsumerFlags; +use power_policy_interface::charger::Device as ChargerDevice; +use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::State}; + /// State of the current consumer #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -29,17 +32,17 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl + 'static> PowerPolicy<'_, D, R> +impl + 'static> Service<'_, D, R> where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Iterate over all devices to determine what is best power port provides the highest power async fn find_best_consumer(&self, state: &InternalState) -> Result, Error> { let mut best_consumer = None; let current_consumer_id = state.current_consumer_state.map(|f| f.device_id); - for node in self.context.devices() { - let device = node.data::>().ok_or(Error::InvalidDevice)?; + for node in self.context.psu_devices() { + let device = node.data::>().ok_or(Error::InvalidDevice)?; let consumer_capability = device.consumer_capability().await; // Don't consider consumers below minimum threshold @@ -92,8 +95,8 @@ where async fn update_unconstrained_state(&self, state: &mut InternalState) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for node in self.context.devices() { - let device = node.data::>().ok_or(Error::InvalidDevice)?; + for node in self.context.psu_devices() { + let device = node.data::>().ok_or(Error::InvalidDevice)?; if let Some(capability) = device.consumer_capability().await { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; @@ -128,10 +131,10 @@ where embassy_time::Timer::after_millis(800).await; // If no chargers are registered, they won't receive the new power capability. - for node in self.context.chargers() { + for node in self.context.charger_devices() { let device = node.data::().ok_or(Error::InvalidDevice)?; // Chargers should be powered at this point, but in case they are not... - if let embedded_services::power::policy::charger::ChargerResponseData::UnpoweredAck = device + if let power_policy_interface::charger::ChargerResponseData::UnpoweredAck = device .execute_command(PolicyEvent::PolicyConfiguration( connected_consumer.consumer_power_capability, )) @@ -162,15 +165,15 @@ where /// Disconnect all chargers pub(super) async fn disconnect_chargers(&self) -> Result<(), Error> { - for node in self.context.chargers() { + for node in self.context.charger_devices() { let device = node.data::().ok_or(Error::InvalidDevice)?; - if let embedded_services::power::policy::charger::ChargerResponseData::UnpoweredAck = device + if let power_policy_interface::charger::ChargerResponseData::UnpoweredAck = device .execute_command(PolicyEvent::PolicyConfiguration(ConsumerPowerCapability { capability: PowerCapability { voltage_mv: 0, current_ma: 0, }, - flags: flags::Consumer::none(), + flags: ConsumerFlags::none(), })) .await? { @@ -198,7 +201,7 @@ where } state.current_consumer_state = None; - let consumer_device = self.context.get_device(current_consumer.device_id)?; + let consumer_device = self.context.get_psu(current_consumer.device_id)?; let mut locked_state = consumer_device.state.lock().await; let mut locked_device = consumer_device.device.lock().await; @@ -231,7 +234,7 @@ where } info!("Device {}, connecting new consumer", new_consumer.device_id.0); - let device = self.context.get_device(new_consumer.device_id)?; + let device = self.context.get_psu(new_consumer.device_id)?; let mut locked_device = device.device.lock().await; let mut locked_state = device.state.lock().await; diff --git a/embedded-service/src/power/policy/policy.rs b/power-policy-service/src/service/context.rs similarity index 56% rename from embedded-service/src/power/policy/policy.rs rename to power-policy-service/src/service/context.rs index e000630df..4ec211817 100644 --- a/embedded-service/src/power/policy/policy.rs +++ b/power-policy-service/src/service/context.rs @@ -2,79 +2,26 @@ use core::marker::PhantomData; use core::pin::pin; -use crate::broadcaster::immediate as broadcaster; -use crate::event::Receiver; -use crate::power::policy::device::DeviceTrait; -use crate::power::policy::{CommsMessage, ConsumerPowerCapability, ProviderPowerCapability}; -use crate::sync::Lockable; use embassy_futures::select::select_slice; - -use super::charger::ChargerResponse; -use super::device::{self}; -use super::{DeviceId, Error, charger}; -use crate::power::policy::charger::ChargerResponseData::Ack; -use crate::{error, intrusive_list}; - -/// Data for a power policy request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RequestData { - /// Notify that a device has attached - Attached, - /// Notify that available power for consumption has changed - UpdatedConsumerCapability(Option), - /// Request the given amount of power to provider - RequestedProviderCapability(Option), - /// Notify that a device cannot consume or provide power anymore - Disconnected, - /// Notify that a device has detached - Detached, -} - -/// Request to the power policy service -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Request { - /// Device that sent this request - pub id: DeviceId, - /// Request data - pub data: RequestData, -} - -/// Data for a power policy response -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// The request was completed successfully - Complete, -} - -impl ResponseData { - /// Returns an InvalidResponse error if the response is not complete - pub fn complete_or_err(self) -> Result<(), Error> { - match self { - ResponseData::Complete => Ok(()), - } - } -} - -/// Response from the power policy service -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Response { - /// Target device - pub id: DeviceId, - /// Response data - pub data: ResponseData, -} +use embedded_services::broadcaster::immediate as broadcaster; +use embedded_services::event::Receiver; +use embedded_services::sync::Lockable; +use power_policy_interface::charger; +use power_policy_interface::psu::Psu; +use power_policy_interface::psu::event::Request; + +use embedded_services::{error, intrusive_list}; +use power_policy_interface::charger::ChargerResponse; +use power_policy_interface::psu::{self, DeviceId, Error, event::RequestData}; +use power_policy_interface::service::event::CommsMessage; /// Power policy context pub struct Context> where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Registered devices - power_devices: intrusive_list::IntrusiveList, + psu_devices: intrusive_list::IntrusiveList, /// Registered chargers charger_devices: intrusive_list::IntrusiveList, /// Message broadcaster @@ -84,7 +31,7 @@ where impl + 'static> Default for Context where - D::Inner: DeviceTrait, + D::Inner: Psu, { fn default() -> Self { Self::new() @@ -93,12 +40,12 @@ where impl + 'static> Context where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Construct a new power policy Context pub const fn new() -> Self { Self { - power_devices: intrusive_list::IntrusiveList::new(), + psu_devices: intrusive_list::IntrusiveList::new(), charger_devices: intrusive_list::IntrusiveList::new(), broadcaster: broadcaster::Immediate::new(), _phantom: PhantomData, @@ -106,34 +53,31 @@ where } /// Register a power device with the service - pub fn register_device( - &self, - device: &'static impl device::DeviceContainer, - ) -> Result<(), intrusive_list::Error> { - let device = device.get_power_policy_device(); - if self.get_device(device.id()).is_ok() { + pub fn register_psu(&self, psu: &'static impl psu::PsuContainer) -> Result<(), intrusive_list::Error> { + let psu = psu.get_power_policy_device(); + if self.get_psu(psu.id()).is_ok() { return Err(intrusive_list::Error::NodeAlreadyInList); } - self.power_devices.push(device) + self.psu_devices.push(psu) } /// Register a charger with the power policy service pub fn register_charger( &self, - device: &'static impl charger::ChargerContainer, + charger: &'static impl charger::ChargerContainer, ) -> Result<(), intrusive_list::Error> { - let device = device.get_charger(); - if self.get_charger(device.id()).is_ok() { + let charger = charger.get_charger(); + if self.get_charger(charger.id()).is_ok() { return Err(intrusive_list::Error::NodeAlreadyInList); } - self.charger_devices.push(device) + self.charger_devices.push(charger) } - /// Get a device by its ID - pub fn get_device(&self, id: DeviceId) -> Result<&'static device::Device<'static, D, R>, Error> { - for device in &self.power_devices { - if let Some(data) = device.data::>() { + /// Get a PSU by its ID + pub fn get_psu(&self, id: DeviceId) -> Result<&'static psu::RegistrationEntry<'static, D, R>, Error> { + for psu in &self.psu_devices { + if let Some(data) = psu.data::>() { if data.id() == id { return Ok(data); } @@ -148,9 +92,9 @@ where /// Returns the total amount of power that is being supplied to external devices pub async fn compute_total_provider_power_mw(&self) -> u32 { let mut total = 0; - for device in self.power_devices.iter_only::>() { - if let Some(capability) = device.provider_capability().await { - if device.is_provider().await { + for psu in self.psu_devices.iter_only::>() { + if let Some(capability) = psu.provider_capability().await { + if psu.is_provider().await { total += capability.capability.max_power_mw(); } } @@ -183,7 +127,7 @@ where } } - Ok(Ack) + Ok(charger::ChargerResponseData::Ack) } /// Check if charger hardware is ready for communications. @@ -195,7 +139,7 @@ where .inspect_err(|e| error!("Charger {:?} failed CheckReady: {:?}", data.id(), e))?; } } - Ok(Ack) + Ok(charger::ChargerResponseData::Ack) } /// Register a message receiver for power policy messages @@ -216,13 +160,13 @@ where Ok(()) } - /// Provides access to the device list - pub fn devices(&self) -> &intrusive_list::IntrusiveList { - &self.power_devices + /// Provides access to the PSU device list + pub fn psu_devices(&self) -> &intrusive_list::IntrusiveList { + &self.psu_devices } /// Provides access to the charger list - pub fn chargers(&self) -> &intrusive_list::IntrusiveList { + pub fn charger_devices(&self) -> &intrusive_list::IntrusiveList { &self.charger_devices } @@ -234,10 +178,10 @@ where /// Get the next pending device event pub async fn wait_request(&self) -> Request { let mut futures = heapless::Vec::<_, 16>::new(); - for device in self.devices().iter_only::>() { + for psu in self.psu_devices().iter_only::>() { // TODO: Validate Vec size at compile time if futures - .push(async { device.receiver.lock().await.wait_next().await }) + .push(async { psu.receiver.lock().await.wait_next().await }) .is_err() { error!("Futures vec overflow"); @@ -247,13 +191,13 @@ where let (event, index) = select_slice(pin!(&mut futures)).await; // Panic safety: The index is guaranteed to be within bounds since it comes from the select_slice result #[allow(clippy::unwrap_used)] - let device = self - .devices() - .iter_only::>() + let psu = self + .psu_devices() + .iter_only::>() .nth(index) .unwrap(); Request { - id: device.id(), + id: psu.id(), data: event, } } diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs new file mode 100644 index 000000000..4cb96fedb --- /dev/null +++ b/power-policy-service/src/service/mod.rs @@ -0,0 +1,220 @@ +//! Power policy related data structures and messages +pub mod config; +pub mod consumer; +pub mod context; +pub mod provider; +pub mod task; + +pub use context::init; +use embassy_sync::mutex::Mutex; +use embedded_services::{GlobalRawMutex, comms, error, event::Receiver, info, sync::Lockable}; + +use power_policy_interface::{ + capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + psu::{ + DeviceId, Error, Psu, RegistrationEntry, + event::{Request, RequestData}, + }, + service::{ + UnconstrainedState, + event::{CommsData, CommsMessage}, + }, +}; + +const MAX_CONNECTED_PROVIDERS: usize = 4; + +#[derive(Clone, Default)] +struct InternalState { + /// Current consumer state, if any + current_consumer_state: Option, + /// Current provider global state + current_provider_state: provider::State, + /// System unconstrained power + unconstrained: UnconstrainedState, + /// Connected providers + connected_providers: heapless::FnvIndexSet, +} + +/// Power policy service +pub struct Service<'a, D: Lockable, R: Receiver> +where + D::Inner: Psu, +{ + /// Power policy context + pub context: &'a context::Context, + /// State + state: Mutex, + /// Comms endpoint + tp: comms::Endpoint, + /// Config + config: config::Config, +} + +impl<'a, D: Lockable + 'static, R: Receiver + 'static> Service<'a, D, R> +where + D::Inner: Psu, +{ + /// Create a new power policy + pub fn new(context: &'a context::Context, config: config::Config) -> Self { + Self { + context, + state: Mutex::new(InternalState::default()), + tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Power)), + config, + } + } + + async fn process_notify_attach(&self, device: &RegistrationEntry<'_, D, R>) { + if let Err(e) = device.state.lock().await.attach() { + error!("Device{}: Invalid state for attach: {:#?}", device.id().0, e); + } + } + + async fn process_notify_detach(&self, device: &RegistrationEntry<'_, D, R>) -> Result<(), Error> { + device.state.lock().await.detach(); + self.update_current_consumer().await + } + + async fn process_notify_consumer_power_capability( + &self, + device: &RegistrationEntry<'_, D, R>, + capability: Option, + ) -> Result<(), Error> { + if let Err(e) = device.state.lock().await.update_consumer_power_capability(capability) { + error!( + "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", + device.id().0, + e, + ); + } + + self.update_current_consumer().await + } + + async fn process_request_provider_power_capabilities( + &self, + device: &RegistrationEntry<'_, D, R>, + capability: Option, + ) -> Result<(), Error> { + if let Err(e) = device + .state + .lock() + .await + .update_requested_provider_power_capability(capability) + { + error!( + "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", + device.id().0, + e, + ); + } + + self.connect_provider(device.id()).await + } + + async fn process_notify_disconnect(&self, device: &RegistrationEntry<'_, D, R>) -> Result<(), Error> { + if let Err(e) = device.state.lock().await.disconnect(true) { + error!( + "Device{}: Invalid state for notify disconnect, catching up: {:#?}", + device.id().0, + e, + ); + } + + if self + .state + .lock() + .await + .current_consumer_state + .is_some_and(|current| current.device_id == device.id()) + { + info!("Device{}: Connected consumer disconnected", device.id().0); + self.disconnect_chargers().await?; + + self.comms_notify(CommsMessage { + data: CommsData::ConsumerDisconnected(device.id()), + }) + .await; + } + + self.remove_connected_provider(device.id()).await; + self.update_current_consumer().await?; + Ok(()) + } + + /// Send a notification with the comms service + async fn comms_notify(&self, message: CommsMessage) { + self.context.broadcast_message(message).await; + let _ = self + .tp + .send(comms::EndpointID::Internal(comms::Internal::Battery), &message) + .await; + } + + /// Common logic for when a provider is disconnected + /// + /// Returns true if the device was operating as a provider + async fn remove_connected_provider(&self, device_id: DeviceId) -> bool { + if self.state.lock().await.connected_providers.remove(&device_id) { + self.comms_notify(CommsMessage { + data: CommsData::ProviderDisconnected(device_id), + }) + .await; + true + } else { + false + } + } + + async fn wait_request(&self) -> Request { + self.context.wait_request().await + } + + async fn process_request(&self, request: Request) -> Result<(), Error> { + let device = self.context.get_psu(request.id)?; + + match request.data { + RequestData::Attached => { + info!("Received notify attached from device {}", device.id().0); + self.process_notify_attach(device).await; + Ok(()) + } + RequestData::Detached => { + info!("Received notify detached from device {}", device.id().0); + self.process_notify_detach(device).await + } + RequestData::UpdatedConsumerCapability(capability) => { + info!( + "Device{}: Received notify consumer capability: {:#?}", + device.id().0, + capability, + ); + self.process_notify_consumer_power_capability(device, capability).await + } + RequestData::RequestedProviderCapability(capability) => { + info!( + "Device{}: Received request provider capability: {:#?}", + device.id().0, + capability, + ); + self.process_request_provider_power_capabilities(device, capability) + .await + } + RequestData::Disconnected => { + info!("Received notify disconnect from device {}", device.id().0); + self.process_notify_disconnect(device).await + } + } + } + + /// Top-level event loop function + pub async fn process(&self) -> Result<(), Error> { + let request = self.wait_request().await; + self.process_request(request).await + } +} + +impl + 'static> comms::MailboxDelegate for Service<'_, D, R> where + D::Inner: Psu +{ +} diff --git a/power-policy-service/src/provider.rs b/power-policy-service/src/service/provider.rs similarity index 81% rename from power-policy-service/src/provider.rs rename to power-policy-service/src/service/provider.rs index 7187f011c..4179d81ba 100644 --- a/power-policy-service/src/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -1,9 +1,12 @@ //! This file implements logic to determine how much power to provide to each connected device. -//! When total provided power is below [limited_power_threshold_mw](super::Config::limited_power_threshold_mw) -//! the system is in unlimited power state. In this mode up to [provider_unlimited](super::Config::provider_unlimited) +//! When total provided power is below [limited_power_threshold_mw](super::config::Config::limited_power_threshold_mw) +//! the system is in unlimited power state. In this mode up to [provider_unlimited](super::config::Config::provider_unlimited) //! is provided to each device. Above this threshold, the system is in limited power state. -//! In this mode [provider_limited](super::Config::provider_limited) is provided to each device -use embedded_services::{debug, event::Receiver, power::policy::policy::RequestData, trace}; +//! In this mode [provider_limited](super::config::Config::provider_limited) is provided to each device +use embedded_services::error; +use embedded_services::{debug, event::Receiver, trace}; + +use power_policy_interface::psu; use super::*; @@ -25,14 +28,14 @@ pub(super) struct State { state: PowerState, } -impl + 'static> PowerPolicy<'_, D, R> +impl + 'static> Service<'_, D, R> where - D::Inner: DeviceTrait, + D::Inner: Psu, { /// Attempt to connect the requester as a provider pub(super) async fn connect_provider(&self, requester_id: DeviceId) -> Result<(), Error> { trace!("Device{}: Attempting to connect as provider", requester_id.0); - let requester = self.context.get_device(requester_id)?; + let requester = self.context.get_psu(requester_id)?; let requested_power_capability = match requester.requested_provider_capability().await { Some(cap) => cap, // Requester is no longer requesting power @@ -45,14 +48,18 @@ where let mut total_power_mw = 0; // Determine total requested power draw - for device in self.context.devices().iter_only::>() { - let target_provider_cap = if device.id() == requester_id { + for psu in self + .context + .psu_devices() + .iter_only::>() + { + let target_provider_cap = if psu.id() == requester_id { // Use the requester's requested power capability // this handles both new connections and upgrade requests Some(requested_power_capability) } else { // Use the device's current working provider capability - device.provider_capability().await + psu.provider_capability().await }; total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); } @@ -84,14 +91,14 @@ where } }; - let device = self.context.get_device(requester_id)?; - let mut locked_state = device.state.lock().await; - let mut locked_device = device.device.lock().await; + let psu = self.context.get_psu(requester_id)?; + let mut locked_state = psu.state.lock().await; + let mut locked_device = psu.device.lock().await; if let e @ Err(_) = locked_state.connect_provider(target_power) { error!( "Device{}: Cannot provide, device is in state {:#?}", - device.id().0, + psu.id().0, locked_state.state() ); e diff --git a/power-policy-service/src/task.rs b/power-policy-service/src/service/task.rs similarity index 76% rename from power-policy-service/src/task.rs rename to power-policy-service/src/service/task.rs index 4c9c8f1e1..20f7aef9d 100644 --- a/power-policy-service/src/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,12 +1,8 @@ -use embedded_services::{ - comms, error, - event::Receiver, - info, - power::policy::{device::DeviceTrait, policy::RequestData}, - sync::Lockable, -}; +use embedded_services::{comms, error, event::Receiver, info, sync::Lockable}; -use crate::PowerPolicy; +use power_policy_interface::psu::{Psu, event::RequestData}; + +use super::Service; #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -21,10 +17,10 @@ pub enum InitError { /// Runs the power policy task. pub async fn task + 'static>( - policy: &'static PowerPolicy<'static, D, R>, + policy: &'static Service<'static, D, R>, ) -> Result where - D::Inner: DeviceTrait, + D::Inner: Psu, { info!("Starting power policy task"); if comms::register_endpoint(policy, &policy.tp).await.is_err() { diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index f4e00cb71..0244229c1 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,10 +1,10 @@ #![allow(clippy::unwrap_used)] use embassy_sync::signal::Signal; -use embedded_services::power::policy::device::{DeviceTrait, InternalState}; -use embedded_services::power::policy::flags::Consumer; -use embedded_services::power::policy::policy::RequestData; -use embedded_services::power::policy::{ConsumerPowerCapability, Error, PowerCapability, ProviderPowerCapability}; use embedded_services::{GlobalRawMutex, event, info}; +use power_policy_interface::{ + capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + psu::{Error, InternalState, Psu, event::RequestData}, +}; #[derive(Debug, Clone, PartialEq, Eq)] #[allow(dead_code)] @@ -47,7 +47,7 @@ impl<'a, S: event::Sender> Mock<'a, S> { let capability = Some(ConsumerPowerCapability { capability, - flags: Consumer::none(), + flags: ConsumerFlags::none(), }); self.state.update_consumer_power_capability(capability).unwrap(); self.sender @@ -61,7 +61,7 @@ impl<'a, S: event::Sender> Mock<'a, S> { } } -impl<'a, S: event::Sender> DeviceTrait for Mock<'a, S> { +impl<'a, S: event::Sender> Psu for Mock<'a, S> { async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { info!("Connect consumer {:#?}", capability); self.record_fn_call(FnCall::ConnectConsumer(capability)); diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 482ca33af..3707dc196 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -9,11 +9,12 @@ use embassy_sync::{ signal::Signal, }; use embassy_time::{Duration, with_timeout}; -use embedded_services::{ - GlobalRawMutex, - power::policy::{self, DeviceId, PowerCapability, device, policy::RequestData}, -}; -use power_policy_service::PowerPolicy; +use embedded_services::GlobalRawMutex; +use power_policy_interface::capability::PowerCapability; +use power_policy_interface::psu; +use power_policy_interface::psu::DeviceId; +use power_policy_interface::psu::event::RequestData; +use power_policy_service::service::Service; pub mod mock; @@ -39,7 +40,7 @@ const EVENT_CHANNEL_SIZE: usize = 4; async fn power_policy_task( completion_signal: &'static Signal, - power_policy: &'static PowerPolicy< + power_policy: &'static Service< 'static, Mutex>>, DynamicReceiver<'static, RequestData>, @@ -50,19 +51,19 @@ async fn power_policy_task( } } -pub type RegistrationType = device::Device< +pub type RegistrationType = psu::RegistrationEntry< 'static, Mutex>>, DynamicReceiver<'static, RequestData>, >; -pub type ServiceType = PowerPolicy< +pub type ServiceType = Service< 'static, Mutex>>, DynamicReceiver<'static, RequestData>, >; -pub type ServiceContext = policy::policy::Context< +pub type ServiceContext = power_policy_service::service::context::Context< Mutex>>, DynamicReceiver<'static, RequestData>, >; @@ -91,7 +92,8 @@ pub async fn run_test>( let device0 = DEVICE0.init(Mutex::new(Mock::new(device0_sender, device0_signal))); static DEVICE0_REGISTRATION: StaticCell = StaticCell::new(); - let device0_registration = DEVICE0_REGISTRATION.init(device::Device::new(DeviceId(0), device0, device0_receiver)); + let device0_registration = + DEVICE0_REGISTRATION.init(psu::RegistrationEntry::new(DeviceId(0), device0, device0_receiver)); static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); @@ -105,16 +107,17 @@ pub async fn run_test>( let device1 = DEVICE1.init(Mutex::new(Mock::new(device1_sender, device1_signal))); static DEVICE1_REGISTRATION: StaticCell = StaticCell::new(); - let device1_registration = DEVICE1_REGISTRATION.init(device::Device::new(DeviceId(1), device1, device1_receiver)); + let device1_registration = + DEVICE1_REGISTRATION.init(psu::RegistrationEntry::new(DeviceId(1), device1, device1_receiver)); static SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let service_context = SERVICE_CONTEXT.init(policy::policy::Context::new()); + let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - service_context.register_device(device0_registration).unwrap(); - service_context.register_device(device1_registration).unwrap(); + service_context.register_psu(device0_registration).unwrap(); + service_context.register_psu(device1_registration).unwrap(); static POWER_POLICY: StaticCell = StaticCell::new(); - let power_policy = POWER_POLICY.init(power_policy_service::PowerPolicy::new( + let power_policy = POWER_POLICY.init(power_policy_service::service::Service::new( service_context, Default::default(), )); diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index bf104cbc2..91c4950eb 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,10 +1,9 @@ #![allow(clippy::unwrap_used)] use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; use embassy_time::{Duration, TimeoutError, with_timeout}; -use embedded_services::{ - GlobalRawMutex, - power::policy::{ConsumerPowerCapability, flags::Consumer, policy::RequestData}, -}; +use embedded_services::GlobalRawMutex; +use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability}; +use power_policy_interface::psu::event::RequestData; mod common; @@ -33,7 +32,7 @@ async fn test_single( 1, FnCall::ConnectConsumer(ConsumerPowerCapability { capability: LOW_POWER, - flags: Consumer::none(), + flags: ConsumerFlags::none(), }) ) ); @@ -69,7 +68,7 @@ async fn test_swap_higher( 1, FnCall::ConnectConsumer(ConsumerPowerCapability { capability: LOW_POWER, - flags: Consumer::none(), + flags: ConsumerFlags::none(), }) ) ); @@ -91,7 +90,7 @@ async fn test_swap_higher( 1, FnCall::ConnectConsumer(ConsumerPowerCapability { capability: HIGH_POWER, - flags: Consumer::none(), + flags: ConsumerFlags::none(), }) ) ); @@ -113,7 +112,7 @@ async fn test_swap_higher( 1, FnCall::ConnectConsumer(ConsumerPowerCapability { capability: LOW_POWER, - flags: Consumer::none(), + flags: ConsumerFlags::none(), }) ) ); diff --git a/time-alarm-service-messages/Cargo.toml b/time-alarm-service-messages/Cargo.toml index 04e172f7d..7d883b7a0 100644 --- a/time-alarm-service-messages/Cargo.toml +++ b/time-alarm-service-messages/Cargo.toml @@ -13,7 +13,7 @@ log = { workspace = true, optional = true } embedded-mcu-hal.workspace = true embedded-services.workspace = true num_enum.workspace = true -zerocopy.workspace = true +zerocopy = { workspace = true, features = ["derive"] } [features] defmt = ["dep:defmt", "embedded-mcu-hal/defmt"] diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 75f6e1ebd..5b3fb5bd0 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -27,6 +27,9 @@ heapless.workspace = true log = { workspace = true, optional = true } tps6699x = { workspace = true, features = ["embassy"] } cfu-service.workspace = true +power-policy-interface.workspace = true +power-policy-service = { path = "../power-policy-service" } +bitvec.workspace = true [dev-dependencies] embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } @@ -47,6 +50,8 @@ defmt = [ "tps6699x/defmt", "embedded-usb-pd/defmt", "cfu-service/defmt", + "power-policy-interface/defmt", + "power-policy-service/defmt", ] log = [ "dep:log", @@ -55,4 +60,6 @@ log = [ "embassy-sync/log", "tps6699x/log", "cfu-service/log", + "power-policy-interface/log", + "power-policy-service/log", ] diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index c6c1f8e19..8470af87f 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -1,3 +1,10 @@ +use crate::type_c; +use crate::type_c::ATTN_VDM_LEN; +use crate::type_c::controller::{ + self, AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, + TypeCStateMachineState, UsbControlConfig, +}; +use crate::type_c::event::PortEvent; use ::tps6699x::registers::field_sets::IntEventBus1; use ::tps6699x::registers::{PdCcPullUp, PpExtVbusSw, PpIntVbusSw}; use ::tps6699x::{PORT0, PORT1, TPS66993_NUM_PORTS, TPS66994_NUM_PORTS}; @@ -9,14 +16,7 @@ use core::iter::zip; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_time::Delay; use embedded_hal_async::i2c::I2c; -use embedded_services::power::policy::PowerCapability; -use embedded_services::type_c::ATTN_VDM_LEN; -use embedded_services::type_c::controller::{ - self, AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, - TypeCStateMachineState, UsbControlConfig, -}; -use embedded_services::type_c::event::PortEvent; -use embedded_services::{debug, error, trace, type_c, warn}; +use embedded_services::{debug, error, trace, warn}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::pdinfo::PowerPathStatus; use embedded_usb_pd::pdo::{Common, Contract, Rdo, sink, source}; @@ -36,6 +36,9 @@ use tps6699x::command::{ use tps6699x::fw_update::UpdateConfig as FwUpdateConfig; use tps6699x::registers::port_config::TypeCStateMachine; +use crate::type_c::power_capability_from_current; +use crate::type_c::power_capability_try_from_contract; + type Updater<'a, M, B> = BorrowedUpdaterInProgress>; /// Firmware update state @@ -275,9 +278,8 @@ impl Controller for Tps6699x<'_, M, B> { /// /// Drop safety: All state changes happen after await point async fn clear_port_events(&mut self, port: LocalPortId) -> Result> { - Ok(core::mem::replace( + Ok(core::mem::take( self.port_events.get_mut(port.0 as usize).ok_or(PdError::InvalidPort)?, - PortEvent::none(), )) } @@ -314,7 +316,8 @@ impl Controller for Tps6699x<'_, M, B> { let rdo = Rdo::for_pdo(rdo_raw, pdo).ok_or(Error::Pd(PdError::InvalidParams))?; debug!("PDO: {:#?}", pdo); debug!("RDO: {:#?}", rdo); - port_status.available_source_contract = Contract::from_source(pdo, rdo).try_into().ok(); + port_status.available_source_contract = + power_capability_try_from_contract(Contract::from_source(pdo, rdo)); port_status.dual_power = pdo.dual_role_power(); } else { // active_rdo_contract doesn't contain the full picture @@ -337,7 +340,8 @@ impl Controller for Tps6699x<'_, M, B> { let rdo = Rdo::for_pdo(rdo_raw, pdo).ok_or(Error::Pd(PdError::InvalidParams))?; debug!("PDO: {:#?}", pdo); debug!("RDO: {:#?}", rdo); - port_status.available_sink_contract = Contract::from_sink(pdo, rdo).try_into().ok(); + port_status.available_sink_contract = + power_capability_try_from_contract(Contract::from_sink(pdo, rdo)); port_status.dual_power = source_pdos[0].dual_role_power(); port_status.unconstrained_power = source_pdos[0].unconstrained_power(); } @@ -345,8 +349,7 @@ impl Controller for Tps6699x<'_, M, B> { // Implicit source contract let current = TypecCurrent::try_from(port_control.typec_current()).map_err(Error::Pd)?; debug!("Port{} type-C source current: {:#?}", port.0, current); - let new_contract = Some(PowerCapability::from(current)); - port_status.available_source_contract = new_contract; + port_status.available_source_contract = Some(power_capability_from_current(current)); } else { // Implicit sink contract let pull = pd_status.cc_pull_up(); @@ -357,7 +360,7 @@ impl Controller for Tps6699x<'_, M, B> { } else { let current = TypecCurrent::try_from(pd_status.cc_pull_up()).map_err(Error::Pd)?; debug!("Port{} type-C sink current: {:#?}", port.0, current); - Some(PowerCapability::from(current)) + Some(power_capability_from_current(current)) }; port_status.available_sink_contract = new_contract; } diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index b1d0a5f11..c2bb53404 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -2,13 +2,12 @@ pub mod driver; pub mod service; pub mod task; +pub mod type_c; pub mod wrapper; use core::future::Future; -use embedded_services::type_c::event::{ - PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged, -}; +use type_c::event::{PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged}; /// Enum to contain all port event variants #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -123,7 +122,7 @@ impl PortEventStreamer { mod tests { use core::sync::atomic::AtomicBool; - use embedded_services::type_c::event::PortPending; + use crate::type_c::event::PortPending; use super::*; diff --git a/type-c-service/src/service/controller.rs b/type-c-service/src/service/controller.rs index f4034e7cd..408fc2efc 100644 --- a/type-c-service/src/service/controller.rs +++ b/type-c-service/src/service/controller.rs @@ -1,12 +1,10 @@ -use embedded_services::{ - debug, error, - type_c::{ - ControllerId, - external::{self, ControllerCommandData}, - }, -}; +use embedded_services::{debug, error}; use super::*; +use crate::type_c::{ + ControllerId, + external::{self, ControllerCommandData}, +}; impl<'a> Service<'a> { /// Process external controller status command diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 90163392d..449e695c2 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -4,24 +4,18 @@ use embassy_sync::{ pubsub::{DynImmediatePublisher, DynSubscriber}, }; use embedded_services::{ - GlobalRawMutex, debug, error, - event::Receiver, - info, intrusive_list, - ipc::deferred, - power::policy::policy, - sync::Lockable, - trace, - type_c::{ - self, comms, - controller::PortStatus, - event::{PortNotificationSingle, PortStatusChanged}, - external, - }, + GlobalRawMutex, debug, error, event::Receiver, info, intrusive_list, ipc::deferred, sync::Lockable, trace, }; -use embedded_services::{power::policy as power_policy, type_c::Cached}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; +use crate::type_c::{ + self, Cached, comms, + controller::PortStatus, + event::{PortNotificationSingle, PortStatusChanged}, + external, +}; + use crate::{PortEventStreamer, PortEventVariant}; pub mod config; @@ -62,12 +56,14 @@ pub struct Service<'a> { /// /// This is the corresponding publisher to [`Self::power_policy_event_subscriber`], power policy events /// will be buffered in the channel until they are brought into the event loop with the subscriber. - power_policy_event_publisher: embedded_services::broadcaster::immediate::Receiver<'a, power_policy::CommsMessage>, + power_policy_event_publisher: + embedded_services::broadcaster::immediate::Receiver<'a, power_policy_interface::service::event::CommsMessage>, /// Power policy event subscriber /// /// This is the corresponding subscriber to [`Self::power_policy_event_publisher`], needs to be a mutex because getting a message /// from the channel requires mutable access. - power_policy_event_subscriber: Mutex>, + power_policy_event_subscriber: + Mutex>, } /// Power policy events @@ -76,7 +72,7 @@ pub struct Service<'a> { // But there's currently not a way to do look-ups between power policy device IDs and GlobalPortIds pub enum PowerPolicyEvent { /// Unconstrained state changed - Unconstrained(power_policy::UnconstrainedState), + Unconstrained(power_policy_interface::service::UnconstrainedState), /// Consumer disconnected ConsumerDisconnected, /// Consumer connected @@ -99,10 +95,10 @@ impl<'a> Service<'a> { /// Create a new service the given configuration pub fn create( config: config::Config, - context: &'a embedded_services::type_c::controller::Context, + context: &'a crate::type_c::controller::Context, controller_list: &'a intrusive_list::IntrusiveList, - power_policy_publisher: DynImmediatePublisher<'a, power_policy::CommsMessage>, - power_policy_subscriber: DynSubscriber<'a, power_policy::CommsMessage>, + power_policy_publisher: DynImmediatePublisher<'a, power_policy_interface::service::event::CommsMessage>, + power_policy_subscriber: DynSubscriber<'a, power_policy_interface::service::event::CommsMessage>, ) -> Self { Self { context, @@ -256,12 +252,15 @@ impl<'a> Service<'a> { } /// Register the Type-C service with the power policy service - pub fn register_comms + 'static>( + pub fn register_comms< + PD: Lockable + 'static, + PR: Receiver + 'static, + >( &'static self, - power_policy_context: &power_policy::policy::Context, + power_policy_context: &power_policy_service::service::context::Context, ) -> Result<(), intrusive_list::Error> where - PD::Inner: embedded_services::power::policy::device::DeviceTrait, + PD::Inner: power_policy_interface::psu::Psu, { power_policy_context.register_message_receiver(&self.power_policy_event_publisher) } diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs index 80517dc8f..b138e82b7 100644 --- a/type-c-service/src/service/port.rs +++ b/type-c-service/src/service/port.rs @@ -1,16 +1,14 @@ -use embedded_services::{ - debug, error, - type_c::{ - controller::{DpConfig, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, - external, - }, -}; +use embedded_services::{debug, error}; use embedded_usb_pd::GlobalPortId; use super::*; use crate::PortEventStreamer; -use embedded_services::type_c::controller::SendVdm; +use crate::type_c::controller::SendVdm; +use crate::type_c::{ + controller::{DpConfig, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, + external, +}; impl<'a> Service<'a> { /// Wait for port flags diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index 64c77a8c8..72e7a6c01 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -1,5 +1,5 @@ use embassy_sync::pubsub::WaitResult; -use embedded_services::power::policy as power_policy; +use power_policy_interface::service as power_policy; use super::*; @@ -13,13 +13,13 @@ impl<'a> Service<'a> { error!("Power policy {} event(s) lagged", lagged); } WaitResult::Message(message) => match message.data { - power_policy::CommsData::Unconstrained(state) => { + power_policy_interface::service::event::CommsData::Unconstrained(state) => { return Event::PowerPolicy(PowerPolicyEvent::Unconstrained(state)); } - power_policy::CommsData::ConsumerDisconnected(_) => { + power_policy_interface::service::event::CommsData::ConsumerDisconnected(_) => { return Event::PowerPolicy(PowerPolicyEvent::ConsumerDisconnected); } - power_policy::CommsData::ConsumerConnected(_, _) => { + power_policy_interface::service::event::CommsData::ConsumerConnected(_, _) => { return Event::PowerPolicy(PowerPolicyEvent::ConsumerConnected); } _ => { diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index 43e97e5e2..adc46f4ec 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,7 +1,7 @@ //! VDM (Vendor Defined Messages) related functionality. +use crate::type_c::controller::{AttnVdm, OtherVdm}; use embedded_services::intrusive_list; -use embedded_services::type_c::controller::{AttnVdm, OtherVdm}; use embedded_usb_pd::{GlobalPortId, PdError}; use super::Service; diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index 8f47d0ebd..aea7981f5 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,6 +1,8 @@ use core::future::Future; use embassy_sync::mutex::Mutex; -use embedded_services::{error, event, info, power::policy::policy, sync::Lockable}; +use embedded_services::{error, event, info, sync::Lockable}; + +use power_policy_service::service::context::Context as PowerPolicyContext; use crate::{ service::Service, @@ -11,16 +13,16 @@ use crate::{ pub async fn task_closure<'a, M, D, S, R, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( service: &'static Service<'a>, wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], - power_policy_context: &policy::Context>, R>, + power_policy_context: &PowerPolicyContext>, R>, cfu_client: &'a cfu_service::CfuClient, f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: crate::wrapper::FwOfferValidator, - D::Inner: embedded_services::type_c::controller::Controller, + D::Inner: crate::type_c::controller::Controller, { info!("Starting type-c task"); @@ -48,15 +50,15 @@ pub async fn task_closure<'a, M, D, S, R, V, Fut: Future, F: Fn(&'a pub async fn task<'a, M, D, S, R, V, const N: usize>( service: &'static Service<'a>, wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], - power_policy_context: &policy::Context>, R>, + power_policy_context: &PowerPolicyContext>, R>, cfu_client: &'a cfu_service::CfuClient, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: embedded_services::sync::Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: crate::wrapper::FwOfferValidator, - ::Inner: embedded_services::type_c::controller::Controller, + ::Inner: crate::type_c::controller::Controller, { task_closure( service, diff --git a/embedded-service/src/type_c/comms.rs b/type-c-service/src/type_c/comms.rs similarity index 100% rename from embedded-service/src/type_c/comms.rs rename to type-c-service/src/type_c/comms.rs diff --git a/embedded-service/src/type_c/controller.rs b/type-c-service/src/type_c/controller.rs similarity index 99% rename from embedded-service/src/type_c/controller.rs rename to type-c-service/src/type_c/controller.rs index 380987d09..6f20fd8fc 100644 --- a/embedded-service/src/type_c/controller.rs +++ b/type-c-service/src/type_c/controller.rs @@ -12,12 +12,13 @@ use embedded_usb_pd::{ }; use super::{ATTN_VDM_LEN, ControllerId, OTHER_VDM_LEN, external}; -use crate::ipc::deferred; -use crate::power::policy; use crate::type_c::Cached; use crate::type_c::comms::CommsMessage; use crate::type_c::event::{PortEvent, PortPending}; -use crate::{GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace}; +use embedded_services::ipc::deferred; +use embedded_services::{ + GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace, +}; /// maximum number of data objects in a VDM pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each @@ -27,9 +28,9 @@ pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PortStatus { /// Current available source contract - pub available_source_contract: Option, + pub available_source_contract: Option, /// Current available sink contract - pub available_sink_contract: Option, + pub available_sink_contract: Option, /// Current connection state pub connection_state: Option, /// Port partner supports dual-power roles diff --git a/embedded-service/src/type_c/event.rs b/type-c-service/src/type_c/event.rs similarity index 99% rename from embedded-service/src/type_c/event.rs rename to type-c-service/src/type_c/event.rs index df7dbd4bc..eb951079d 100644 --- a/embedded-service/src/type_c/event.rs +++ b/type-c-service/src/type_c/event.rs @@ -4,9 +4,9 @@ //! Processing these events typically requires acessing similar registers so they are grouped together. //! [`PortNotification`] contains events that are typically more message-like (PD alerts, VDMs, etc) and can be processed independently. //! Consequently [`PortNotification`] implements iterator traits to allow for processing these events as a stream. -use super::error; use bitfield::bitfield; use bitvec::BitArr; +use embedded_services::error; bitfield! { /// Raw bitfield of possible port status events diff --git a/embedded-service/src/type_c/external.rs b/type-c-service/src/type_c/external.rs similarity index 98% rename from embedded-service/src/type_c/external.rs rename to type-c-service/src/type_c/external.rs index 7216efd2b..ef253db2a 100644 --- a/embedded-service/src/type_c/external.rs +++ b/type-c-service/src/type_c/external.rs @@ -1,19 +1,18 @@ //! Message definitions for external type-C commands use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError, ucsi}; -use crate::{ - intrusive_list, - type_c::{ - Cached, - controller::{Context, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, - }, -}; +use embedded_services::intrusive_list; use super::{ ControllerId, controller::{ControllerStatus, DpConfig, DpStatus, PortStatus, RetimerFwUpdateState, SendVdm, lookup_controller}, }; +use crate::type_c::{ + Cached, + controller::{Context, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, +}; + /// Data for controller-specific commands #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/type-c-service/src/type_c/mod.rs b/type-c-service/src/type_c/mod.rs new file mode 100644 index 000000000..a5100bf17 --- /dev/null +++ b/type-c-service/src/type_c/mod.rs @@ -0,0 +1,68 @@ +//! Type-C service +use embedded_usb_pd::pdo::{Common, Contract}; +use embedded_usb_pd::type_c; + +pub mod comms; +pub mod controller; +pub mod event; +pub mod external; + +/// Controller ID +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControllerId(pub u8); + +/// Length of the Other VDM data +pub const OTHER_VDM_LEN: usize = 29; +/// Length of the Attention VDM data +pub const ATTN_VDM_LEN: usize = 9; + +pub fn power_capability_try_from_contract( + contract: Contract, +) -> Option { + Some(power_policy_interface::capability::PowerCapability { + voltage_mv: contract.pdo.max_voltage_mv(), + current_ma: contract.operating_current_ma()?, + }) +} + +pub fn power_capability_from_current(current: type_c::Current) -> power_policy_interface::capability::PowerCapability { + power_policy_interface::capability::PowerCapability { + voltage_mv: 5000, + // Assume higher power for now + current_ma: current.to_ma(false), + } +} + +/// Type-C USB2 power capability 5V@500mA +pub const POWER_CAPABILITY_USB_DEFAULT_USB2: power_policy_interface::capability::PowerCapability = + power_policy_interface::capability::PowerCapability { + voltage_mv: 5000, + current_ma: 500, + }; + +/// Type-C USB3 power capability 5V@900mA +pub const POWER_CAPABILITY_USB_DEFAULT_USB3: power_policy_interface::capability::PowerCapability = + power_policy_interface::capability::PowerCapability { + voltage_mv: 5000, + current_ma: 900, + }; + +/// Type-C power capability 5V@1.5A +pub const POWER_CAPABILITY_5V_1A5: power_policy_interface::capability::PowerCapability = + power_policy_interface::capability::PowerCapability { + voltage_mv: 5000, + current_ma: 1500, + }; + +/// Type-C power capability 5V@3A +pub const POWER_CAPABILITY_5V_3A0: power_policy_interface::capability::PowerCapability = + power_policy_interface::capability::PowerCapability { + voltage_mv: 5000, + current_ma: 3000, + }; + +/// Newtype to help clarify arguments to port status commands +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Cached(pub bool); diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index d93f03534..ceed5aae9 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -1,64 +1,6 @@ //! Various types of state and objects required for [`crate::wrapper::ControllerWrapper`]. //! -//! The wrapper needs per-port state which ultimately needs to come from something like an array. -//! We need to erase the generic `N` parameter from that storage so as not to monomorphize the entire -//! wrapper over it. This module provides the necessary types and traits to do so. Things required by -//! the wrapper can be split into two categories: objects used for service registration (which must be immutable), -//! and mutable state. These are represented by the [`Registration`] and [`DynPortState`] respectively. The later -//! is a trait intended to be used as a trait object to erase the generic port count. -//! -//! [`Storage`] is the base storage type and is generic over the number of ports. However, there are additional -//! objects that need to reference the storage. To avoid a self-referential -//! struct, [`ReferencedStorage`] contains these. This struct is still generic over the number of ports. -//! -//! Lastly, [`Backing`] contains references to the registration and type-erased state and is what is passed -//! to the wrapper. -//! -//! Example usage: -//! ``` -//! use embassy_sync::blocking_mutex::raw::NoopRawMutex; -//! use static_cell::StaticCell; -//! use embedded_services::type_c::ControllerId; -//! use embedded_services::power; -//! use embedded_usb_pd::GlobalPortId; -//! use type_c_service::wrapper::backing::{Storage, IntermediateStorage, ReferencedStorage}; -//! use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; -//! use embedded_services::power::policy::policy; -//! -//! fn init(context: &'static embedded_services::type_c::controller::Context) { -//! static STORAGE: StaticCell> = StaticCell::new(); -//! let storage = STORAGE.init(Storage::new( -//! context, -//! ControllerId(0), -//! 0x0, // CFU component ID (unused) -//! [GlobalPortId(0)], -//! )); -//! -//! static INTERMEDIATE: StaticCell> = -//! StaticCell::new(); -//! let intermediate = INTERMEDIATE.init(storage.try_create_intermediate().expect("Failed to create intermediate storage")); -//! -//! static POLICY_CHANNEL: StaticCell> = StaticCell::new(); -//! let policy_channel = POLICY_CHANNEL.init(Channel::new()); -//! -//! let policy_sender = policy_channel.dyn_sender(); -//! let policy_receiver = policy_channel.dyn_receiver(); -//! -//! static REFERENCED: StaticCell< -//! type_c_service::wrapper::backing::ReferencedStorage< -//! 1, -//! NoopRawMutex, -//! DynamicSender<'_, policy::RequestData>, -//! DynamicReceiver<'_, policy::RequestData>, -//! >, -//! > = StaticCell::new(); -//! let referenced = REFERENCED.init( -//! intermediate -//! .try_create_referenced([(power::policy::DeviceId(0), policy_sender, policy_receiver)]) -//! .expect("Failed to create referenced storage"), -//! ); -//! } -//! ``` +//! TODO: Update as part of type-C refactoring use core::{ array::from_fn, cell::{RefCell, RefMut}, @@ -72,20 +14,16 @@ use embassy_sync::{ }; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; -use embedded_services::{ - event, - power::{ - self, - policy::{DeviceId, policy}, - }, - type_c::{ - ControllerId, - controller::PortStatus, - event::{PortEvent, PortStatusChanged}, - }, -}; +use embedded_services::event; use embedded_usb_pd::{GlobalPortId, ado::Ado}; +use crate::type_c::{ + ControllerId, + controller::PortStatus, + event::{PortEvent, PortStatusChanged}, +}; +use power_policy_interface::psu::DeviceId; + use crate::{ PortEventStreamer, wrapper::{ @@ -129,13 +67,13 @@ impl Default for ControllerState { } /// Internal state containing all per-port and per-controller state -struct InternalState<'a, const N: usize, S: event::Sender> { +struct InternalState<'a, const N: usize, S: event::Sender> { controller_state: ControllerState, port_states: [PortState<'a>; N], port_power: [PortPower; N], } -impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { +impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { fn try_new(storage: &'a Storage, power_events: [S; N]) -> Option { let port_states = storage.pd_alerts.each_ref().map(|pd_alert| { Some(PortState { @@ -164,7 +102,9 @@ impl<'a, const N: usize, S: event::Sender> InternalState<'a } } -impl<'a, const N: usize, S: event::Sender> DynPortState<'a, S> for InternalState<'a, N, S> { +impl<'a, const N: usize, S: event::Sender> DynPortState<'a, S> + for InternalState<'a, N, S> +{ fn num_ports(&self) -> usize { self.port_states.len() } @@ -195,7 +135,7 @@ impl<'a, const N: usize, S: event::Sender> DynPortState<'a, } /// Trait to erase the generic port count argument -pub trait DynPortState<'a, S: event::Sender> { +pub trait DynPortState<'a, S: event::Sender> { fn num_ports(&self) -> usize; fn port_states(&self) -> &[PortState<'a>]; @@ -209,14 +149,14 @@ pub trait DynPortState<'a, S: event::Sender> { } /// Service registration objects -pub struct Registration<'a, M: RawMutex, R: event::Receiver> { - pub context: &'a embedded_services::type_c::controller::Context, - pub pd_controller: &'a embedded_services::type_c::controller::Device<'a>, +pub struct Registration<'a, M: RawMutex, R: event::Receiver> { + pub context: &'a crate::type_c::controller::Context, + pub pd_controller: &'a crate::type_c::controller::Device<'a>, pub cfu_device: &'a CfuDevice, - pub power_devices: &'a [embedded_services::power::policy::device::Device<'a, Mutex>, R>], + pub power_devices: &'a [power_policy_interface::psu::RegistrationEntry<'a, Mutex>, R>], } -impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, M, R> { +impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, M, R> { pub fn num_ports(&self) -> usize { self.power_devices.len() } @@ -225,15 +165,15 @@ impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, /// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. const MAX_BUFFERED_PD_ALERTS: usize = 4; -pub struct PortPower> { +pub struct PortPower> { pub sender: S, - pub state: power::policy::device::InternalState, + pub state: power_policy_interface::psu::InternalState, } /// Base storage pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related - context: &'a embedded_services::type_c::controller::Context, + context: &'a crate::type_c::controller::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: CfuDevice, @@ -245,7 +185,7 @@ pub struct Storage<'a, const N: usize, M: RawMutex> { impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( - context: &'a embedded_services::type_c::controller::Context, + context: &'a crate::type_c::controller::Context, controller_id: ControllerId, cfu_id: ComponentId, pd_ports: [GlobalPortId; N], @@ -295,7 +235,11 @@ impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { } /// Create referenced storage from this intermediate storage - pub fn try_create_referenced<'b, S: event::Sender, R: event::Receiver>( + pub fn try_create_referenced< + 'b, + S: event::Sender, + R: event::Receiver, + >( &'b self, policy_args: [(DeviceId, S, R); N], ) -> Option> @@ -314,17 +258,22 @@ pub struct ReferencedStorage< 'a, const N: usize, M: RawMutex, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, > { intermediate: &'a IntermediateStorage<'a, N, M>, state: RefCell>, - pd_controller: embedded_services::type_c::controller::Device<'a>, - power_devices: [embedded_services::power::policy::device::Device<'a, Mutex>, R>; N], + pd_controller: crate::type_c::controller::Device<'a>, + power_devices: [power_policy_interface::psu::RegistrationEntry<'a, Mutex>, R>; N], } -impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: event::Receiver> - ReferencedStorage<'a, N, M, S, R> +impl< + 'a, + const N: usize, + M: RawMutex, + S: event::Sender, + R: event::Receiver, +> ReferencedStorage<'a, N, M, S, R> { /// Create a new referenced storage from the given intermediate storage fn try_from_intermediate( @@ -337,7 +286,7 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: for (i, (device_id, policy_sender, policy_receiver)) in policy_args.into_iter().enumerate() { power_senders.push(policy_sender).ok()?; power_devices - .push(embedded_services::power::policy::device::Device::new( + .push(power_policy_interface::psu::RegistrationEntry::new( device_id, intermediate.power_proxy_devices.get(i)?, policy_receiver, @@ -352,7 +301,7 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: // Safe because both have N elements power_senders.into_array().ok()?, )?), - pd_controller: embedded_services::type_c::controller::Device::new( + pd_controller: crate::type_c::controller::Device::new( intermediate.storage.controller_id, intermediate.storage.pd_ports.as_slice(), ), @@ -379,7 +328,12 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, R: } /// Wrapper around registration and type-erased state -pub struct Backing<'a, M: RawMutex, S: event::Sender, R: event::Receiver> { +pub struct Backing< + 'a, + M: RawMutex, + S: event::Sender, + R: event::Receiver, +> { pub(crate) registration: Registration<'a, M, R>, pub(crate) state: RefMut<'a, dyn DynPortState<'a, S>>, pub(crate) power_receivers: &'a [Mutex>], diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index b7d1910b2..d82f0c38e 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,11 +1,9 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation +use crate::type_c::controller::Controller; use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; -use embedded_services::power; -use embedded_services::power::policy::policy; -use embedded_services::type_c::controller::Controller; use embedded_services::{debug, error}; use super::message::EventCfu; @@ -34,8 +32,8 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > ControllerWrapper<'device, M, D, S, R, V> where @@ -151,9 +149,12 @@ where let controller_id = self.registration.pd_controller.id(); for power in state.port_power_mut() { info!("Controller{}: checking power device", controller_id.0); - if power.state.state() != power::policy::device::State::Detached { + if power.state.state() != power_policy_interface::psu::State::Detached { info!("Controller{}: Detaching power device", controller_id.0); - power.sender.send(policy::RequestData::Detached).await; + power + .sender + .send(power_policy_interface::psu::event::RequestData::Detached) + .await; } } diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index d54fb7b1b..0f634473a 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,15 +1,16 @@ use super::{ControllerWrapper, FwOfferValidator}; +use crate::type_c::controller::Controller; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_services::{event, power::policy::policy, sync::Lockable, trace, type_c::controller::Controller}; +use embedded_services::{event, sync::Lockable, trace}; use embedded_usb_pd::{Error, LocalPortId}; impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > ControllerWrapper<'device, M, D, S, R, V> where diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index c9b1b66fd..c49d31475 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -1,15 +1,12 @@ //! [`crate::wrapper::ControllerWrapper`] message types -use embedded_services::{ - GlobalRawMutex, - ipc::deferred, - power::policy, - type_c::{ - controller::{self, DpStatus, PortStatus}, - event::{PortNotificationSingle, PortStatusChanged}, - }, -}; +use embedded_services::{GlobalRawMutex, ipc::deferred}; use embedded_usb_pd::{LocalPortId, ado::Ado}; +use crate::type_c::{ + controller::{self, DpStatus, PortStatus}, + event::{PortNotificationSingle, PortStatusChanged}, +}; + /// Port status changed event data #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -35,7 +32,7 @@ pub struct EventPowerPolicyCommand { /// Port ID pub port: LocalPortId, /// Power policy request - pub request: policy::device::CommandData, + pub request: power_policy_interface::psu::CommandData, } /// CFU events @@ -91,7 +88,7 @@ pub struct OutputPowerPolicyCommand { /// Port ID pub port: LocalPortId, /// Response - pub response: policy::device::InternalResponseData, + pub response: power_policy_interface::psu::InternalResponseData, } /// Controller command output data @@ -105,7 +102,7 @@ pub struct OutputControllerCommand<'a> { pub mod vdm { //! Events and output for vendor-defined messaging. use super::LocalPortId; - use embedded_services::type_c::controller::{AttnVdm, OtherVdm}; + use crate::type_c::controller::{AttnVdm, OtherVdm}; /// The kind of output from processing a vendor-defined message. #[derive(Copy, Clone, Debug)] diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index e58ff41e3..15dd4d258 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -1,9 +1,8 @@ //! This module contains the [`ControllerWrapper`] struct. This struct serves as a bridge between various service messages -//! and the actual controller functions provided by [`embedded_services::type_c::controller::Controller`]. +//! and the actual controller functions provided by [`crate::type_c::controller::Controller`]. //! # Supported service messaging //! This struct current currently supports messages from the following services: -//! * Type-C: [`embedded_services::type_c::controller::Command`] -//! * Power policy: [`embedded_services::power::policy::device::Command`] +//! * Type-C: [`crate::type_c::controller::Command`] //! * CFU: [`cfu_service::Request`] //! # Event loop //! This struct follows a standard wait/process/finalize event loop. @@ -21,6 +20,8 @@ use core::cell::RefMut; use core::future::pending; use core::ops::DerefMut; +use crate::type_c::controller::{self, Controller, PortStatus}; +use crate::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; use cfu_service::CfuClient; use embassy_futures::select::{Either, Either5, select, select_array, select5}; use embassy_sync::blocking_mutex::raw::RawMutex; @@ -28,10 +29,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::power::policy::policy; use embedded_services::sync::Lockable; -use embedded_services::type_c::controller::{self, Controller, PortStatus}; -use embedded_services::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; use embedded_services::{debug, error, info, trace, warn}; use embedded_services::{event, intrusive_list}; use embedded_usb_pd::ado::Ado; @@ -69,13 +67,13 @@ pub trait FwOfferValidator { /// Maximum number of supported ports pub const MAX_SUPPORTED_PORTS: usize = 2; -/// Common functionality implemented on top of [`embedded_services::type_c::controller::Controller`] +/// Common functionality implemented on top of [`crate::type_c::controller::Controller`] pub struct ControllerWrapper< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > where D::Inner: Controller, @@ -101,8 +99,8 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > ControllerWrapper<'device, M, D, S, R, V> where @@ -205,10 +203,16 @@ where info!("Plug event"); if status.is_connected() { info!("Plug inserted"); - power.sender.send(policy::RequestData::Attached).await; + power + .sender + .send(power_policy_interface::psu::event::RequestData::Attached) + .await; } else { info!("Plug removed"); - power.sender.send(policy::RequestData::Detached).await; + power + .sender + .send(power_policy_interface::psu::event::RequestData::Detached) + .await; } Ok(()) @@ -607,14 +611,11 @@ where pub fn register( &'static self, controllers: &intrusive_list::IntrusiveList, - power_policy_context: &embedded_services::power::policy::policy::Context< - Mutex>, - R, - >, + power_policy_context: &power_policy_service::service::context::Context>, R>, cfu_client: &'static CfuClient, ) -> Result<(), Error<::BusError>> { for device in self.registration.power_devices { - power_policy_context.register_device(device).map_err(|_| { + power_policy_context.register_psu(device).map_err(|_| { error!( "Controller{}: Failed to register power device {}", self.registration.pd_controller.id().0, @@ -648,8 +649,8 @@ impl< 'device, M: RawMutex, C: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > Lockable for ControllerWrapper<'device, M, C, S, R, V> where diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index a47fd93e5..6a306d182 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,12 +1,12 @@ +use crate::type_c::Cached; +use crate::type_c::controller::{InternalResponseData, Response}; use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; use embedded_services::debug; -use embedded_services::power::policy::device::State; -use embedded_services::type_c::Cached; -use embedded_services::type_c::controller::{InternalResponseData, Response}; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; use embedded_usb_pd::ucsi::{self, lpm}; +use power_policy_interface::psu::State; use super::*; @@ -14,8 +14,8 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > ControllerWrapper<'device, M, D, S, R, V> where @@ -148,7 +148,10 @@ where "Port{}: Disconnecting consumer before setting max sink voltage", local_port.0 ); - port_power.sender.send(policy::RequestData::Disconnected).await; + port_power + .sender + .send(power_policy_interface::psu::event::RequestData::Disconnected) + .await; } } diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 2c2267193..e778b5042 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -2,17 +2,12 @@ use core::pin::pin; use embassy_futures::select::select_slice; -use embedded_services::{ - debug, - power::policy::{ - ConsumerPowerCapability, ProviderPowerCapability, - device::{CommandData, InternalResponseData, ResponseData}, - flags::PsuType, - }, -}; +use embedded_services::debug; -use embedded_services::power::policy::Error as PowerError; -use embedded_services::power::policy::device::CommandData as PowerCommand; +use power_policy_interface::capability::{ConsumerPowerCapability, ProviderPowerCapability, PsuType}; +use power_policy_interface::psu::CommandData as PowerCommand; +use power_policy_interface::psu::Error as PowerError; +use power_policy_interface::psu::{CommandData, InternalResponseData, ResponseData}; use crate::wrapper::config::UnconstrainedSink; @@ -22,8 +17,8 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > ControllerWrapper<'device, M, D, S, R, V> where @@ -50,7 +45,7 @@ where power .sender - .send(policy::RequestData::UpdatedConsumerCapability(available_sink_contract)) + .send(power_policy_interface::psu::event::RequestData::UpdatedConsumerCapability(available_sink_contract)) .await; Ok(()) } @@ -64,13 +59,15 @@ where info!("Process New provider contract"); power .sender - .send(policy::RequestData::RequestedProviderCapability( - status.available_source_contract.map(|caps| { - let mut caps = ProviderPowerCapability::from(caps); - caps.flags.set_psu_type(PsuType::TypeC); - caps - }), - )) + .send( + power_policy_interface::psu::event::RequestData::RequestedProviderCapability( + status.available_source_contract.map(|caps| { + let mut caps = ProviderPowerCapability::from(caps); + caps.flags.set_psu_type(PsuType::TypeC); + caps + }), + ), + ) .await; Ok(()) } diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs index 252247e88..c8f95110a 100644 --- a/type-c-service/src/wrapper/proxy.rs +++ b/type-c-service/src/wrapper/proxy.rs @@ -1,9 +1,6 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; -use embedded_services::power; -use embedded_services::power::policy::device::{ - CommandData as PolicyCommandData, DeviceTrait, InternalResponseData as PolicyResponseData, -}; +use power_policy_interface::psu::{CommandData as PolicyCommandData, InternalResponseData as PolicyResponseData, Psu}; pub struct PowerProxyChannel { command_channel: Channel, @@ -74,15 +71,15 @@ impl<'a> PowerProxyDevice<'a> { } } -impl<'a> DeviceTrait for PowerProxyDevice<'a> { - async fn disconnect(&mut self) -> Result<(), power::policy::Error> { +impl<'a> Psu for PowerProxyDevice<'a> { + async fn disconnect(&mut self) -> Result<(), power_policy_interface::psu::Error> { self.execute(PolicyCommandData::Disconnect).await?.complete_or_err() } async fn connect_provider( &mut self, - capability: power::policy::ProviderPowerCapability, - ) -> Result<(), power::policy::Error> { + capability: power_policy_interface::capability::ProviderPowerCapability, + ) -> Result<(), power_policy_interface::psu::Error> { self.execute(PolicyCommandData::ConnectAsProvider(capability)) .await? .complete_or_err() @@ -90,8 +87,8 @@ impl<'a> DeviceTrait for PowerProxyDevice<'a> { async fn connect_consumer( &mut self, - capability: power::policy::ConsumerPowerCapability, - ) -> Result<(), power::policy::Error> { + capability: power_policy_interface::capability::ConsumerPowerCapability, + ) -> Result<(), power_policy_interface::psu::Error> { self.execute(PolicyCommandData::ConnectAsConsumer(capability)) .await? .complete_or_err() diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 9f8a6b4fa..1ca951b50 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -1,26 +1,22 @@ use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_services::{ - event, - power::policy::policy, - sync::Lockable, - trace, - type_c::{ - controller::Controller, - event::{PortPending, VdmNotification}, - }, -}; +use embedded_services::{event, sync::Lockable, trace}; use embedded_usb_pd::{Error, LocalPortId, PdError}; use crate::wrapper::{DynPortState, message::vdm::OutputKind}; +use crate::type_c::{ + controller::Controller, + event::{PortPending, VdmNotification}, +}; + use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, + R: event::Receiver, V: FwOfferValidator, > ControllerWrapper<'device, M, D, S, R, V> where From 6163f677e447e15d0db1307e39c76319a7be4c30 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Tue, 10 Mar 2026 09:13:19 -0700 Subject: [PATCH 26/79] Power policy remove intrusive list (#731) * Move PSU code away from `intrusive_list`. * Remove numeric PSU ids. * Remove `'static` lifetime constraints in service code and update integration tests accordingly. * Remove interior mutability from service. --- Cargo.lock | 13 +- Cargo.toml | 1 + battery-service/Cargo.toml | 4 +- battery-service/src/context.rs | 7 +- battery-service/src/lib.rs | 7 +- examples/pico-de-gallo/Cargo.lock | 408 ++++++++++-- examples/rt633/Cargo.lock | 610 ++++++++++++++---- examples/rt685s-evk/.cargo/config.toml | 14 +- examples/rt685s-evk/Cargo.lock | 1 - examples/rt685s-evk/src/bin/type_c.rs | 131 ++-- examples/rt685s-evk/src/bin/type_c_cfu.rs | 125 ++-- examples/std/Cargo.lock | 1 - examples/std/Cargo.toml | 4 - examples/std/src/bin/power_policy.rs | 173 +++-- examples/std/src/bin/type_c/external.rs | 206 ------ examples/std/src/bin/type_c/service.rs | 111 ++-- examples/std/src/bin/type_c/ucsi.rs | 158 ++--- examples/std/src/bin/type_c/unconstrained.rs | 205 +++--- .../std/src/lib/type_c/mock_controller.rs | 3 +- power-policy-interface/src/psu/event.rs | 26 +- power-policy-interface/src/psu/mod.rs | 239 ++----- power-policy-interface/src/service/event.rs | 42 +- power-policy-service/Cargo.toml | 3 + power-policy-service/src/lib.rs | 1 + power-policy-service/src/psu.rs | 40 ++ power-policy-service/src/service/consumer.rs | 188 +++--- power-policy-service/src/service/context.rs | 124 +--- power-policy-service/src/service/mod.rs | 240 +++---- power-policy-service/src/service/provider.rs | 84 +-- power-policy-service/src/service/task.rs | 41 +- power-policy-service/tests/common/mock.rs | 40 +- power-policy-service/tests/common/mod.rs | 159 +++-- power-policy-service/tests/consumer.rs | 35 +- supply-chain/audits.toml | 15 + type-c-service/Cargo.toml | 3 - type-c-service/src/service/controller.rs | 5 +- type-c-service/src/service/mod.rs | 41 +- type-c-service/src/service/pd.rs | 8 +- type-c-service/src/service/port.rs | 5 +- type-c-service/src/service/power.rs | 13 +- type-c-service/src/service/ucsi.rs | 5 +- type-c-service/src/service/vdm.rs | 8 +- type-c-service/src/task.rs | 67 +- type-c-service/src/wrapper/backing.rs | 274 +++----- type-c-service/src/wrapper/cfu.rs | 61 +- type-c-service/src/wrapper/dp.rs | 5 +- type-c-service/src/wrapper/mod.rs | 216 +++---- type-c-service/src/wrapper/pd.rs | 139 ++-- type-c-service/src/wrapper/power.rs | 26 +- type-c-service/src/wrapper/proxy.rs | 35 +- type-c-service/src/wrapper/vdm.rs | 26 +- 51 files changed, 2265 insertions(+), 2131 deletions(-) delete mode 100644 examples/std/src/bin/type_c/external.rs create mode 100644 power-policy-service/src/psu.rs diff --git a/Cargo.lock b/Cargo.lock index 1b77a98e0..341488c59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1343,9 +1343,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" dependencies = [ "jiff-static", "log", @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" dependencies = [ "proc-macro2", "quote", @@ -1822,9 +1822,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -2529,7 +2529,6 @@ dependencies = [ "heapless", "log", "power-policy-interface", - "power-policy-service", "static_cell", "tokio", "tps6699x", diff --git a/Cargo.toml b/Cargo.toml index eadaac9e8..410ee4a6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ num_enum = { version = "0.7.5", default-features = false } portable-atomic = { version = "1.11", default-features = false } power-policy-interface = { path = "./power-policy-interface" } paste = "1.0.15" +power-policy-service = { path = "./power-policy-service" } fixed = "1.23.1" heapless = "0.8.*" log = "0.4" diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index f18b61102..6d867a81e 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -32,17 +32,17 @@ defmt = [ "dep:defmt", "battery-service-messages/defmt", "embedded-services/defmt", - "power-policy-interface/defmt", "embassy-time/defmt", "embassy-sync/defmt", "embedded-batteries-async/defmt", "mctp-rs/defmt", + "power-policy-interface/defmt", ] log = [ "dep:log", "embedded-services/log", - "power-policy-interface/log", "embassy-time/log", "embassy-sync/log", + "power-policy-interface/log", ] mock = [] diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index 0b8f77169..aaaed99b2 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -7,7 +7,6 @@ use embassy_sync::channel::TrySendError; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; -use embedded_services::comms::MailboxDelegateError; use embedded_services::{IntrusiveList, debug, error, info, intrusive_list, trace, warn}; use power_policy_interface::capability::PowerCapability; @@ -517,7 +516,9 @@ impl Context { *self.power_info.lock().await } - pub(crate) fn set_power_info( + // TODO: bring this back after moving away from comms for power policy + // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 + /*pub(crate) fn set_power_info( &self, power_info: &power_policy_interface::service::event::CommsData, ) -> Result<(), MailboxDelegateError> { @@ -546,7 +547,7 @@ impl Context { trace!("Battery: PSU state: {:?}", psu_state); Ok(()) - } + }*/ } impl Default for Context { diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index dd3cf76ae..8a7391692 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -126,12 +126,15 @@ impl comms::MailboxDelegate for Service { self.context.send_event_no_wait(*event).map_err(|e| match e { embassy_sync::channel::TrySendError::Full(_) => comms::MailboxDelegateError::BufferFull, })? - } else if let Some(power_policy_msg) = message + } + // TODO: Migrate away from using comms for power policy updates + // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 + /*else if let Some(power_policy_msg) = message .data .get::() { self.context.set_power_info(&power_policy_msg.data)?; - } + }*/ Ok(()) } diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index f8e429c6b..87248daf5 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -61,6 +73,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "aquamarine" version = "0.6.0" @@ -68,13 +86,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.5", "proc-macro-error2", "proc-macro2", "quote", "syn", ] +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "winnow 0.7.14", +] + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -195,9 +256,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -213,9 +274,9 @@ dependencies = [ [[package]] name = "bq40z50-rx" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b6faf600295f12c3fb99b45266bc9140af5c344b08f2705bc06bfa0e8b549e" +checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" dependencies = [ "device-driver", "embassy-time", @@ -233,15 +294,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "shlex", @@ -268,6 +329,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cordyceps" version = "0.3.4" @@ -332,16 +402,55 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "dd-manifest-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" +dependencies = [ + "yaml-rust2", +] + [[package]] name = "device-driver" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ + "device-driver-macros", "embedded-io 0.6.1", "embedded-io-async", ] +[[package]] +name = "device-driver-generation" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3935aec9cf5bb2ab927f59ca69faecf976190390b0ce34c6023889e9041040c0" +dependencies = [ + "anyhow", + "askama", + "bitvec", + "convert_case", + "dd-manifest-tree", + "itertools 0.14.0", + "kdl", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "device-driver-macros" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fdc68ed515c4eddff2e95371185b4becba066085bf36d50f07f09782af98e17" +dependencies = [ + "device-driver-generation", + "proc-macro2", + "syn", +] + [[package]] name = "document-features" version = "0.2.12" @@ -552,11 +661,20 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" -version = "1.0.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -564,9 +682,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", @@ -600,9 +718,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "funty" @@ -612,15 +730,15 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "generator" @@ -655,6 +773,24 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown", +] + [[package]] name = "heapless" version = "0.7.17" @@ -729,11 +865,26 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "jiff" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e3d65f018c6ae946ab16e80944b97096ed73c35b221d1c478a6c81d8f57940" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -744,15 +895,26 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17c2b211d863c7fde02cbea8a3c1a439b98e109286554f2860bdded7ff83818" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "kdl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a29e7b50079ff44549f68c0becb1c73d7f6de2a4ea952da77966daf3d4761e" +dependencies = [ + "miette", + "num", + "winnow 0.6.24", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -761,9 +923,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" @@ -850,9 +1012,19 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miette" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "unicode-width", +] [[package]] name = "mycelium-bitfield" @@ -884,6 +1056,70 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -951,6 +1187,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pico-de-gallo" version = "0.1.0" @@ -1035,15 +1277,15 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] @@ -1163,9 +1405,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "regex" -version = "1.12.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1175,9 +1417,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1186,9 +1428,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -1290,6 +1538,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1307,9 +1568,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1380,9 +1641,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1520,9 +1781,21 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unty" @@ -1554,6 +1827,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "void" version = "1.0.2" @@ -1732,6 +2011,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.5.1" @@ -1741,22 +2038,39 @@ dependencies = [ "tap", ] +[[package]] +name = "yaml-rust2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 517cec0af..cfdc94d9b 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "aquamarine" version = "0.6.0" @@ -13,7 +31,50 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "syn 2.0.104", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "winnow 0.7.12", ] [[package]] @@ -24,9 +85,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "az" -version = "1.3.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" [[package]] name = "bare-metal" @@ -46,7 +107,7 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", @@ -61,7 +122,7 @@ name = "battery-service-messages" version = "0.1.0" dependencies = [ "defmt 0.3.100", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-services", "num_enum", ] @@ -103,22 +164,22 @@ checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" [[package]] name = "bitfield" -version = "0.19.4" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" +checksum = "db1bcd90f88eabbf0cadbfb87a45bceeaebcd3b4bc9e43da379cd2ef0162590d" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.19.4" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" +checksum = "3787a07661997bfc05dd3431e379c0188573f78857080cf682e1393ab8e4d64c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -129,7 +190,7 @@ checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -140,7 +201,7 @@ checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -151,9 +212,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -169,23 +230,23 @@ dependencies = [ [[package]] name = "bq25773" -version = "0.1.1" -source = "git+https://github.com/OpenDevicePartnership/bq25773#0ca102dec24a617df5762cf0bf4217fd387d5c63" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/bq25773#20bc26219b5372bc6146cdc509c21f6d43e257b3" dependencies = [ "device-driver", - "embedded-batteries-async", + "embedded-batteries-async 0.2.1", "embedded-hal 1.0.0", "embedded-hal-async", ] [[package]] name = "bq40z50-rx" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b6faf600295f12c3fb99b45266bc9140af5c344b08f2705bc06bfa0e8b549e" +checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" dependencies = [ "device-driver", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-hal 1.0.0", "embedded-hal-async", "smbus-pec", @@ -193,9 +254,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -205,9 +266,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "convert_case" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "cortex-m" @@ -239,7 +309,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -275,7 +345,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.104", ] [[package]] @@ -286,7 +356,16 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "dd-manifest-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" +dependencies = [ + "yaml-rust2", ] [[package]] @@ -327,7 +406,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -336,7 +415,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror", + "thiserror 2.0.16", ] [[package]] @@ -351,19 +430,49 @@ dependencies = [ [[package]] name = "device-driver" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" +checksum = "1298272ea07037196af2fe8d1eb50792206f45476d79eefa435432b9323cf488" dependencies = [ + "device-driver-macros", "embedded-io", "embedded-io-async", ] +[[package]] +name = "device-driver-generation" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86a17ed060a6119daeb05ad5596ac3bd945f7ab2213cc6260bf6a7623e73da1" +dependencies = [ + "anyhow", + "askama", + "bitvec", + "convert_case", + "dd-manifest-tree", + "itertools 0.14.0", + "kdl", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "device-driver-macros" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c29238099c66bf44098efaa772cae6f47d632aebb7ade8d3087bd565e8fae0" +dependencies = [ + "device-driver-generation", + "proc-macro2", + "syn 2.0.104", +] + [[package]] name = "document-features" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] @@ -415,7 +524,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -537,7 +646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" dependencies = [ "bitfield-struct 0.10.1", - "bitflags 2.11.0", + "bitflags 2.9.1", "defmt 0.3.100", "embedded-hal 1.0.0", "zerocopy", @@ -545,17 +654,28 @@ dependencies = [ [[package]] name = "embedded-batteries" -version = "0.3.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" +checksum = "3b8996d7168535579180a0eead82efaba718ebd598782f986bfd635458259df2" dependencies = [ - "bitfield-struct 0.12.1", - "bitflags 2.11.0", + "bitfield-struct 0.10.1", + "bitflags 2.9.1", "defmt 0.3.100", "embedded-hal 1.0.0", "zerocopy", ] +[[package]] +name = "embedded-batteries-async" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" +dependencies = [ + "bitfield-struct 0.10.1", + "embedded-batteries 0.2.1", + "embedded-hal 1.0.0", +] + [[package]] name = "embedded-batteries-async" version = "0.3.4" @@ -564,14 +684,14 @@ checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" dependencies = [ "bitfield-struct 0.12.1", "defmt 0.3.100", - "embedded-batteries 0.3.4", + "embedded-batteries 0.3.1", "embedded-hal 1.0.0", ] [[package]] name = "embedded-cfu-protocol" version = "0.2.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" +source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" dependencies = [ "defmt 0.3.100", "embedded-io-async", @@ -647,7 +767,7 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitflags 2.11.0", + "bitflags 2.9.1", "bitvec", "cfg-if", "cortex-m", @@ -694,18 +814,27 @@ source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce dependencies = [ "aquamarine", "bincode", - "bitfield 0.19.4", + "bitfield 0.19.1", "defmt 0.3.100", "embedded-hal-async", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#9805f13c044b0e314d415410c57a8a59a40eabeb" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" dependencies = [ "bit-register", - "bitflags 2.11.0", + "bitflags 2.9.1", "num-traits", "num_enum", "static_assertions", @@ -735,9 +864,9 @@ dependencies = [ [[package]] name = "fixed" -version = "1.30.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c566da967934c6c7ee0458a9773de9b2a685bd2ce26a3b28ddfc740e640182f5" +checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" dependencies = [ "az", "bytemuck", @@ -759,9 +888,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -773,9 +902,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -783,61 +912,61 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", "futures-sink", "futures-task", "pin-project-lite", + "pin-utils", ] [[package]] name = "half" -version = "2.7.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", - "zerocopy", ] [[package]] @@ -849,6 +978,24 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown", +] + [[package]] name = "heapless" version = "0.8.0" @@ -861,9 +1008,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.5.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "ident_case" @@ -908,11 +1055,38 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "kdl" +version = "6.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661358400b02cbbf1fbd05f0a483335490e8a6bd1867620f2eeb78f304a22f" +dependencies = [ + "miette", + "num", + "thiserror 1.0.69", + "winnow 0.6.24", +] + [[package]] name = "litrs" -version = "1.0.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "mctp-rs" @@ -925,7 +1099,35 @@ dependencies = [ "espi-device", "num_enum", "smbus-pec", - "thiserror", + "thiserror 2.0.16", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -978,6 +1180,70 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1005,9 +1271,15 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "panic-probe" version = "0.3.2" @@ -1024,17 +1296,29 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "power-policy-interface" @@ -1044,7 +1328,7 @@ dependencies = [ "defmt 0.3.100", "embassy-futures", "embassy-sync", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-services", "heapless", "num_enum", @@ -1069,23 +1353,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1134,7 +1418,7 @@ dependencies = [ "embassy-imxrt", "embassy-sync", "embassy-time", - "embedded-batteries-async", + "embedded-batteries-async 0.3.4", "embedded-hal-async", "embedded-services", "espi-service", @@ -1146,6 +1430,12 @@ dependencies = [ "static_cell", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.2.3" @@ -1161,6 +1451,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "semver" version = "0.9.0" @@ -1178,32 +1474,34 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ - "serde_core", "serde_derive", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "serde_derive" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ - "serde_derive", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "serde_json" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ - "proc-macro2", - "quote", - "syn", + "itoa", + "memchr", + "ryu", + "serde", ] [[package]] @@ -1217,9 +1515,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" @@ -1249,21 +1547,32 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subenum" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3d08fe7078c57309d5c3d938e50eba95ba1d33b9c3a101a8465fc6861a5416" +checksum = "4f5d5dfb8556dd04017db5e318bbeac8ab2b0c67b76bf197bfb79e9b29f18ecf" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "syn" -version = "2.0.117" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1288,22 +1597,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -1320,15 +1649,27 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unty" @@ -1348,6 +1689,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "void" version = "1.0.2" @@ -1363,6 +1710,24 @@ dependencies = [ "vcell", ] +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.5.1" @@ -1372,22 +1737,33 @@ dependencies = [ "tap", ] +[[package]] +name = "yaml-rust2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] diff --git a/examples/rt685s-evk/.cargo/config.toml b/examples/rt685s-evk/.cargo/config.toml index db42be81d..ef1631db6 100644 --- a/examples/rt685s-evk/.cargo/config.toml +++ b/examples/rt685s-evk/.cargo/config.toml @@ -2,16 +2,20 @@ runner = 'probe-rs run --chip MIMXRT685SFVKB' rustflags = [ - "-C", "linker=flip-link", - "-C", "link-arg=-Tlink.x", - "-C", "link-arg=-Tdefmt.x", + "-C", + "linker=flip-link", + "-C", + "link-arg=-Tlink.x", + "-C", + "link-arg=-Tdefmt.x", # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 - "-C", "link-arg=--nmagic", + "-C", + "link-arg=--nmagic", ] [build] target = "thumbv8m.main-none-eabihf" # Cortex-M33 [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "info" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 819c5ac34..b01a71d86 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1683,7 +1683,6 @@ dependencies = [ "embedded-usb-pd", "heapless", "power-policy-interface", - "power-policy-service", "tps6699x", ] diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index e73a71f75..5232dcf07 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -13,13 +13,13 @@ use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; -use embassy_time::{self as _, Delay, Timer}; +use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; -use power_policy_interface::psu::DeviceId as PowerId; -use power_policy_service::service::Service as PowerPolicyService; +use power_policy_interface::psu; +use power_policy_service::psu::EventReceivers; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; @@ -35,8 +35,8 @@ const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const PORT0_PWR_ID: PowerId = PowerId(0); -const PORT1_PWR_ID: PowerId = PowerId(1); + +type DeviceType = Mutex>; bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; @@ -58,8 +58,7 @@ type Wrapper<'a> = ControllerWrapper< 'a, GlobalRawMutex, Tps6699xMutex<'a>, - DynamicSender<'a, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'a, power_policy_interface::psu::event::RequestData>, + DynamicSender<'a, power_policy_interface::psu::event::EventData>, Validator, >; type Controller<'a> = tps6699x::controller::Controller>; @@ -80,31 +79,21 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st } #[embassy_executor::task] -async fn power_policy_service_task( - service: &'static PowerPolicyService< - 'static, - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, +async fn power_policy_task( + psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static Mutex>, ) { - Timer::after_millis(100).await; // Give some time for other tasks to start - power_policy_service::service::task::task(service) - .await - .expect("Failed to start power policy service task"); + power_policy_service::service::task::task(psu_events, power_policy).await; } #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static>, + service: &'static Service<'static, DeviceType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; + type_c_service::task::task(service, wrappers, cfu_client).await; } #[embassy_executor::main] @@ -147,26 +136,6 @@ async fn main(spawner: Spawner) { .await .unwrap(); - // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell< - power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - - static POWER_SERVICE: StaticCell< - power_policy_service::service::Service< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( - power_service_context, - power_policy_service::service::config::Config::default(), - )); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); @@ -181,39 +150,31 @@ async fn main(spawner: Spawner) { [PORT0_ID, PORT1_ID], )); - static INTERMEDIATE: StaticCell> = StaticCell::new(); - let intermediate = INTERMEDIATE.init( - storage - .try_create_intermediate() - .expect("Failed to create intermediate storage"), - ); - - static POLICY_CHANNEL0: StaticCell> = - StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); - static POLICY_CHANNEL1: StaticCell> = - StaticCell::new(); + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); + static INTERMEDIATE: StaticCell< + IntermediateStorage>, + > = StaticCell::new(); + let intermediate = INTERMEDIATE.init( + storage + .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) + .expect("Failed to create intermediate storage"), + ); + static REFERENCED: StaticCell< - ReferencedStorage< - TPS66994_NUM_PORTS, - GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, - >, + ReferencedStorage>, > = StaticCell::new(); let referenced = REFERENCED.init( intermediate - .try_create_referenced([ - (PORT0_PWR_ID, policy_sender0, policy_receiver0), - (PORT1_PWR_ID, policy_sender1, policy_receiver1), - ]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -222,12 +183,16 @@ async fn main(spawner: Spawner) { let controller_mutex = CONTROLLER_MUTEX.init(Mutex::new(tps6699x_drv::tps66994(tps6699x, Default::default()))); static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = - WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); + let wrapper = WRAPPER.init(ControllerWrapper::new( + controller_mutex, + Default::default(), + referenced, + Validator, + )); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, 4, 1, 0>, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -235,7 +200,22 @@ async fn main(spawner: Spawner) { // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); + + static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); + let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + + static POWER_SERVICE: StaticCell>> = + StaticCell::new(); + let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( + psu_registration, + power_service_context, + power_policy_service::service::config::Config::default(), + ))); + + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, @@ -249,15 +229,16 @@ async fn main(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task( - type_c_service, - [wrapper], - power_service_context, - cfu_client, - )); + spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); info!("Spawining power policy task"); - spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(power_policy_task( + EventReceivers::new( + [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + [policy_receiver0, policy_receiver1], + ), + power_service, + )); spawner.must_spawn(pd_controller_task(wrapper)); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index f0c5a9e6b..f8ab5c200 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -21,8 +21,8 @@ use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferRe use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; -use power_policy_interface::psu::DeviceId as PowerId; -use power_policy_service::service::Service as PowerPolicyService; +use power_policy_interface::psu; +use power_policy_service::psu::EventReceivers; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; @@ -47,6 +47,8 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { } } +type DeviceType = Mutex>; + type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; @@ -54,8 +56,7 @@ type Wrapper<'a> = ControllerWrapper< 'a, GlobalRawMutex, Tps6699xMutex<'a>, - DynamicSender<'a, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'a, power_policy_interface::psu::event::RequestData>, + DynamicSender<'a, power_policy_interface::psu::event::EventData>, Validator, >; type Controller<'a> = tps6699x::controller::Controller>; @@ -66,8 +67,6 @@ const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const PORT0_PWR_ID: PowerId = PowerId(0); -const PORT1_PWR_ID: PowerId = PowerId(1); #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { @@ -164,31 +163,21 @@ async fn fw_update_task() { } #[embassy_executor::task] -async fn power_policy_service_task( - service: &'static PowerPolicyService< - 'static, - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, +async fn power_policy_task( + psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static Mutex>, ) { - Timer::after_millis(100).await; // Give some time for other tasks to start - power_policy_service::service::task::task(service) - .await - .expect("Failed to start power policy service task"); + power_policy_service::service::task::task(psu_events, power_policy).await; } #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static>, + service: &'static Service<'static, DeviceType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; + type_c_service::task::task(service, wrappers, cfu_client).await; } #[embassy_executor::main] @@ -231,26 +220,6 @@ async fn main(spawner: Spawner) { .await .unwrap(); - // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell< - power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - - static POWER_SERVICE: StaticCell< - power_policy_service::service::Service< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( - power_service_context, - power_policy_service::service::config::Config::default(), - )); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); @@ -265,39 +234,35 @@ async fn main(spawner: Spawner) { [PORT0_ID, PORT1_ID], )); - static INTERMEDIATE: StaticCell> = StaticCell::new(); - let intermediate = INTERMEDIATE.init( - storage - .try_create_intermediate() - .expect("Failed to create intermediate storage"), - ); - - static POLICY_CHANNEL0: StaticCell> = - StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); - static POLICY_CHANNEL1: StaticCell> = - StaticCell::new(); + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); + static INTERMEDIATE: StaticCell< + IntermediateStorage>, + > = StaticCell::new(); + let intermediate = INTERMEDIATE.init( + storage + .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) + .expect("Failed to create intermediate storage"), + ); + static REFERENCED: StaticCell< ReferencedStorage< TPS66994_NUM_PORTS, GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, + DynamicSender<'_, power_policy_interface::psu::event::EventData>, >, > = StaticCell::new(); let referenced = REFERENCED.init( intermediate - .try_create_referenced([ - (PORT0_PWR_ID, policy_sender0, policy_receiver0), - (PORT1_PWR_ID, policy_sender1, policy_receiver1), - ]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -306,12 +271,31 @@ async fn main(spawner: Spawner) { let controller_mutex = CONTROLLER_MUTEX.init(Mutex::new(tps6699x_drv::tps66994(tps6699x, Default::default()))); static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = - WRAPPER.init(ControllerWrapper::try_new(controller_mutex, Default::default(), referenced, Validator).unwrap()); + let wrapper = WRAPPER.init(ControllerWrapper::new( + controller_mutex, + Default::default(), + referenced, + Validator, + )); + + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); + + static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); + let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + + static POWER_SERVICE: StaticCell>> = + StaticCell::new(); + let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( + psu_registration, + power_service_context, + power_policy_service::service::config::Config::default(), + ))); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, 4, 1, 0>, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -319,7 +303,7 @@ async fn main(spawner: Spawner) { // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, @@ -333,15 +317,16 @@ async fn main(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task( - type_c_service, - [wrapper], - power_service_context, - cfu_client, - )); + spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); info!("Spawining power policy task"); - spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(power_policy_task( + EventReceivers::new( + [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + [policy_receiver0, policy_receiver1], + ), + power_service, + )); spawner.must_spawn(pd_controller_task(wrapper)); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 92cb2bfec..aa554b464 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1898,7 +1898,6 @@ dependencies = [ "heapless", "log", "power-policy-interface", - "power-policy-service", "tps6699x", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 8c661ca95..b63e125f9 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -72,10 +72,6 @@ path = "src/bin/type_c/basic.rs" name = "type-c-service" path = "src/bin/type_c/service.rs" -[[bin]] -name = "type-c-external" -path = "src/bin/type_c/external.rs" - [[bin]] name = "type-c-unconstrained" path = "src/bin/type_c/unconstrained.rs" diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index f6150926a..40e8a292f 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -3,15 +3,16 @@ use embassy_sync::{ blocking_mutex::raw::NoopRawMutex, channel::{self, Channel}, mutex::Mutex, - pubsub::PubSubChannel, }; use embassy_time::{self as _, Timer}; -use embedded_services::{GlobalRawMutex, broadcaster::immediate as broadcaster}; +use embedded_services::GlobalRawMutex; use log::*; -use power_policy_interface::capability::{ - ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability, -}; use power_policy_interface::psu::{Error, Psu}; +use power_policy_interface::{ + capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + psu, +}; +use power_policy_service::psu::EventReceivers; use static_cell::StaticCell; const LOW_POWER: PowerCapability = PowerCapability { @@ -24,35 +25,41 @@ const HIGH_POWER: PowerCapability = PowerCapability { current_ma: 3000, }; -const DEVICE0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); -const DEVICE1_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(1); - const PER_CALL_DELAY_MS: u64 = 1000; struct ExampleDevice<'a> { - sender: channel::DynamicSender<'a, power_policy_interface::psu::event::RequestData>, + sender: channel::DynamicSender<'a, power_policy_interface::psu::event::EventData>, + state: psu::State, + name: &'static str, } impl<'a> ExampleDevice<'a> { - fn new(sender: channel::DynamicSender<'a, power_policy_interface::psu::event::RequestData>) -> Self { - Self { sender } + fn new( + name: &'static str, + sender: channel::DynamicSender<'a, power_policy_interface::psu::event::EventData>, + ) -> Self { + Self { + name, + sender, + state: Default::default(), + } } pub async fn simulate_attach(&mut self) { self.sender - .send(power_policy_interface::psu::event::RequestData::Attached) + .send(power_policy_interface::psu::event::EventData::Attached) .await; } pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option) { self.sender - .send(power_policy_interface::psu::event::RequestData::UpdatedConsumerCapability(capability)) + .send(power_policy_interface::psu::event::EventData::UpdatedConsumerCapability(capability)) .await; } pub async fn simulate_detach(&mut self) { self.sender - .send(power_policy_interface::psu::event::RequestData::Detached) + .send(power_policy_interface::psu::event::EventData::Detached) .await; } @@ -61,7 +68,7 @@ impl<'a> ExampleDevice<'a> { capability: Option, ) { self.sender - .send(power_policy_interface::psu::event::RequestData::RequestedProviderCapability(capability)) + .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) .await } } @@ -81,75 +88,71 @@ impl Psu for ExampleDevice<'_> { debug!("ExampleDevice connect_consumer with {capability:?}"); Ok(()) } + + fn state(&self) -> &psu::State { + &self.state + } + + fn state_mut(&mut self) -> &mut psu::State { + &mut self.state + } + + fn name(&self) -> &'static str { + self.name + } } +type DeviceType = Mutex>; + #[embassy_executor::task] async fn run(spawner: Spawner) { embedded_services::init().await; info!("Creating device 0"); - static DEVICE0_EVENT_CHANNEL: StaticCell> = + static DEVICE0_EVENT_CHANNEL: StaticCell> = StaticCell::new(); let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); - static DEVICE0: StaticCell> = StaticCell::new(); - let device0 = DEVICE0.init(Mutex::new(ExampleDevice::new(device0_event_channel.dyn_sender()))); - static DEVICE0_REGISTRATION: StaticCell< - power_policy_interface::psu::RegistrationEntry< - 'static, - Mutex, - channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let device0_registration = DEVICE0_REGISTRATION.init(power_policy_interface::psu::RegistrationEntry::new( - DEVICE0_ID, - device0, - device0_event_channel.dyn_receiver(), - )); + static DEVICE0: StaticCell = StaticCell::new(); + let device0 = DEVICE0.init(Mutex::new(ExampleDevice::new( + "Device 0", + device0_event_channel.dyn_sender(), + ))); info!("Creating device 1"); - static DEVICE1_EVENT_CHANNEL: StaticCell> = + static DEVICE1_EVENT_CHANNEL: StaticCell> = StaticCell::new(); let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); - static DEVICE1: StaticCell> = StaticCell::new(); - let device1 = DEVICE1.init(Mutex::new(ExampleDevice::new(device1_event_channel.dyn_sender()))); - static DEVICE1_REGISTRATION: StaticCell< - power_policy_interface::psu::RegistrationEntry< - 'static, - Mutex, - channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let device1_registration = DEVICE1_REGISTRATION.init(power_policy_interface::psu::RegistrationEntry::new( - DEVICE1_ID, - device1, - device1_event_channel.dyn_receiver(), - )); + static DEVICE1: StaticCell = StaticCell::new(); + let device1 = DEVICE1.init(Mutex::new(ExampleDevice::new( + "Device 1", + device1_event_channel.dyn_sender(), + ))); - static SERVICE_CONTEXT: StaticCell< - power_policy_service::service::context::Context< - Mutex>, - channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); + static SERVICE_CONTEXT: StaticCell = StaticCell::new(); let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - service_context.register_psu(device0_registration).unwrap(); - service_context.register_psu(device1_registration).unwrap(); + static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); + let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); - static SERVICE: StaticCell< - power_policy_service::service::Service< - Mutex>, - channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let service = SERVICE.init(power_policy_service::service::Service::new( + static SERVICE: StaticCell>> = + StaticCell::new(); + let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( + psu_registration.as_slice(), service_context, power_policy_service::service::config::Config::default(), + ))); + + spawner.must_spawn(power_policy_task( + EventReceivers::new( + [device0, device1], + [ + device0_event_channel.dyn_receiver(), + device1_event_channel.dyn_receiver(), + ], + ), + service, )); - spawner.must_spawn(power_policy_task(service)); - spawner.must_spawn(receiver_task(service)); - // Plug in device 0, should become current consumer info!("Connecting device 0"); { @@ -270,49 +273,17 @@ async fn run(spawner: Spawner) { Timer::after_millis(PER_CALL_DELAY_MS).await; } -#[embassy_executor::task] -async fn receiver_task( - service: &'static power_policy_service::service::Service< - 'static, - Mutex>, - channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, -) { - static CHANNEL: StaticCell< - PubSubChannel, - > = StaticCell::new(); - let channel = CHANNEL.init(PubSubChannel::new()); - - let publisher = channel.dyn_immediate_publisher(); - let mut subscriber = channel.dyn_subscriber().unwrap(); - - static RECEIVER: StaticCell> = - StaticCell::new(); - let receiver = RECEIVER.init(broadcaster::Receiver::new(publisher)); - - service.context.register_message_receiver(receiver).unwrap(); - - loop { - match subscriber.next_message().await { - embassy_sync::pubsub::WaitResult::Message(msg) => { - info!("Received message: {msg:?}"); - } - embassy_sync::pubsub::WaitResult::Lagged(count) => { - warn!("Lagged messages: {count}"); - } - } - } -} - #[embassy_executor::task] async fn power_policy_task( - power_policy: &'static power_policy_service::service::Service< + psu_events: EventReceivers< 'static, - Mutex>, - channel::DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, + 2, + DeviceType, + channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, + power_policy: &'static Mutex>, ) { - power_policy_service::service::task::task(power_policy).await.unwrap(); + power_policy_service::service::task::task(psu_events, power_policy).await; } fn main() { diff --git a/examples/std/src/bin/type_c/external.rs b/examples/std/src/bin/type_c/external.rs deleted file mode 100644 index 4804731c8..000000000 --- a/examples/std/src/bin/type_c/external.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! Low-level example of external messaging with a simple type-C service -use embassy_executor::{Executor, Spawner}; -use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; -use embassy_sync::mutex::Mutex; -use embassy_sync::pubsub::PubSubChannel; -use embassy_time::Timer; -use embedded_services::{GlobalRawMutex, IntrusiveList}; -use embedded_usb_pd::GlobalPortId; -use log::*; -use static_cell::StaticCell; -use std_examples::type_c::mock_controller::{self, Wrapper}; -use type_c_service::service::{Service, config::Config}; -use type_c_service::type_c::{Cached, ControllerId, controller, controller::Context}; -use type_c_service::wrapper::backing::Storage; - -const NUM_PD_CONTROLLERS: usize = 1; -const CONTROLLER0_ID: ControllerId = ControllerId(0); -const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); - -#[embassy_executor::task] -async fn controller_task(wrapper: &'static Wrapper<'static>) { - loop { - if let Err(e) = wrapper.process_next_event().await { - error!("Error processing wrapper: {e:#?}"); - } - } -} - -#[embassy_executor::task] -async fn task(_spawner: Spawner, controller_context: &'static Context) { - info!("Starting main task"); - embedded_services::init().await; - - // Allow the controller to initialize and register itself - Timer::after_secs(1).await; - info!("Getting controller status"); - let controller_status = controller_context - .get_controller_status_external(ControllerId(0)) - .await - .unwrap(); - info!("Controller status: {controller_status:?}"); - - info!("Getting port status"); - let port_status = controller_context - .get_port_status_external(GlobalPortId(0), Cached(true)) - .await - .unwrap(); - info!("Port status: {port_status:?}"); - - info!("Getting retimer fw update status"); - let rt_fw_update_status = controller_context - .port_get_rt_fw_update_status_external(GlobalPortId(0)) - .await - .unwrap(); - info!("Get retimer fw update status: {rt_fw_update_status:?}"); - - info!("Setting retimer fw update state"); - controller_context - .port_set_rt_fw_update_state_external(GlobalPortId(0)) - .await - .unwrap(); - - info!("Clearing retimer fw update state"); - controller_context - .port_clear_rt_fw_update_state_external(GlobalPortId(0)) - .await - .unwrap(); - - info!("Setting retimer compliance"); - controller_context - .port_set_rt_compliance_external(GlobalPortId(0)) - .await - .unwrap(); - - info!("Setting max sink voltage"); - controller_context - .set_max_sink_voltage_external(GlobalPortId(0), Some(5000)) - .await - .unwrap(); - - info!("Clearing dead battery flag"); - controller_context - .clear_dead_battery_flag_external(GlobalPortId(0)) - .await - .unwrap(); - - info!("Reconfiguring retimer"); - controller_context - .reconfigure_retimer_external(GlobalPortId(0)) - .await - .unwrap(); -} - -#[embassy_executor::task] -async fn service_task( - controller_context: &'static Context, - controllers: &'static IntrusiveList, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], -) { - info!("Starting type-c task"); - - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - for controller_wrapper in wrappers { - controller::register_controller(controllers, controller_wrapper.registration.pd_controller).unwrap(); - } - - static SERVICE: StaticCell = StaticCell::new(); - let service = SERVICE.init(Service::create( - Config::default(), - controller_context, - controllers, - power_policy_publisher, - power_policy_subscriber, - )); - - loop { - if let Err(e) = service.process_next_event().await { - error!("Type-C service processing error: {e:#?}"); - } - } -} - -fn create_wrapper(controller_context: &'static Context) -> &'static mut Wrapper<'static> { - static STATE: StaticCell = StaticCell::new(); - let state = STATE.init(mock_controller::ControllerState::new()); - - static STORAGE: StaticCell> = StaticCell::new(); - let backing_storage = STORAGE.init(Storage::new( - controller_context, - CONTROLLER0_ID, - 0, // CFU component ID (unused) - [PORT0_ID], - )); - - static INTERMEDIATE: StaticCell> = - StaticCell::new(); - let intermediate = INTERMEDIATE.init( - backing_storage - .try_create_intermediate() - .expect("Failed to create intermediate storage"), - ); - - static POLICY_CHANNEL: StaticCell> = - StaticCell::new(); - let policy_channel = POLICY_CHANNEL.init(Channel::new()); - - let policy_sender = policy_channel.dyn_sender(); - let policy_receiver = policy_channel.dyn_receiver(); - - static REFERENCED: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let referenced = REFERENCED.init( - intermediate - .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) - .expect("Failed to create referenced storage"), - ); - - static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - - static WRAPPER: StaticCell = StaticCell::new(); - WRAPPER.init( - mock_controller::Wrapper::try_new( - controller, - Default::default(), - referenced, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ) -} - -fn main() { - env_logger::builder().filter_level(log::LevelFilter::Trace).init(); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - let wrapper = create_wrapper(context); - - static EXECUTOR: StaticCell = StaticCell::new(); - let executor = EXECUTOR.init(Executor::new()); - executor.run(|spawner| { - spawner.must_spawn(service_task(context, controller_list, [wrapper])); - spawner.must_spawn(task(spawner, context)); - spawner.must_spawn(controller_task(wrapper)); - }); -} diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 189f6ad67..a03d7c29e 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -10,7 +10,8 @@ use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; -use power_policy_service::service::Service as PowerPolicyService; +use power_policy_interface::psu; +use power_policy_service::psu::EventReceivers; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; @@ -25,9 +26,10 @@ use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); const DELAY_MS: u64 = 1000; +type DeviceType = Mutex>; + #[embassy_executor::task] async fn controller_task( wrapper: &'static Wrapper<'static>, @@ -62,34 +64,29 @@ async fn task(spawner: Spawner) { embedded_services::init().await; // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell< - power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); + static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); static CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); - let (wrapper, controller, state) = create_wrapper(controller_context); + let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); - static POWER_SERVICE: StaticCell< - power_policy_service::service::Service< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( + static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 1]> = StaticCell::new(); + let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy]); + + static POWER_SERVICE: StaticCell>> = + StaticCell::new(); + let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( + psu_registration, power_service_context, power_policy_service::service::config::Config::default(), - )); + ))); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, 4, 1, 0>, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -100,7 +97,7 @@ async fn task(spawner: Spawner) { static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Config::default(), controller_context, @@ -113,13 +110,11 @@ async fn task(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(power_policy_service_task(power_service)); - spawner.must_spawn(type_c_service_task( - type_c_service, - [wrapper], - power_service_context, - cfu_client, + spawner.must_spawn(power_policy_task( + EventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), + power_service, )); + spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); spawner.must_spawn(controller_task(wrapper, controller)); Timer::after_millis(1000).await; @@ -147,36 +142,28 @@ async fn task(spawner: Spawner) { } #[embassy_executor::task] -async fn power_policy_service_task( - service: &'static PowerPolicyService< - 'static, - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, +async fn power_policy_task( + psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static Mutex>, ) { - power_policy_service::service::task::task(service) - .await - .expect("Failed to start power policy service task"); + power_policy_service::service::task::task(psu_events, power_policy).await; } #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static>, + service: &'static Service<'static, DeviceType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; + type_c_service::task::task(service, wrappers, cfu_client).await; } fn create_wrapper( context: &'static Context, ) -> ( &'static Wrapper<'static>, + DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, &'static Mutex>, &'static mock_controller::ControllerState, ) { @@ -191,32 +178,36 @@ fn create_wrapper( [PORT0_ID], )); - static INTERMEDIATE: StaticCell> = - StaticCell::new(); - let intermediate = INTERMEDIATE.init( - storage - .try_create_intermediate() - .expect("Failed to create intermediate storage"), - ); - - static POLICY_CHANNEL: StaticCell> = + static POLICY_CHANNEL: StaticCell> = StaticCell::new(); let policy_channel = POLICY_CHANNEL.init(Channel::new()); let policy_sender = policy_channel.dyn_sender(); let policy_receiver = policy_channel.dyn_receiver(); + static INTERMEDIATE: StaticCell< + type_c_service::wrapper::backing::IntermediateStorage< + 1, + GlobalRawMutex, + DynamicSender<'static, psu::event::EventData>, + >, + > = StaticCell::new(); + let intermediate = INTERMEDIATE.init( + storage + .try_create_intermediate([("Pd0", policy_sender)]) + .expect("Failed to create intermediate storage"), + ); + static REFERENCED: StaticCell< type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, + DynamicSender<'_, psu::event::EventData>, >, > = StaticCell::new(); let referenced = REFERENCED.init( intermediate - .try_create_referenced([(POWER0_ID, policy_sender, policy_receiver)]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -225,15 +216,13 @@ fn create_wrapper( static WRAPPER: StaticCell = StaticCell::new(); ( - WRAPPER.init( - mock_controller::Wrapper::try_new( - controller, - Default::default(), - referenced, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ), + WRAPPER.init(mock_controller::Wrapper::new( + controller, + Default::default(), + referenced, + crate::mock_controller::Validator, + )), + policy_receiver, controller, state, ) diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 7bb5c144c..24d0cb9c6 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -15,7 +15,8 @@ use embedded_usb_pd::ucsi::ppm::set_notification_enable::NotificationEnable; use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; use power_policy_interface::capability::PowerCapability; -use power_policy_service::service::Service as PowerPolicyService; +use power_policy_interface::psu; +use power_policy_service::psu::EventReceivers; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -30,12 +31,12 @@ const NUM_PD_CONTROLLERS: usize = 2; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER1_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(1); const CFU0_ID: u8 = 0x00; const CFU1_ID: u8 = 0x01; +type DeviceType = Mutex>; + #[embassy_executor::task] async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { @@ -88,8 +89,9 @@ async fn opm_task(context: &'static Context, state: [&'static mock_controller::C info!("Sending command complete ack successful"); } - info!("Connecting sinks on both ports"); + info!("Connecting sink on port 0"); state[0].connect_sink(CAPABILITY, false).await; + info!("Connecting sink on port 1"); state[1].connect_sink(CAPABILITY, false).await; // Ensure connect flow has time to complete @@ -176,30 +178,21 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { } #[embassy_executor::task] -async fn power_policy_service_task( - service: &'static PowerPolicyService< - 'static, - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, +async fn power_policy_task( + psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static Mutex>, ) { - power_policy_service::service::task::task(service) - .await - .expect("Failed to start power policy service task"); + power_policy_service::service::task::task(psu_events, power_policy).await; } #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static>, + service: &'static Service<'static, DeviceType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; + type_c_service::task::task(service, wrappers, cfu_client).await; } #[embassy_executor::task] @@ -208,26 +201,6 @@ async fn task(spawner: Spawner) { embedded_services::init().await; - // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell< - power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - - static POWER_SERVICE: StaticCell< - power_policy_service::service::Service< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( - power_service_context, - power_policy_service::service::config::Config::default(), - )); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); @@ -237,31 +210,34 @@ async fn task(spawner: Spawner) { static STORAGE0: StaticCell> = StaticCell::new(); let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); - static INTERMEDIATE0: StaticCell> = - StaticCell::new(); + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + + static INTERMEDIATE0: StaticCell< + type_c_service::wrapper::backing::IntermediateStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, psu::event::EventData>, + >, + > = StaticCell::new(); let intermediate0 = INTERMEDIATE0.init( storage0 - .try_create_intermediate() + .try_create_intermediate([("Pd0", policy_sender0)]) .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL0: StaticCell> = - StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - static REFERENCED0: StaticCell< type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, + DynamicSender<'_, psu::event::EventData>, >, > = StaticCell::new(); let referenced0 = REFERENCED0.init( intermediate0 - .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -270,38 +246,43 @@ async fn task(spawner: Spawner) { static CONTROLLER0: StaticCell> = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); static WRAPPER0: StaticCell = StaticCell::new(); - let wrapper0 = WRAPPER0.init( - mock_controller::Wrapper::try_new(controller0, Default::default(), referenced0, mock_controller::Validator) - .expect("Failed to create wrapper"), - ); + let wrapper0 = WRAPPER0.init(mock_controller::Wrapper::new( + controller0, + Default::default(), + referenced0, + mock_controller::Validator, + )); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); static STORAGE1: StaticCell> = StaticCell::new(); let storage1 = STORAGE1.init(Storage::new(controller_context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); - static INTERMEDIATE1: StaticCell> = - StaticCell::new(); + static INTERMEDIATE1: StaticCell< + type_c_service::wrapper::backing::IntermediateStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, psu::event::EventData>, + >, + > = StaticCell::new(); let intermediate1 = INTERMEDIATE1.init( storage1 - .try_create_intermediate() + .try_create_intermediate([("Pd1", policy_sender1)]) .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL1: StaticCell> = - StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - static REFERENCED1: StaticCell< type_c_service::wrapper::backing::ReferencedStorage< 1, GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, + DynamicSender<'_, psu::event::EventData>, >, > = StaticCell::new(); let referenced1 = REFERENCED1.init( intermediate1 - .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -310,15 +291,32 @@ async fn task(spawner: Spawner) { static CONTROLLER1: StaticCell> = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); static WRAPPER1: StaticCell = StaticCell::new(); - let wrapper1 = WRAPPER1.init( - mock_controller::Wrapper::try_new(controller1, Default::default(), referenced1, mock_controller::Validator) - .expect("Failed to create wrapper"), - ); + let wrapper1 = WRAPPER1.init(mock_controller::Wrapper::new( + controller1, + Default::default(), + referenced1, + mock_controller::Validator, + )); + + // Create power policy service + static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); + let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); + + static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); + let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy]); + + static POWER_SERVICE: StaticCell>> = + StaticCell::new(); + let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( + psu_registration, + power_service_context, + power_policy_service::service::config::Config::default(), + ))); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, 4, 1, 0>, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -326,7 +324,7 @@ async fn task(spawner: Spawner) { // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Config { ucsi_capabilities: UcsiCapabilities { @@ -362,13 +360,15 @@ async fn task(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(power_policy_service_task(power_service)); - spawner.must_spawn(type_c_service_task( - type_c_service, - [wrapper0, wrapper1], - power_service_context, - cfu_client, + spawner.must_spawn(power_policy_task( + EventReceivers::new( + [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], + [policy_receiver0, policy_receiver1], + ), + power_service, )); + + spawner.must_spawn(type_c_service_task(type_c_service, [wrapper0, wrapper1], cfu_client)); spawner.must_spawn(wrapper_task(wrapper0)); spawner.must_spawn(wrapper_task(wrapper1)); spawner.must_spawn(opm_task(controller_context, [state0, state1])); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 5913bbbcc..813e80527 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -12,7 +12,8 @@ use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; -use power_policy_service::service::Service as PowerPolicyService; +use power_policy_interface::psu; +use power_policy_service::psu::EventReceivers; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -24,21 +25,20 @@ use type_c_service::wrapper::proxy::PowerProxyDevice; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const POWER0_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(0); const CFU0_ID: u8 = 0x00; const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const POWER1_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(1); const CFU1_ID: u8 = 0x01; const CONTROLLER2_ID: ControllerId = ControllerId(2); const PORT2_ID: GlobalPortId = GlobalPortId(2); -const POWER2_ID: power_policy_interface::psu::DeviceId = power_policy_interface::psu::DeviceId(2); const CFU2_ID: u8 = 0x02; const DELAY_MS: u64 = 1000; +type DeviceType = Mutex>; + #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -53,59 +53,38 @@ async fn task(spawner: Spawner) { embedded_services::init().await; // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell< - power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); + static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - static POWER_SERVICE: StaticCell< - power_policy_service::service::Service< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); - let power_service = POWER_SERVICE.init(power_policy_service::service::Service::new( - power_service_context, - power_policy_service::service::config::Config::default(), - )); - static CONTEXT: StaticCell = StaticCell::new(); let context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); static CONTROLLER_LIST: StaticCell = StaticCell::new(); let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); + let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); + let policy_sender0 = policy_channel0.dyn_sender(); + let policy_receiver0 = policy_channel0.dyn_receiver(); + static STORAGE0: StaticCell> = StaticCell::new(); let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); - static INTERMEDIATE0: StaticCell> = StaticCell::new(); + static INTERMEDIATE0: StaticCell< + IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, + > = StaticCell::new(); let intermediate0 = INTERMEDIATE0.init( storage0 - .try_create_intermediate() + .try_create_intermediate([("Pd0", policy_sender0)]) .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL0: StaticCell> = + static REFERENCED0: StaticCell>> = StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - - static REFERENCED0: StaticCell< - ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); let referenced0 = REFERENCED0.init( intermediate0 - .try_create_referenced([(POWER0_ID, policy_sender0, policy_receiver0)]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -114,42 +93,34 @@ async fn task(spawner: Spawner) { static CONTROLLER0: StaticCell> = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); static WRAPPER0: StaticCell = StaticCell::new(); - let wrapper0 = WRAPPER0.init( - mock_controller::Wrapper::try_new( - controller0, - Default::default(), - referenced0, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ); + let wrapper0 = WRAPPER0.init(mock_controller::Wrapper::new( + controller0, + Default::default(), + referenced0, + crate::mock_controller::Validator, + )); + + static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); + let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); + let policy_sender1 = policy_channel1.dyn_sender(); + let policy_receiver1 = policy_channel1.dyn_receiver(); static STORAGE1: StaticCell> = StaticCell::new(); let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); - static INTERMEDIATE1: StaticCell> = StaticCell::new(); + static INTERMEDIATE1: StaticCell< + IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, + > = StaticCell::new(); let intermediate1 = INTERMEDIATE1.init( storage1 - .try_create_intermediate() + .try_create_intermediate([("Pd1", policy_sender1)]) .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL1: StaticCell> = + static REFERENCED1: StaticCell>> = StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - - static REFERENCED1: StaticCell< - ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); let referenced1 = REFERENCED1.init( intermediate1 - .try_create_referenced([(POWER1_ID, policy_sender1, policy_receiver1)]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -158,42 +129,34 @@ async fn task(spawner: Spawner) { static CONTROLLER1: StaticCell> = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); static WRAPPER1: StaticCell = StaticCell::new(); - let wrapper1 = WRAPPER1.init( - mock_controller::Wrapper::try_new( - controller1, - Default::default(), - referenced1, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ); + let wrapper1 = WRAPPER1.init(mock_controller::Wrapper::new( + controller1, + Default::default(), + referenced1, + crate::mock_controller::Validator, + )); + + static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); + let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); + let policy_sender2 = policy_channel2.dyn_sender(); + let policy_receiver2 = policy_channel2.dyn_receiver(); static STORAGE2: StaticCell> = StaticCell::new(); let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); - static INTERMEDIATE2: StaticCell> = StaticCell::new(); + static INTERMEDIATE2: StaticCell< + IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, + > = StaticCell::new(); let intermediate2 = INTERMEDIATE2.init( storage2 - .try_create_intermediate() + .try_create_intermediate([("Pd2", policy_sender2)]) .expect("Failed to create intermediate storage"), ); - static POLICY_CHANNEL2: StaticCell> = + static REFERENCED2: StaticCell>> = StaticCell::new(); - let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); - let policy_sender2 = policy_channel2.dyn_sender(); - let policy_receiver2 = policy_channel2.dyn_receiver(); - - static REFERENCED2: StaticCell< - ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::RequestData>, - DynamicReceiver<'_, power_policy_interface::psu::event::RequestData>, - >, - > = StaticCell::new(); let referenced2 = REFERENCED2.init( intermediate2 - .try_create_referenced([(POWER2_ID, policy_sender2, policy_receiver2)]) + .try_create_referenced() .expect("Failed to create referenced storage"), ); @@ -202,20 +165,32 @@ async fn task(spawner: Spawner) { static CONTROLLER2: StaticCell> = StaticCell::new(); let controller2 = CONTROLLER2.init(Mutex::new(mock_controller::Controller::new(state2))); static WRAPPER2: StaticCell = StaticCell::new(); - let wrapper2 = WRAPPER2.init( - mock_controller::Wrapper::try_new( - controller2, - Default::default(), - referenced2, - crate::mock_controller::Validator, - ) - .expect("Failed to create wrapper"), - ); + let wrapper2 = WRAPPER2.init(mock_controller::Wrapper::new( + controller2, + Default::default(), + referenced2, + crate::mock_controller::Validator, + )); + + static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 3]> = StaticCell::new(); + let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([ + &wrapper0.ports[0].proxy, + &wrapper1.ports[0].proxy, + &wrapper2.ports[0].proxy, + ]); + + static POWER_SERVICE: StaticCell>> = + StaticCell::new(); + let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( + psu_registration, + power_service_context, + power_policy_service::service::config::Config::default(), + ))); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, + PubSubChannel, 4, 1, 0>, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); @@ -223,7 +198,7 @@ async fn task(spawner: Spawner) { // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, @@ -236,11 +211,20 @@ async fn task(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(power_policy_service_task(power_service)); + spawner.must_spawn(power_policy_task( + EventReceivers::new( + [ + &wrapper0.ports[0].proxy, + &wrapper1.ports[0].proxy, + &wrapper2.ports[0].proxy, + ], + [policy_receiver0, policy_receiver1, policy_receiver2], + ), + power_service, + )); spawner.must_spawn(type_c_service_task( type_c_service, [wrapper0, wrapper1, wrapper2], - power_service_context, cfu_client, )); @@ -298,30 +282,21 @@ async fn task(spawner: Spawner) { } #[embassy_executor::task] -async fn power_policy_service_task( - service: &'static PowerPolicyService< - 'static, - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, +async fn power_policy_task( + psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static Mutex>, ) { - power_policy_service::service::task::task(service) - .await - .expect("Failed to start power policy service task"); + power_policy_service::service::task::task(psu_events, power_policy).await; } #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static>, + service: &'static Service<'static, DeviceType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - power_policy_context: &'static power_policy_service::service::context::Context< - Mutex>, - DynamicReceiver<'static, power_policy_interface::psu::event::RequestData>, - >, cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, power_policy_context, cfu_client).await; + type_c_service::task::task(service, wrappers, cfu_client).await; } fn main() { diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 45dd0478a..1f9352c5b 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -342,7 +342,6 @@ pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< 'a, GlobalRawMutex, Mutex>, - channel::DynamicSender<'a, power_policy_interface::psu::event::RequestData>, - channel::DynamicReceiver<'a, power_policy_interface::psu::event::RequestData>, + channel::DynamicSender<'a, power_policy_interface::psu::event::EventData>, Validator, >; diff --git a/power-policy-interface/src/psu/event.rs b/power-policy-interface/src/psu/event.rs index 9ac19ede4..10b9fbdfe 100644 --- a/power-policy-interface/src/psu/event.rs +++ b/power-policy-interface/src/psu/event.rs @@ -1,10 +1,15 @@ //! Messages originating from a PSU -use crate::capability::{ConsumerPowerCapability, ProviderPowerCapability}; +use embedded_services::sync::Lockable; -/// Data for a power policy request +use crate::{ + capability::{ConsumerPowerCapability, ProviderPowerCapability}, + psu, +}; + +/// Data for an event broadcast from a PSU. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RequestData { +pub enum EventData { /// Notify that a device has attached Attached, /// Notify that available power for consumption has changed @@ -17,14 +22,17 @@ pub enum RequestData { Detached, } -/// Request to the power policy service +/// Event broadcast from a PSU. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Request { +pub struct Event<'a, D: Lockable> +where + D::Inner: psu::Psu, +{ /// Device that sent this request - pub id: super::DeviceId, - /// Request data - pub data: RequestData, + pub psu: &'a D, + /// Event data + pub event: EventData, } /// Data for a power policy response @@ -48,8 +56,6 @@ impl ResponseData { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Response { - /// Target device - pub id: super::DeviceId, /// Response data pub data: ResponseData, } diff --git a/power-policy-interface/src/psu/mod.rs b/power-policy-interface/src/psu/mod.rs index 8f045559e..323b83183 100644 --- a/power-policy-interface/src/psu/mod.rs +++ b/power-policy-interface/src/psu/mod.rs @@ -1,10 +1,5 @@ //! Device struct and methods -use embassy_sync::mutex::Mutex; - use crate::capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}; -use embedded_services::event::Receiver; -use embedded_services::sync::Lockable; -use embedded_services::{GlobalRawMutex, intrusive_list}; pub mod event; @@ -34,11 +29,6 @@ pub enum Error { Failed, } -/// Device ID new type -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - /// Most basic device states #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -56,7 +46,7 @@ pub enum StateKind { /// Current state of the power device #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { +pub enum PsuState { /// Device is attached, but is not currently providing or consuming power Idle, /// Device is attached and is currently providing power @@ -67,14 +57,14 @@ pub enum State { Detached, } -impl State { - /// Returns the correpsonding state kind +impl PsuState { + /// Returns the corresponding state kind pub fn kind(&self) -> StateKind { match self { - State::Idle => StateKind::Idle, - State::ConnectedProvider(_) => StateKind::ConnectedProvider, - State::ConnectedConsumer(_) => StateKind::ConnectedConsumer, - State::Detached => StateKind::Detached, + PsuState::Idle => StateKind::Idle, + PsuState::ConnectedProvider(_) => StateKind::ConnectedProvider, + PsuState::ConnectedConsumer(_) => StateKind::ConnectedConsumer, + PsuState::Detached => StateKind::Detached, } } } @@ -89,34 +79,34 @@ impl State { /// end up catching up to this state anyway. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InternalState { +pub struct State { /// Current state of the device - state: State, + pub psu_state: PsuState, /// Current consumer capability - consumer_capability: Option, + pub consumer_capability: Option, /// Current requested provider capability - requested_provider_capability: Option, + pub requested_provider_capability: Option, } -impl Default for InternalState { +impl Default for State { fn default() -> Self { Self { - state: State::Detached, + psu_state: PsuState::Detached, consumer_capability: None, requested_provider_capability: None, } } } -impl InternalState { +impl State { /// Attach the device pub fn attach(&mut self) -> Result<(), Error> { - let result = if self.state == State::Detached { + let result = if self.psu_state == PsuState::Detached { Ok(()) } else { - Err(Error::InvalidState(&[StateKind::Detached], self.state.kind())) + Err(Error::InvalidState(&[StateKind::Detached], self.psu_state.kind())) }; - self.state = State::Idle; + self.psu_state = PsuState::Idle; result } @@ -124,22 +114,25 @@ impl InternalState { /// /// Detach is always a valid transition pub fn detach(&mut self) { - self.state = State::Detached; + self.psu_state = PsuState::Detached; self.consumer_capability = None; self.requested_provider_capability = None; } /// Disconnect this device pub fn disconnect(&mut self, clear_caps: bool) -> Result<(), Error> { - let result = if matches!(self.state, State::ConnectedConsumer(_) | State::ConnectedProvider(_)) { + let result = if matches!( + self.psu_state, + PsuState::ConnectedConsumer(_) | PsuState::ConnectedProvider(_) + ) { Ok(()) } else { Err(Error::InvalidState( &[StateKind::ConnectedConsumer, StateKind::ConnectedProvider], - self.state.kind(), + self.psu_state.kind(), )) }; - self.state = State::Idle; + self.psu_state = PsuState::Idle; if clear_caps { self.consumer_capability = None; self.requested_provider_capability = None; @@ -152,15 +145,15 @@ impl InternalState { &mut self, capability: Option, ) -> Result<(), Error> { - let result = match self.state { - State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => Ok(()), + let result = match self.psu_state { + PsuState::Idle | PsuState::ConnectedConsumer(_) | PsuState::ConnectedProvider(_) => Ok(()), _ => Err(Error::InvalidState( &[ StateKind::Idle, StateKind::ConnectedConsumer, StateKind::ConnectedProvider, ], - self.state.kind(), + self.psu_state.kind(), )), }; self.consumer_capability = capability; @@ -177,15 +170,15 @@ impl InternalState { return Ok(()); } - let result = match self.state { - State::Idle | State::ConnectedConsumer(_) | State::ConnectedProvider(_) => Ok(()), + let result = match self.psu_state { + PsuState::Idle | PsuState::ConnectedConsumer(_) | PsuState::ConnectedProvider(_) => Ok(()), _ => Err(Error::InvalidState( &[ StateKind::Idle, StateKind::ConnectedProvider, StateKind::ConnectedConsumer, ], - self.state.kind(), + self.psu_state.kind(), )), }; @@ -193,44 +186,50 @@ impl InternalState { result } + /// Check if a request to connect as a consumer from the policy is valid given the current state + /// Returns () or the error with information about why the request is invalid + pub fn can_connect_consumer(&self) -> Result<(), Error> { + match self.psu_state { + PsuState::Idle | PsuState::ConnectedConsumer(_) => Ok(()), + _ => Err(Error::InvalidState( + &[StateKind::Idle, StateKind::ConnectedConsumer], + self.psu_state.kind(), + )), + } + } + /// Handle a request to connect as a consumer from the policy pub fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { - let result = if self.state == State::Idle { - Ok(()) - } else { - Err(Error::InvalidState(&[StateKind::Idle], self.state.kind())) - }; - self.state = State::ConnectedConsumer(capability); - result + self.can_connect_consumer()?; + self.psu_state = PsuState::ConnectedConsumer(capability); + Ok(()) } - /// Handle a request to connect as a provider from the policy - pub fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { - let result = if matches!(self.state, State::Idle | State::ConnectedProvider(_)) { - Ok(()) - } else { - Err(Error::InvalidState( + /// Check if a request to connect as a provider from the policy is valid given the current state + /// Returns () or the error with information about why the request is invalid + pub fn can_connect_provider(&self) -> Result<(), Error> { + match self.psu_state { + PsuState::Idle | PsuState::ConnectedProvider(_) => Ok(()), + _ => Err(Error::InvalidState( &[StateKind::Idle, StateKind::ConnectedProvider], - self.state.kind(), - )) - }; - self.state = State::ConnectedProvider(capability); - result - } - - /// Returns the current state machine state - pub fn state(&self) -> State { - self.state + self.psu_state.kind(), + )), + } } - /// Returns the current consumer capability - pub fn consumer_capability(&self) -> Option { - self.consumer_capability + /// Handle a request to connect as a provider from the policy + pub fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { + self.can_connect_provider()?; + self.psu_state = PsuState::ConnectedProvider(capability); + Ok(()) } - /// Returns the requested provider capability - pub fn requested_provider_capability(&self) -> Option { - self.requested_provider_capability + /// Returns the current provider capability if the PSU is connected as a provider + pub fn connected_provider_capability(&self) -> Option { + match self.psu_state { + PsuState::ConnectedProvider(capability) => Some(capability), + _ => None, + } } } @@ -250,8 +249,6 @@ pub enum CommandData { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Command { - /// Target device - pub id: DeviceId, /// Request data pub data: CommandData, } @@ -278,8 +275,6 @@ pub type InternalResponseData = Result; /// Response from a device to the power policy service pub struct Response { - /// Target device - pub id: DeviceId, /// Response data pub data: ResponseData, } @@ -292,102 +287,10 @@ pub trait Psu { fn connect_provider(&mut self, capability: ProviderPowerCapability) -> impl Future>; /// Connect this device to consume power from an external connection fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> impl Future>; -} - -/// PSU registration struct -pub struct RegistrationEntry<'a, D: Lockable, R: Receiver> -where - D::Inner: Psu, -{ - /// Intrusive list node - node: intrusive_list::Node, - /// Device ID - id: DeviceId, - /// Current state of the device - pub state: Mutex, - /// Reference to hardware - pub device: &'a D, - /// Event receiver - pub receiver: Mutex, -} - -impl<'a, D: Lockable, R: Receiver> RegistrationEntry<'a, D, R> -where - D::Inner: Psu, -{ - /// Create a new device - pub fn new(id: DeviceId, device: &'a D, receiver: R) -> Self { - Self { - node: intrusive_list::Node::uninit(), - id, - state: Mutex::new(InternalState { - state: State::Detached, - consumer_capability: None, - requested_provider_capability: None, - }), - device, - receiver: Mutex::new(receiver), - } - } - - /// Get the device ID - pub fn id(&self) -> DeviceId { - self.id - } - - /// Returns the current consumer capability of the device - pub async fn consumer_capability(&self) -> Option { - self.state.lock().await.consumer_capability - } - - /// Returns true if the device is currently consuming power - pub async fn is_consumer(&self) -> bool { - self.state.lock().await.state.kind() == StateKind::ConnectedConsumer - } - - /// Returns current provider power capability - pub async fn provider_capability(&self) -> Option { - match self.state.lock().await.state { - State::ConnectedProvider(capability) => Some(capability), - _ => None, - } - } - - /// Returns the current requested provider capability - pub async fn requested_provider_capability(&self) -> Option { - self.state.lock().await.requested_provider_capability - } - - /// Returns true if the device is currently providing power - pub async fn is_provider(&self) -> bool { - self.state.lock().await.state.kind() == StateKind::ConnectedProvider - } -} - -impl + 'static> intrusive_list::NodeContainer - for RegistrationEntry<'static, D, R> -where - D::Inner: Psu, -{ - fn get_node(&self) -> &intrusive_list::Node { - &self.node - } -} - -/// Trait for any container that holds a device -pub trait PsuContainer> -where - D::Inner: Psu, -{ - /// Get the underlying device struct - fn get_power_policy_device(&self) -> &RegistrationEntry<'_, D, R>; -} - -impl> PsuContainer for RegistrationEntry<'_, D, R> -where - D::Inner: Psu, -{ - fn get_power_policy_device(&self) -> &RegistrationEntry<'_, D, R> { - self - } + /// Return an immutable reference to the current PSU state + fn state(&self) -> &State; + /// Return a mutable reference to the current PSU state + fn state_mut(&mut self) -> &mut State; + /// Return the name of the PSU + fn name(&self) -> &'static str; } diff --git a/power-policy-interface/src/service/event.rs b/power-policy-interface/src/service/event.rs index ca09844d0..9888142e4 100644 --- a/power-policy-interface/src/service/event.rs +++ b/power-policy-interface/src/service/event.rs @@ -1,29 +1,43 @@ +use embedded_services::sync::Lockable; + use crate::{ capability::{ConsumerPowerCapability, ProviderPowerCapability}, - psu::DeviceId, + psu::Psu, service::UnconstrainedState, }; -/// Data to send with the comms service -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Events broadcast from the service. +#[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CommsData { +pub enum Event<'device, D: Lockable> +where + D::Inner: Psu, +{ /// Consumer disconnected - ConsumerDisconnected(DeviceId), + ConsumerDisconnected(&'device D), /// Consumer connected - ConsumerConnected(DeviceId, ConsumerPowerCapability), + ConsumerConnected(&'device D, ConsumerPowerCapability), /// Provider disconnected - ProviderDisconnected(DeviceId), + ProviderDisconnected(&'device D), /// Provider connected - ProviderConnected(DeviceId, ProviderPowerCapability), + ProviderConnected(&'device D, ProviderPowerCapability), /// Unconstrained state changed Unconstrained(UnconstrainedState), } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Message to send with the comms service -pub struct CommsMessage { - /// Message data - pub data: CommsData, +impl<'device, D> Clone for Event<'device, D> +where + D: Lockable, + D::Inner: Psu, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'device, D> Copy for Event<'device, D> +where + D: Lockable, + D::Inner: Psu, +{ } diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 404b150f5..2f0552f04 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -30,6 +30,9 @@ embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } tokio = { workspace = true, features = ["rt", "macros", "time"] } env_logger = "0.11.8" log = { workspace = true } +# TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile +# Uncomment this line to enable log output in tests +#power-policy-service = { workspace = true, features = ["log"] } [features] default = [] diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index de4e4958a..e9592e0e7 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -1,2 +1,3 @@ #![no_std] +pub mod psu; pub mod service; diff --git a/power-policy-service/src/psu.rs b/power-policy-service/src/psu.rs new file mode 100644 index 000000000..47ce355d6 --- /dev/null +++ b/power-policy-service/src/psu.rs @@ -0,0 +1,40 @@ +use core::pin::pin; + +use embassy_futures::select::select_slice; +use embedded_services::event::Receiver; +use embedded_services::sync::Lockable; +use power_policy_interface::psu::Psu; +use power_policy_interface::psu::event::{Event, EventData}; + +/// Struct used to contain PSU event receivers and manage mapping from a receiver to its corresponding device. +pub struct EventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> +where + PSU::Inner: Psu, +{ + pub psu_devices: [&'a PSU; N], + pub receivers: [R; N], +} + +impl<'a, const N: usize, PSU: Lockable, R: Receiver> EventReceivers<'a, N, PSU, R> +where + PSU::Inner: Psu, +{ + /// Create a new instance + pub fn new(psu_devices: [&'a PSU; N], receivers: [R; N]) -> Self { + Self { psu_devices, receivers } + } + + /// Get the next pending PSU event + pub async fn wait_event(&mut self) -> Event<'a, PSU> { + let ((event, psu), _) = { + let mut futures = heapless::Vec::<_, N>::new(); + for (receiver, psu) in self.receivers.iter_mut().zip(self.psu_devices.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 { psu, event } + } +} diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index 9210011d8..ea271d62c 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -1,23 +1,37 @@ use core::cmp::Ordering; -use core::ops::DerefMut; use embedded_services::{debug, error}; use super::*; use power_policy_interface::capability::ConsumerFlags; use power_policy_interface::charger::Device as ChargerDevice; -use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::State}; +use power_policy_interface::service::event::Event as ServiceEvent; +use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::PsuState}; /// State of the current consumer -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AvailableConsumer { - /// The ID of the currently connected consumer - pub device_id: DeviceId, +pub struct AvailableConsumer<'device, D: Lockable> +where + D::Inner: Psu, +{ + /// Device reference + pub psu: &'device D, /// The power capability of the currently connected consumer pub consumer_power_capability: ConsumerPowerCapability, } +impl<'device, D: Lockable> Clone for AvailableConsumer<'device, D> +where + D::Inner: Psu, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'device, D: Lockable> Copy for AvailableConsumer<'device, D> where D::Inner: Psu {} + /// Compare two consumer capabilities to determine which one is better /// /// This is not part of the `Ord` implementation for `ConsumerPowerCapability`, because it's specific to this implementation. @@ -32,27 +46,26 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl + 'static> Service<'_, D, R> +impl<'a, PSU: Lockable> Service<'a, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Iterate over all devices to determine what is best power port provides the highest power - async fn find_best_consumer(&self, state: &InternalState) -> Result, Error> { + async fn find_best_consumer(&self) -> Result>, Error> { let mut best_consumer = None; - let current_consumer_id = state.current_consumer_state.map(|f| f.device_id); - - for node in self.context.psu_devices() { - let device = node.data::>().ok_or(Error::InvalidDevice)?; + let current_consumer = self.state.current_consumer_state.as_ref().map(|f| f.psu); - let consumer_capability = device.consumer_capability().await; + for psu in self.psu_devices.iter() { + let locked_psu = psu.lock().await; + let consumer_capability = locked_psu.state().consumer_capability; // Don't consider consumers below minimum threshold if consumer_capability .zip(self.config.min_consumer_threshold_mw) .is_some_and(|(cap, min)| cap.capability.max_power_mw() < min) { info!( - "Device{}: Not considering consumer, power capability is too low", - device.id().0, + "({}): Not considering consumer, power capability is too low", + locked_psu.name(), ); continue; } @@ -63,7 +76,7 @@ where (None, None) => None, // No existing consumer (None, Some(power_capability)) => Some(AvailableConsumer { - device_id: device.id(), + psu: *psu, consumer_power_capability: power_capability, }), // Existing consumer, no new consumer @@ -72,13 +85,13 @@ where (Some(best), Some(available)) => { if cmp_consumer_capability( &available, - Some(device.id()) == current_consumer_id, + current_consumer.is_some_and(|current_consumer| ptr::eq(current_consumer, *psu)), &best.consumer_power_capability, - Some(best.device_id) == current_consumer_id, + current_consumer.is_some_and(|current_consumer| ptr::eq(current_consumer, best.psu)), ) == core::cmp::Ordering::Greater { Some(AvailableConsumer { - device_id: device.id(), + psu, consumer_power_capability: available, }) } else { @@ -92,12 +105,11 @@ where } /// Update unconstrained state and broadcast notifications if needed - async fn update_unconstrained_state(&self, state: &mut InternalState) -> Result<(), Error> { + async fn update_unconstrained_state(&mut self) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for node in self.context.psu_devices() { - let device = node.data::>().ok_or(Error::InvalidDevice)?; - if let Some(capability) = device.consumer_capability().await { + for psu in self.psu_devices.iter() { + if let Some(capability) = psu.lock().await.state().consumer_capability { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; } @@ -105,28 +117,24 @@ where } // The overall unconstrained state is true if an unconstrained consumer is currently connected - unconstrained_new.unconstrained = state + unconstrained_new.unconstrained = self + .state .current_consumer_state + .as_ref() .is_some_and(|current| current.consumer_power_capability.flags.unconstrained_power()); - if unconstrained_new != state.unconstrained { + if unconstrained_new != self.state.unconstrained { info!("Unconstrained state changed: {:?}", unconstrained_new); - state.unconstrained = unconstrained_new; - self.comms_notify(CommsMessage { - data: CommsData::Unconstrained(state.unconstrained), - }) - .await; + self.state.unconstrained = unconstrained_new; + self.broadcast_event(ServiceEvent::Unconstrained(self.state.unconstrained)) + .await; } Ok(()) } /// Common logic to execute after a consumer is connected - async fn post_consumer_connected( - &self, - state: &mut InternalState, - connected_consumer: AvailableConsumer, - ) -> Result<(), Error> { - state.current_consumer_state = Some(connected_consumer); + async fn post_consumer_connected(&mut self, connected_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + self.state.current_consumer_state = Some(connected_consumer); // todo: review the delay time embassy_time::Timer::after_millis(800).await; @@ -152,12 +160,10 @@ where .await?; } } - self.comms_notify(CommsMessage { - data: CommsData::ConsumerConnected( - connected_consumer.device_id, - connected_consumer.consumer_power_capability, - ), - }) + self.broadcast_event(ServiceEvent::ConsumerConnected( + connected_consumer.psu, + connected_consumer.consumer_power_capability, + )) .await; Ok(()) @@ -185,14 +191,10 @@ where } /// Connect to a new consumer - async fn connect_new_consumer( - &self, - state: &mut InternalState, - new_consumer: AvailableConsumer, - ) -> Result<(), Error> { + async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { // Handle our current consumer - if let Some(current_consumer) = state.current_consumer_state { - if new_consumer.device_id == current_consumer.device_id + if let Some(current_consumer) = self.state.current_consumer_state { + if ptr::eq(current_consumer.psu, new_consumer.psu) && new_consumer.consumer_power_capability == current_consumer.consumer_power_capability { // If the consumer is the same device, capability, and is still available, we don't need to do anything @@ -200,22 +202,17 @@ where return Ok(()); } - state.current_consumer_state = None; - let consumer_device = self.context.get_psu(current_consumer.device_id)?; - let mut locked_state = consumer_device.state.lock().await; - let mut locked_device = consumer_device.device.lock().await; + self.state.current_consumer_state = None; + let mut current_psu = current_consumer.psu.lock().await; - if matches!(locked_state.state(), State::ConnectedConsumer(_)) { + if matches!(current_psu.state().psu_state, PsuState::ConnectedConsumer(_)) { // Disconnect the current consumer if needed - info!("Device{}: Disconnecting current consumer", current_consumer.device_id.0); + info!("({}): Disconnecting current consumer", current_psu.name()); // disconnect current consumer and set idle - locked_device.disconnect().await?; - if let Err(e) = locked_state.disconnect(false) { + current_psu.disconnect().await?; + if let Err(e) = current_psu.state_mut().disconnect(false) { // This should never happen because we check the state above, log an error instead of a panic - error!( - "Device{}: Disconnect transition failed: {:#?}", - current_consumer.device_id.0, e - ); + error!("({}): Disconnect transition failed: {:#?}", current_psu.name(), e); } } @@ -225,61 +222,60 @@ where // so just continue execution. self.disconnect_chargers().await?; - self.comms_notify(CommsMessage { - data: CommsData::ConsumerDisconnected(current_consumer.device_id), - }) - .await; + self.broadcast_event(ServiceEvent::ConsumerDisconnected(current_consumer.psu)) + .await; // Don't update the unconstrained here because this is a transitional state } - info!("Device {}, connecting new consumer", new_consumer.device_id.0); - let device = self.context.get_psu(new_consumer.device_id)?; - let mut locked_device = device.device.lock().await; - let mut locked_state = device.state.lock().await; + let mut psu = new_consumer.psu.lock().await; + info!("({}): Connecting new consumer", psu.name()); - if let e @ Err(_) = locked_state.connect_consumer(new_consumer.consumer_power_capability) { + if let e @ Err(_) = psu.state().can_connect_consumer() { error!( - "Device{}: Not ready to connect consumer, state: {:#?}", - device.id().0, - locked_state.state() + "({}): Not ready to connect consumer, state: {:#?}", + psu.name(), + psu.state().psu_state ); e } else { - locked_device - .connect_consumer(new_consumer.consumer_power_capability) - .await?; - self.post_consumer_connected(state, new_consumer).await + psu.connect_consumer(new_consumer.consumer_power_capability).await?; + psu.state_mut() + .connect_consumer(new_consumer.consumer_power_capability)?; + self.post_consumer_connected(new_consumer).await } } /// Determines and connects the best external power - pub(super) async fn update_current_consumer(&self) -> Result<(), Error> { - let mut guard = self.state.lock().await; - let state = guard.deref_mut(); - info!( - "Selecting power port, current power: {:#?}", - state.current_consumer_state - ); - - let best_consumer = self.find_best_consumer(state).await?; - info!("Best consumer: {:#?}", best_consumer); + pub(super) async fn update_current_consumer(&mut self) -> Result<(), Error> { + let current_consumer_name = if let Some(current_consumer) = self.state.current_consumer_state { + current_consumer.psu.lock().await.name() + } else { + "None" + }; + info!("Selecting power port, current power: {:#?}", current_consumer_name); + + let best_consumer = self.find_best_consumer().await?; + let best_consumer_name = if let Some(best_consumer) = best_consumer { + best_consumer.psu.lock().await.name() + } else { + "None" + }; + info!("Best consumer: {:#?}", best_consumer_name); if let Some(best_consumer) = best_consumer { - self.connect_new_consumer(state, best_consumer).await?; + self.connect_new_consumer(best_consumer).await?; } else { // Notify disconnect if recently detached consumer was previously attached. - if let Some(consumer_state) = state.current_consumer_state { + if let Some(current_consumer) = self.state.current_consumer_state { self.disconnect_chargers().await?; - self.comms_notify(CommsMessage { - data: CommsData::ConsumerDisconnected(consumer_state.device_id), - }) - .await; + self.broadcast_event(ServiceEvent::ConsumerDisconnected(current_consumer.psu)) + .await; } // No new consumer available - state.current_consumer_state = None; + self.state.current_consumer_state = None; } - self.update_unconstrained_state(state).await + self.update_unconstrained_state().await } } diff --git a/power-policy-service/src/service/context.rs b/power-policy-service/src/service/context.rs index 4ec211817..100861abe 100644 --- a/power-policy-service/src/service/context.rs +++ b/power-policy-service/src/service/context.rs @@ -1,64 +1,27 @@ //! Context for any power policy implementations -use core::marker::PhantomData; -use core::pin::pin; - -use embassy_futures::select::select_slice; -use embedded_services::broadcaster::immediate as broadcaster; -use embedded_services::event::Receiver; -use embedded_services::sync::Lockable; -use power_policy_interface::charger; -use power_policy_interface::psu::Psu; -use power_policy_interface::psu::event::Request; - use embedded_services::{error, intrusive_list}; +use power_policy_interface::charger; use power_policy_interface::charger::ChargerResponse; -use power_policy_interface::psu::{self, DeviceId, Error, event::RequestData}; -use power_policy_interface::service::event::CommsMessage; +use power_policy_interface::psu::Error; /// Power policy context -pub struct Context> -where - D::Inner: Psu, -{ - /// Registered devices - psu_devices: intrusive_list::IntrusiveList, +pub struct Context { /// Registered chargers charger_devices: intrusive_list::IntrusiveList, - /// Message broadcaster - broadcaster: broadcaster::Immediate, - _phantom: PhantomData<(D, R)>, } -impl + 'static> Default for Context -where - D::Inner: Psu, -{ +impl Default for Context { fn default() -> Self { Self::new() } } -impl + 'static> Context -where - D::Inner: Psu, -{ +impl Context { /// Construct a new power policy Context pub const fn new() -> Self { Self { - psu_devices: intrusive_list::IntrusiveList::new(), charger_devices: intrusive_list::IntrusiveList::new(), - broadcaster: broadcaster::Immediate::new(), - _phantom: PhantomData, - } - } - - /// Register a power device with the service - pub fn register_psu(&self, psu: &'static impl psu::PsuContainer) -> Result<(), intrusive_list::Error> { - let psu = psu.get_power_policy_device(); - if self.get_psu(psu.id()).is_ok() { - return Err(intrusive_list::Error::NodeAlreadyInList); } - self.psu_devices.push(psu) } /// Register a charger with the power policy service @@ -74,35 +37,6 @@ where self.charger_devices.push(charger) } - /// Get a PSU by its ID - pub fn get_psu(&self, id: DeviceId) -> Result<&'static psu::RegistrationEntry<'static, D, R>, Error> { - for psu in &self.psu_devices { - if let Some(data) = psu.data::>() { - if data.id() == id { - return Ok(data); - } - } else { - error!("Non-device located in devices list"); - } - } - - Err(Error::InvalidDevice) - } - - /// Returns the total amount of power that is being supplied to external devices - pub async fn compute_total_provider_power_mw(&self) -> u32 { - let mut total = 0; - for psu in self.psu_devices.iter_only::>() { - if let Some(capability) = psu.provider_capability().await { - if psu.is_provider().await { - total += capability.capability.max_power_mw(); - } - } - } - - total - } - /// Get a charger by its ID pub fn get_charger(&self, id: charger::ChargerId) -> Result<&'static charger::Device, Error> { for charger in &self.charger_devices { @@ -142,14 +76,6 @@ where Ok(charger::ChargerResponseData::Ack) } - /// Register a message receiver for power policy messages - pub fn register_message_receiver( - &self, - receiver: &'static broadcaster::Receiver<'_, CommsMessage>, - ) -> intrusive_list::Result<()> { - self.broadcaster.register_receiver(receiver) - } - /// Initialize Policy charger devices pub async fn init(&self) -> Result<(), Error> { // Check if the chargers are powered and able to communicate @@ -160,48 +86,8 @@ where Ok(()) } - /// Provides access to the PSU device list - pub fn psu_devices(&self) -> &intrusive_list::IntrusiveList { - &self.psu_devices - } - /// Provides access to the charger list pub fn charger_devices(&self) -> &intrusive_list::IntrusiveList { &self.charger_devices } - - /// Broadcast a power policy message to all subscribers - pub async fn broadcast_message(&self, message: CommsMessage) { - self.broadcaster.broadcast(message).await; - } - - /// Get the next pending device event - pub async fn wait_request(&self) -> Request { - let mut futures = heapless::Vec::<_, 16>::new(); - for psu in self.psu_devices().iter_only::>() { - // TODO: Validate Vec size at compile time - if futures - .push(async { psu.receiver.lock().await.wait_next().await }) - .is_err() - { - error!("Futures vec overflow"); - } - } - - let (event, index) = select_slice(pin!(&mut futures)).await; - // Panic safety: The index is guaranteed to be within bounds since it comes from the select_slice result - #[allow(clippy::unwrap_used)] - let psu = self - .psu_devices() - .iter_only::>() - .nth(index) - .unwrap(); - Request { - id: psu.id(), - data: event, - } - } } - -/// Init power policy service -pub fn init() {} diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 4cb96fedb..7ea2feee8 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -1,220 +1,230 @@ //! Power policy related data structures and messages +use core::ptr; + pub mod config; pub mod consumer; pub mod context; pub mod provider; pub mod task; -pub use context::init; -use embassy_sync::mutex::Mutex; -use embedded_services::{GlobalRawMutex, comms, error, event::Receiver, info, sync::Lockable}; +use embedded_services::{error, info, sync::Lockable}; use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, psu::{ - DeviceId, Error, Psu, RegistrationEntry, - event::{Request, RequestData}, - }, - service::{ - UnconstrainedState, - event::{CommsData, CommsMessage}, + Error, Psu, + event::{Event as PsuEvent, EventData as PsuEventData}, }, + service::{UnconstrainedState, event::Event as ServiceEvent}, }; const MAX_CONNECTED_PROVIDERS: usize = 4; -#[derive(Clone, Default)] -struct InternalState { +#[derive(Clone)] +struct InternalState<'device, D: Lockable> +where + D::Inner: Psu, +{ /// Current consumer state, if any - current_consumer_state: Option, + current_consumer_state: Option>, /// Current provider global state current_provider_state: provider::State, /// System unconstrained power unconstrained: UnconstrainedState, /// Connected providers - connected_providers: heapless::FnvIndexSet, + connected_providers: heapless::FnvIndexSet, } -/// Power policy service -pub struct Service<'a, D: Lockable, R: Receiver> +impl Default for InternalState<'_, D> where D::Inner: Psu, +{ + fn default() -> Self { + Self { + current_consumer_state: None, + current_provider_state: provider::State::default(), + unconstrained: UnconstrainedState::default(), + connected_providers: heapless::FnvIndexSet::new(), + } + } +} + +/// Power policy service +pub struct Service<'a, PSU: Lockable> +where + PSU::Inner: Psu, { /// Power policy context - pub context: &'a context::Context, + pub context: &'a context::Context, + /// PSU devices + psu_devices: &'a [&'a PSU], /// State - state: Mutex, - /// Comms endpoint - tp: comms::Endpoint, + state: InternalState<'a, PSU>, /// Config config: config::Config, } -impl<'a, D: Lockable + 'static, R: Receiver + 'static> Service<'a, D, R> +impl<'a, PSU: Lockable> Service<'a, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Create a new power policy - pub fn new(context: &'a context::Context, config: config::Config) -> Self { + pub fn new(psu_devices: &'a [&'a PSU], context: &'a context::Context, config: config::Config) -> Self { Self { context, - state: Mutex::new(InternalState::default()), - tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Power)), + psu_devices, + state: InternalState::default(), config, } } - async fn process_notify_attach(&self, device: &RegistrationEntry<'_, D, R>) { - if let Err(e) = device.state.lock().await.attach() { - error!("Device{}: Invalid state for attach: {:#?}", device.id().0, e); + /// Returns the total amount of power that is being supplied to external devices + pub async fn compute_total_provider_power_mw(&self) -> u32 { + let mut total = 0; + + for psu in self.psu_devices.iter() { + let psu = psu.lock().await; + total += psu + .state() + .connected_provider_capability() + .map(|cap| cap.capability.max_power_mw()) + .unwrap_or(0); } + + total } - async fn process_notify_detach(&self, device: &RegistrationEntry<'_, D, R>) -> Result<(), Error> { - device.state.lock().await.detach(); + async fn process_notify_attach(&self, device: &PSU) { + let mut device = device.lock().await; + info!("({}): Received notify attached", device.name()); + if let Err(e) = device.state_mut().attach() { + error!("({}): Invalid state for attach: {:#?}", device.name(), e); + } + } + + async fn process_notify_detach(&mut self, device: &PSU) -> Result<(), Error> { + { + let mut device = device.lock().await; + info!("({}): Received notify detached", device.name()); + device.state_mut().detach(); + } self.update_current_consumer().await } async fn process_notify_consumer_power_capability( - &self, - device: &RegistrationEntry<'_, D, R>, + &mut self, + device: &PSU, capability: Option, ) -> Result<(), Error> { - if let Err(e) = device.state.lock().await.update_consumer_power_capability(capability) { - error!( - "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", - device.id().0, - e, + { + let mut device = device.lock().await; + info!( + "({}): Received notify consumer capability: {:#?}", + device.name(), + capability, ); + if let Err(e) = device.state_mut().update_consumer_power_capability(capability) { + error!( + "({}): Invalid state for notify consumer capability, catching up: {:#?}", + device.name(), + e, + ); + } } self.update_current_consumer().await } async fn process_request_provider_power_capabilities( - &self, - device: &RegistrationEntry<'_, D, R>, + &mut self, + requester: &'a PSU, capability: Option, ) -> Result<(), Error> { - if let Err(e) = device - .state - .lock() - .await - .update_requested_provider_power_capability(capability) { - error!( - "Device{}: Invalid state for notify consumer capability, catching up: {:#?}", - device.id().0, - e, + let mut requester = requester.lock().await; + info!( + "({}): Received request provider capability: {:#?}", + requester.name(), + capability, ); + if let Err(e) = requester + .state_mut() + .update_requested_provider_power_capability(capability) + { + error!( + "({}): Invalid state for notify consumer capability, catching up: {:#?}", + requester.name(), + e, + ); + } } - self.connect_provider(device.id()).await + self.connect_provider(requester).await } - async fn process_notify_disconnect(&self, device: &RegistrationEntry<'_, D, R>) -> Result<(), Error> { - if let Err(e) = device.state.lock().await.disconnect(true) { + async fn process_notify_disconnect(&mut self, device: &'a PSU) -> Result<(), Error> { + let mut locked_device = device.lock().await; + info!("({}): Received notify disconnect", locked_device.name()); + + if let Err(e) = locked_device.state_mut().disconnect(true) { error!( - "Device{}: Invalid state for notify disconnect, catching up: {:#?}", - device.id().0, + "({}): Invalid state for notify disconnect, catching up: {:#?}", + locked_device.name(), e, ); } if self .state - .lock() - .await .current_consumer_state - .is_some_and(|current| current.device_id == device.id()) + .as_ref() + .is_some_and(|current| ptr::eq(current.psu, device)) { - info!("Device{}: Connected consumer disconnected", device.id().0); + info!("({}): Connected consumer disconnected", locked_device.name()); self.disconnect_chargers().await?; - self.comms_notify(CommsMessage { - data: CommsData::ConsumerDisconnected(device.id()), - }) - .await; + self.broadcast_event(ServiceEvent::ConsumerDisconnected(device)).await; } - self.remove_connected_provider(device.id()).await; + self.remove_connected_provider(device).await; self.update_current_consumer().await?; Ok(()) } - /// Send a notification with the comms service - async fn comms_notify(&self, message: CommsMessage) { - self.context.broadcast_message(message).await; - let _ = self - .tp - .send(comms::EndpointID::Internal(comms::Internal::Battery), &message) - .await; + /// Send an event to all registered listeners + async fn broadcast_event(&mut self, _message: ServiceEvent<'a, PSU>) { + // TODO: Add this back as part of the migration away from comms + // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 } /// Common logic for when a provider is disconnected /// /// Returns true if the device was operating as a provider - async fn remove_connected_provider(&self, device_id: DeviceId) -> bool { - if self.state.lock().await.connected_providers.remove(&device_id) { - self.comms_notify(CommsMessage { - data: CommsData::ProviderDisconnected(device_id), - }) - .await; + async fn remove_connected_provider(&mut self, psu: &'a PSU) -> bool { + if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { + self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; true } else { false } } - async fn wait_request(&self) -> Request { - self.context.wait_request().await - } - - async fn process_request(&self, request: Request) -> Result<(), Error> { - let device = self.context.get_psu(request.id)?; - - match request.data { - RequestData::Attached => { - info!("Received notify attached from device {}", device.id().0); + pub async fn process_psu_event(&mut self, event: PsuEvent<'a, PSU>) -> Result<(), Error> { + let device = event.psu; + match event.event { + PsuEventData::Attached => { self.process_notify_attach(device).await; Ok(()) } - RequestData::Detached => { - info!("Received notify detached from device {}", device.id().0); - self.process_notify_detach(device).await - } - RequestData::UpdatedConsumerCapability(capability) => { - info!( - "Device{}: Received notify consumer capability: {:#?}", - device.id().0, - capability, - ); + PsuEventData::Detached => self.process_notify_detach(device).await, + PsuEventData::UpdatedConsumerCapability(capability) => { self.process_notify_consumer_power_capability(device, capability).await } - RequestData::RequestedProviderCapability(capability) => { - info!( - "Device{}: Received request provider capability: {:#?}", - device.id().0, - capability, - ); + PsuEventData::RequestedProviderCapability(capability) => { self.process_request_provider_power_capabilities(device, capability) .await } - RequestData::Disconnected => { - info!("Received notify disconnect from device {}", device.id().0); - self.process_notify_disconnect(device).await - } + PsuEventData::Disconnected => self.process_notify_disconnect(device).await, } } - - /// Top-level event loop function - pub async fn process(&self) -> Result<(), Error> { - let request = self.wait_request().await; - self.process_request(request).await - } -} - -impl + 'static> comms::MailboxDelegate for Service<'_, D, R> where - D::Inner: Psu -{ } diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index 4179d81ba..dd174d157 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -3,10 +3,9 @@ //! the system is in unlimited power state. In this mode up to [provider_unlimited](super::config::Config::provider_unlimited) //! is provided to each device. Above this threshold, the system is in limited power state. //! In this mode [provider_limited](super::config::Config::provider_limited) is provided to each device -use embedded_services::error; -use embedded_services::{debug, event::Receiver, trace}; +use core::ptr; -use power_policy_interface::psu; +use embedded_services::debug; use super::*; @@ -28,51 +27,48 @@ pub(super) struct State { state: PowerState, } -impl + 'static> Service<'_, D, R> +impl<'a, PSU: Lockable> Service<'a, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&self, requester_id: DeviceId) -> Result<(), Error> { - trace!("Device{}: Attempting to connect as provider", requester_id.0); - let requester = self.context.get_psu(requester_id)?; - let requested_power_capability = match requester.requested_provider_capability().await { - Some(cap) => cap, - // Requester is no longer requesting power - _ => { - info!("Device{}: No-longer requesting power", requester.id().0); - return Ok(()); + pub(super) async fn connect_provider(&mut self, requester: &'a PSU) -> Result<(), Error> { + let requested_power_capability = { + let requester = requester.lock().await; + debug!("({}): Attempting to connect as provider", requester.name()); + match requester.state().requested_provider_capability { + Some(cap) => cap, + // Requester is no longer requesting power + _ => { + info!("({}): No-longer requesting power", requester.name()); + return Ok(()); + } } }; - let mut policy_state = self.state.lock().await; - let mut total_power_mw = 0; // Determine total requested power draw - for psu in self - .context - .psu_devices() - .iter_only::>() - { - let target_provider_cap = if psu.id() == requester_id { + let mut total_power_mw = 0; + for psu in self.psu_devices.iter() { + let target_provider_cap = if ptr::eq(*psu, requester) { // Use the requester's requested power capability // this handles both new connections and upgrade requests Some(requested_power_capability) } else { // Use the device's current working provider capability - psu.provider_capability().await + psu.lock().await.state().connected_provider_capability() }; total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); } if total_power_mw > self.config.limited_power_threshold_mw { - policy_state.current_provider_state.state = PowerState::Limited; + self.state.current_provider_state.state = PowerState::Limited; } else { - policy_state.current_provider_state.state = PowerState::Unlimited; + self.state.current_provider_state.state = PowerState::Unlimited; } - debug!("New power state: {:?}", policy_state.current_provider_state.state); + debug!("New power state: {:?}", self.state.current_provider_state.state); - let target_power = match policy_state.current_provider_state.state { + let target_power = match self.state.current_provider_state.state { PowerState::Limited => ProviderPowerCapability { capability: self.config.provider_limited, flags: requested_power_capability.flags, @@ -91,36 +87,26 @@ where } }; - let psu = self.context.get_psu(requester_id)?; - let mut locked_state = psu.state.lock().await; - let mut locked_device = psu.device.lock().await; - - if let e @ Err(_) = locked_state.connect_provider(target_power) { + let mut locked_requester = requester.lock().await; + if let e @ Err(_) = locked_requester.state().can_connect_provider() { error!( - "Device{}: Cannot provide, device is in state {:#?}", - psu.id().0, - locked_state.state() + "({}): Cannot provide, device is in state {:#?}", + locked_requester.name(), + locked_requester.state().psu_state ); e } else { - locked_device.connect_provider(target_power).await?; - self.post_provider_connected(&mut policy_state, requester_id, target_power) - .await; + locked_requester.connect_provider(target_power).await?; + locked_requester.state_mut().connect_provider(target_power)?; + self.post_provider_connected(requester, target_power).await; Ok(()) } } /// Common logic for after a provider has successfully connected - async fn post_provider_connected( - &self, - state: &mut InternalState, - provider_id: DeviceId, - target_power: ProviderPowerCapability, - ) { - let _ = state.connected_providers.insert(provider_id); - self.comms_notify(CommsMessage { - data: CommsData::ProviderConnected(provider_id, target_power), - }) - .await; + async fn post_provider_connected(&mut self, requester: &'a PSU, target_power: ProviderPowerCapability) { + let _ = self.state.connected_providers.insert(requester as *const PSU as usize); + self.broadcast_event(ServiceEvent::ProviderConnected(requester, target_power)) + .await; } } diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index 20f7aef9d..d394957d9 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,35 +1,30 @@ -use embedded_services::{comms, error, event::Receiver, info, sync::Lockable}; +use embedded_services::{error, info, sync::Lockable}; -use power_policy_interface::psu::{Psu, event::RequestData}; +use embedded_services::event::Receiver; +use power_policy_interface::psu::Psu; +use power_policy_interface::psu::event::EventData; use super::Service; -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InitError { - /// Comms registration failed - RegistrationFailed, - /// Power device registration failed - PowerDeviceRegistrationFailed, - /// Charger device registration failed - ChargerDeviceRegistrationFailed, -} - /// Runs the power policy task. -pub async fn task + 'static>( - policy: &'static Service<'static, D, R>, -) -> Result +pub async fn task< + 'a, + const PSU_COUNT: usize, + S: Lockable>, + PSU: Lockable, + R: Receiver, +>( + mut psu_events: crate::psu::EventReceivers<'a, PSU_COUNT, PSU, R>, + policy: &'a S, +) -> ! where - D::Inner: Psu, + PSU::Inner: Psu, { info!("Starting power policy task"); - if comms::register_endpoint(policy, &policy.tp).await.is_err() { - error!("Failed to register power policy endpoint"); - return Err(InitError::RegistrationFailed); - } - loop { - if let Err(e) = policy.process().await { + let event = psu_events.wait_event().await; + + if let Err(e) = policy.lock().await.process_psu_event(event).await { error!("Error processing request: {:?}", e); } } diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index 0244229c1..f117b73ba 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,9 +1,9 @@ #![allow(clippy::unwrap_used)] use embassy_sync::signal::Signal; -use embedded_services::{GlobalRawMutex, event, info}; +use embedded_services::{GlobalRawMutex, event::Sender, info}; use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, - psu::{Error, InternalState, Psu, event::RequestData}, + psu::{Error, Psu, State, event::EventData}, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -15,16 +15,18 @@ pub enum FnCall { Reset, } -pub struct Mock<'a, S: event::Sender> { +pub struct Mock<'a, S: Sender> { sender: S, fn_call: &'a Signal, // Internal state - pub state: InternalState, + pub state: State, + name: &'static str, } -impl<'a, S: event::Sender> Mock<'a, S> { - pub fn new(sender: S, fn_call: &'a Signal) -> Self { +impl<'a, S: Sender> Mock<'a, S> { + pub fn new(name: &'static str, sender: S, fn_call: &'a Signal) -> Self { Self { + name, sender, fn_call, state: Default::default(), @@ -41,27 +43,21 @@ impl<'a, S: event::Sender> Mock<'a, S> { } pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { - self.state.attach().unwrap(); - - self.sender.send(RequestData::Attached).await; + self.sender.send(EventData::Attached).await; let capability = Some(ConsumerPowerCapability { capability, flags: ConsumerFlags::none(), }); - self.state.update_consumer_power_capability(capability).unwrap(); - self.sender - .send(RequestData::UpdatedConsumerCapability(capability)) - .await; + self.sender.send(EventData::UpdatedConsumerCapability(capability)).await; } pub async fn simulate_detach(&mut self) { - self.state.detach(); - self.sender.send(RequestData::Detached).await; + self.sender.send(EventData::Detached).await; } } -impl<'a, S: event::Sender> Psu for Mock<'a, S> { +impl<'a, S: Sender> Psu for Mock<'a, S> { async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { info!("Connect consumer {:#?}", capability); self.record_fn_call(FnCall::ConnectConsumer(capability)); @@ -79,4 +75,16 @@ impl<'a, S: event::Sender> Psu for Mock<'a, S> { self.record_fn_call(FnCall::Disconnect); Ok(()) } + + fn state(&self) -> &State { + &self.state + } + + fn state_mut(&mut self) -> &mut State { + &mut self.state + } + + fn name(&self) -> &'static str { + self.name + } } diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 3707dc196..5ec320589 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -6,20 +6,19 @@ use embassy_futures::{ use embassy_sync::{ channel::{Channel, DynamicReceiver, DynamicSender}, mutex::Mutex, + once_lock::OnceLock, signal::Signal, }; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; use power_policy_interface::capability::PowerCapability; -use power_policy_interface::psu; -use power_policy_interface::psu::DeviceId; -use power_policy_interface::psu::event::RequestData; +use power_policy_interface::psu::event::EventData; +use power_policy_service::psu::EventReceivers; use power_policy_service::service::Service; pub mod mock; use mock::Mock; -use static_cell::StaticCell; use crate::common::mock::FnCall; @@ -38,99 +37,97 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); const EVENT_CHANNEL_SIZE: usize = 4; -async fn power_policy_task( - completion_signal: &'static Signal, - power_policy: &'static Service< - 'static, - Mutex>>, - DynamicReceiver<'static, RequestData>, - >, +pub type DeviceType<'a> = Mutex>>; +pub type ServiceType<'a> = Service<'a, DeviceType<'a>>; + +async fn power_policy_task<'a, const N: usize>( + completion_signal: &'a Signal, + mut power_policy: ServiceType<'a>, + mut event_receivers: EventReceivers<'a, N, DeviceType<'a>, DynamicReceiver<'a, EventData>>, ) { - while let Either::First(result) = select(power_policy.process(), completion_signal.wait()).await { - result.unwrap(); + while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { + power_policy.process_psu_event(result).await.unwrap(); } } -pub type RegistrationType = psu::RegistrationEntry< - 'static, - Mutex>>, - DynamicReceiver<'static, RequestData>, ->; - -pub type ServiceType = Service< - 'static, - Mutex>>, - DynamicReceiver<'static, RequestData>, ->; - -pub type ServiceContext = power_policy_service::service::context::Context< - Mutex>>, - DynamicReceiver<'static, RequestData>, ->; - -pub async fn run_test>( - timeout: Duration, - test: impl FnOnce( - &'static Mutex>>, - &'static Signal, - &'static Mutex>>, - &'static Signal, - ) -> F, -) { - env_logger::builder().filter_level(log::LevelFilter::Trace).init(); +/// This trait is a workaround for Rust's current limitations on closures returning a generic future. +/// +/// The trait we want to express for `run_test` is something like: +/// ``` +/// for<'a> F: FnOnce( +/// &'a Mutex>>, +/// &'a Signal, +/// &'a Mutex>>, +/// &'a Signal +/// ) -> impl (Future + 'a) +/// ``` +/// However, `impl (Future + 'a)` is not real syntax. This could be done with the unstable feature type_alias_impl_trait, +/// but we use this helper trait so as to not require use of nightly. +pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a>: + FnOnce(Arg0, Arg1, Arg2, Arg3) -> Self::Fut +{ + type Fut: Future; +} + +impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3> for F +where + F: FnOnce(Arg0, Arg1, Arg2, Arg3) -> Fut, + Fut: Future, +{ + type Fut = Fut; +} + +pub async fn run_test(timeout: Duration, test: F) +where + for<'a> F: TestArgsFnOnce< + 'a, + &'a Mutex>>, + &'a Signal, + &'a Mutex>>, + &'a Signal, + >, +{ + // 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; - static DEVICE0_EVENT_CHANNEL: StaticCell> = - StaticCell::new(); - let device0_event_channel = DEVICE0_EVENT_CHANNEL.init(Channel::new()); + let device0_signal = Signal::new(); + let device0_event_channel: Channel = Channel::new(); let device0_sender = device0_event_channel.dyn_sender(); let device0_receiver = device0_event_channel.dyn_receiver(); + let device0 = Mutex::new(Mock::new("PSU0", device0_sender, &device0_signal)); - static DEVICE0_SIGNAL: StaticCell> = StaticCell::new(); - let device0_signal = DEVICE0_SIGNAL.init(Signal::new()); - static DEVICE0: StaticCell>>> = StaticCell::new(); - let device0 = DEVICE0.init(Mutex::new(Mock::new(device0_sender, device0_signal))); - - static DEVICE0_REGISTRATION: StaticCell = StaticCell::new(); - let device0_registration = - DEVICE0_REGISTRATION.init(psu::RegistrationEntry::new(DeviceId(0), device0, device0_receiver)); - - static DEVICE1_EVENT_CHANNEL: StaticCell> = - StaticCell::new(); - let device1_event_channel = DEVICE1_EVENT_CHANNEL.init(Channel::new()); + let device1_signal = Signal::new(); + let device1_event_channel: Channel = Channel::new(); let device1_sender = device1_event_channel.dyn_sender(); let device1_receiver = device1_event_channel.dyn_receiver(); + let device1 = Mutex::new(Mock::new("PSU1", device1_sender, &device1_signal)); - static DEVICE1_SIGNAL: StaticCell> = StaticCell::new(); - let device1_signal = DEVICE1_SIGNAL.init(Signal::new()); - static DEVICE1: StaticCell>>> = StaticCell::new(); - let device1 = DEVICE1.init(Mutex::new(Mock::new(device1_sender, device1_signal))); - - static DEVICE1_REGISTRATION: StaticCell = StaticCell::new(); - let device1_registration = - DEVICE1_REGISTRATION.init(psu::RegistrationEntry::new(DeviceId(1), device1, device1_receiver)); - - static SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - - service_context.register_psu(device0_registration).unwrap(); - service_context.register_psu(device1_registration).unwrap(); - - static POWER_POLICY: StaticCell = StaticCell::new(); - let power_policy = POWER_POLICY.init(power_policy_service::service::Service::new( - service_context, - Default::default(), - )); + let service_context = power_policy_service::service::context::Context::new(); + let psu_registration = [&device0, &device1]; + let completion_signal = Signal::new(); - static COMPLETION_SIGNAL: StaticCell> = StaticCell::new(); - let completion_signal = COMPLETION_SIGNAL.init(Signal::new()); + let power_policy = + power_policy_service::service::Service::new(psu_registration.as_slice(), &service_context, Default::default()); with_timeout( timeout, - join(power_policy_task(completion_signal, power_policy), async { - test(device0, device0_signal, device1, device1_signal).await; - completion_signal.signal(()); - }), + join( + power_policy_task( + &completion_signal, + power_policy, + EventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), + ), + async { + test(&device0, &device0_signal, &device1, &device1_signal).await; + completion_signal.signal(()); + }, + ), ) .await .unwrap(); diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 91c4950eb..94feac601 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -2,12 +2,13 @@ use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::GlobalRawMutex; +use embedded_services::info; use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability}; -use power_policy_interface::psu::event::RequestData; mod common; use common::LOW_POWER; +use power_policy_interface::psu::event::EventData; use crate::common::{ DEFAULT_TIMEOUT, HIGH_POWER, @@ -19,9 +20,12 @@ const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic consumer flow with a single device. async fn test_single( - device0: &'static Mutex>>, - device0_signal: &'static Signal, + device0: &Mutex>>, + device0_signal: &Signal, + _device1: &Mutex>>, + _device1_signal: &Signal, ) { + info!("Running test_single"); // Test initial connection { device0.lock().await.simulate_consumer_connection(LOW_POWER).await; @@ -53,11 +57,12 @@ async fn test_single( /// Test swapping to a higher powered device. async fn test_swap_higher( - device0: &'static Mutex>>, - device0_signal: &'static Signal, - device1: &'static Mutex>>, - device1_signal: &'static Signal, + device0: &Mutex>>, + device0_signal: &Signal, + device1: &Mutex>>, + device1_signal: &Signal, ) { + info!("Running test_swap_higher"); // Device0 connection at low power { device0.lock().await.simulate_consumer_connection(LOW_POWER).await; @@ -120,15 +125,13 @@ async fn test_swap_higher( } } -/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. #[tokio::test] async fn run_all_tests() { - run_test( - DEFAULT_TIMEOUT, - |device0, device0_signal, device1, device1_signal| async move { - test_single(device0, device0_signal).await; - test_swap_higher(device0, device0_signal, device1, device1_signal).await; - }, - ) - .await; + run_test(DEFAULT_TIMEOUT, test_swap_higher).await; +} + +/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. +#[tokio::test] +async fn run_test_single() { + run_test(DEFAULT_TIMEOUT, test_single).await; } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 1e5ba66bb..8991824d9 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -254,11 +254,21 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "0.2.18" +[[audits.jiff]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.20" + [[audits.jiff-static]] who = "Robert Zieba " criteria = "safe-to-run" version = "0.2.18" +[[audits.jiff-static]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.20" + [[audits.libc]] who = "Robert Zieba " criteria = "safe-to-run" @@ -361,6 +371,11 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "0.2.4" +[[audits.portable-atomic-util]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.5" + [[audits.proc-macro-error]] who = "Jerry Xie " criteria = "safe-to-deploy" diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 5b3fb5bd0..812ed8972 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -28,7 +28,6 @@ log = { workspace = true, optional = true } tps6699x = { workspace = true, features = ["embassy"] } cfu-service.workspace = true power-policy-interface.workspace = true -power-policy-service = { path = "../power-policy-service" } bitvec.workspace = true [dev-dependencies] @@ -51,7 +50,6 @@ defmt = [ "embedded-usb-pd/defmt", "cfu-service/defmt", "power-policy-interface/defmt", - "power-policy-service/defmt", ] log = [ "dep:log", @@ -61,5 +59,4 @@ log = [ "tps6699x/log", "cfu-service/log", "power-policy-interface/log", - "power-policy-service/log", ] diff --git a/type-c-service/src/service/controller.rs b/type-c-service/src/service/controller.rs index 408fc2efc..74debf8c0 100644 --- a/type-c-service/src/service/controller.rs +++ b/type-c-service/src/service/controller.rs @@ -6,7 +6,10 @@ use crate::type_c::{ external::{self, ControllerCommandData}, }; -impl<'a> Service<'a> { +impl<'a, PSU: Lockable> Service<'a, PSU> +where + PSU::Inner: psu::Psu, +{ /// Process external controller status command pub(super) async fn process_external_controller_status( &self, diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 449e695c2..68124cc56 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -3,11 +3,10 @@ use embassy_sync::{ mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber}, }; -use embedded_services::{ - GlobalRawMutex, debug, error, event::Receiver, info, intrusive_list, ipc::deferred, sync::Lockable, trace, -}; +use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, ipc::deferred, sync::Lockable, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; +use power_policy_interface::psu; use crate::type_c::{ self, Cached, comms, @@ -43,7 +42,10 @@ struct State { /// /// Constructing a Service is the first step in using the Type-C service. /// Arguments should be an initialized context -pub struct Service<'a> { +pub struct Service<'a, PSU: Lockable> +where + PSU::Inner: psu::Psu, +{ /// Type-C context context: &'a type_c::controller::Context, /// Controller intrusive list @@ -56,14 +58,14 @@ pub struct Service<'a> { /// /// This is the corresponding publisher to [`Self::power_policy_event_subscriber`], power policy events /// will be buffered in the channel until they are brought into the event loop with the subscriber. - power_policy_event_publisher: - embedded_services::broadcaster::immediate::Receiver<'a, power_policy_interface::service::event::CommsMessage>, + _power_policy_event_publisher: + embedded_services::broadcaster::immediate::Receiver<'a, power_policy_interface::service::event::Event<'a, PSU>>, /// Power policy event subscriber /// /// This is the corresponding subscriber to [`Self::power_policy_event_publisher`], needs to be a mutex because getting a message /// from the channel requires mutable access. power_policy_event_subscriber: - Mutex>, + Mutex>>, } /// Power policy events @@ -91,20 +93,23 @@ pub enum Event<'a> { PowerPolicy(PowerPolicyEvent), } -impl<'a> Service<'a> { +impl<'a, PSU: Lockable> Service<'a, PSU> +where + PSU::Inner: psu::Psu, +{ /// Create a new service the given configuration pub fn create( config: config::Config, context: &'a crate::type_c::controller::Context, controller_list: &'a intrusive_list::IntrusiveList, - power_policy_publisher: DynImmediatePublisher<'a, power_policy_interface::service::event::CommsMessage>, - power_policy_subscriber: DynSubscriber<'a, power_policy_interface::service::event::CommsMessage>, + power_policy_publisher: DynImmediatePublisher<'a, power_policy_interface::service::event::Event<'a, PSU>>, + power_policy_subscriber: DynSubscriber<'a, power_policy_interface::service::event::Event<'a, PSU>>, ) -> Self { Self { context, state: Mutex::new(State::default()), config, - power_policy_event_publisher: power_policy_publisher.into(), + _power_policy_event_publisher: power_policy_publisher.into(), power_policy_event_subscriber: Mutex::new(power_policy_subscriber), controllers: controller_list, } @@ -251,20 +256,6 @@ impl<'a> Service<'a> { self.process_event(event).await } - /// Register the Type-C service with the power policy service - pub fn register_comms< - PD: Lockable + 'static, - PR: Receiver + 'static, - >( - &'static self, - power_policy_context: &power_policy_service::service::context::Context, - ) -> Result<(), intrusive_list::Error> - where - PD::Inner: power_policy_interface::psu::Psu, - { - power_policy_context.register_message_receiver(&self.power_policy_event_publisher) - } - pub(crate) fn controllers(&self) -> &'a intrusive_list::IntrusiveList { self.controllers } diff --git a/type-c-service/src/service/pd.rs b/type-c-service/src/service/pd.rs index 5076953f4..2070dbbc7 100644 --- a/type-c-service/src/service/pd.rs +++ b/type-c-service/src/service/pd.rs @@ -1,11 +1,15 @@ //! Power Delivery (PD) related functionality. -use embedded_services::intrusive_list; +use embedded_services::{intrusive_list, sync::Lockable}; use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; +use power_policy_interface::psu; use super::Service; -impl Service<'_> { +impl<'a, PSU: Lockable> Service<'a, PSU> +where + PSU::Inner: psu::Psu, +{ /// Get the oldest unhandled PD alert for the given port. /// /// Returns [`None`] if no alerts are pending. diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs index b138e82b7..ccf1bdc4f 100644 --- a/type-c-service/src/service/port.rs +++ b/type-c-service/src/service/port.rs @@ -10,7 +10,10 @@ use crate::type_c::{ external, }; -impl<'a> Service<'a> { +impl<'a, PSU: Lockable> Service<'a, PSU> +where + PSU::Inner: psu::Psu, +{ /// Wait for port flags pub(super) async fn wait_port_flags(&self) -> PortEventStreamer { if let Some(ref streamer) = self.state.lock().await.port_event_streaming_state { diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index 72e7a6c01..ba16485e9 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -3,7 +3,10 @@ use power_policy_interface::service as power_policy; use super::*; -impl<'a> Service<'a> { +impl<'a, PSU: Lockable> Service<'a, PSU> +where + PSU::Inner: psu::Psu, +{ /// Wait for a power policy event pub(super) async fn wait_power_policy_event(&self) -> Event<'_> { loop { @@ -12,14 +15,14 @@ impl<'a> Service<'a> { // Missed some messages, all we can do is log an error error!("Power policy {} event(s) lagged", lagged); } - WaitResult::Message(message) => match message.data { - power_policy_interface::service::event::CommsData::Unconstrained(state) => { + WaitResult::Message(message) => match message { + power_policy_interface::service::event::Event::Unconstrained(state) => { return Event::PowerPolicy(PowerPolicyEvent::Unconstrained(state)); } - power_policy_interface::service::event::CommsData::ConsumerDisconnected(_) => { + power_policy_interface::service::event::Event::ConsumerDisconnected(_) => { return Event::PowerPolicy(PowerPolicyEvent::ConsumerDisconnected); } - power_policy_interface::service::event::CommsData::ConsumerConnected(_, _) => { + power_policy_interface::service::event::Event::ConsumerConnected(_, _) => { return Event::PowerPolicy(PowerPolicyEvent::ConsumerConnected); } _ => { diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index 53bf886e4..c07c90ce3 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -28,7 +28,10 @@ pub(super) struct State { pub(super) psu_connected: bool, } -impl<'a> Service<'a> { +impl<'a, PSU: Lockable> Service<'a, PSU> +where + PSU::Inner: psu::Psu, +{ /// PPM reset implementation fn process_ppm_reset(&self, state: &mut State) { debug!("Resetting PPM"); diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index adc46f4ec..ca4770e52 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,12 +1,16 @@ //! VDM (Vendor Defined Messages) related functionality. use crate::type_c::controller::{AttnVdm, OtherVdm}; -use embedded_services::intrusive_list; +use embedded_services::{intrusive_list, sync::Lockable}; use embedded_usb_pd::{GlobalPortId, PdError}; +use power_policy_interface::psu; use super::Service; -impl Service<'_> { +impl Service<'_, PSU> +where + PSU::Inner: psu::Psu, +{ /// Get the other vdm for the given port pub async fn get_other_vdm( &self, diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index aea7981f5..afd52d886 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,41 +1,41 @@ use core::future::Future; -use embassy_sync::mutex::Mutex; use embedded_services::{error, event, info, sync::Lockable}; -use power_policy_service::service::context::Context as PowerPolicyContext; +use power_policy_interface::psu; -use crate::{ - service::Service, - wrapper::{ControllerWrapper, proxy::PowerProxyDevice}, -}; +use crate::{service::Service, wrapper::ControllerWrapper}; /// Task to run the Type-C service, takes a closure to customize the event loop -pub async fn task_closure<'a, M, D, S, R, V, Fut: Future, F: Fn(&'a Service) -> Fut, const N: usize>( - service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], - power_policy_context: &PowerPolicyContext>, R>, +pub async fn task_closure< + 'a, + M, + D, + PSU: Lockable, + S, + V, + Fut: Future, + F: Fn(&'a Service<'a, PSU>) -> Fut, + const N: usize, +>( + service: &'static Service<'a, PSU>, + wrappers: [&'a ControllerWrapper<'a, M, D, S, V>; N], cfu_client: &'a cfu_service::CfuClient, f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + PSU::Inner: psu::Psu, + S: event::Sender, V: crate::wrapper::FwOfferValidator, D::Inner: crate::type_c::controller::Controller, { info!("Starting type-c task"); - if service.register_comms(power_policy_context).is_err() { - error!("Failed to register type-c service endpoint"); - return; - } + // TODO: move this service to use the new power policy event subscribers and receivers + // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 for controller_wrapper in wrappers { - if controller_wrapper - .register(service.controllers(), power_policy_context, cfu_client) - .is_err() - { + if controller_wrapper.register(service.controllers(), cfu_client).is_err() { error!("Failed to register a controller"); return; } @@ -47,29 +47,22 @@ pub async fn task_closure<'a, M, D, S, R, V, Fut: Future, F: Fn(&'a } /// Task to run the Type-C service, running the default event loop -pub async fn task<'a, M, D, S, R, V, const N: usize>( - service: &'static Service<'a>, - wrappers: [&'a ControllerWrapper<'a, M, D, S, R, V>; N], - power_policy_context: &PowerPolicyContext>, R>, +pub async fn task<'a, M, D, PSU: Lockable, S, V, const N: usize>( + service: &'static Service<'a, PSU>, + wrappers: [&'a ControllerWrapper<'a, M, D, S, V>; N], cfu_client: &'a cfu_service::CfuClient, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: embedded_services::sync::Lockable, - S: event::Sender, - R: event::Receiver, + PSU::Inner: psu::Psu, + S: event::Sender, V: crate::wrapper::FwOfferValidator, ::Inner: crate::type_c::controller::Controller, { - task_closure( - service, - wrappers, - power_policy_context, - cfu_client, - |service: &Service| async { - if let Err(e) = service.process_next_event().await { - error!("Type-C service processing error: {:#?}", e); - } - }, - ) + task_closure(service, wrappers, cfu_client, |service: &Service<'_, PSU>| async { + if let Err(e) = service.process_next_event().await { + error!("Type-C service processing error: {:#?}", e); + } + }) .await; } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index ceed5aae9..68c54916d 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -1,10 +1,8 @@ //! Various types of state and objects required for [`crate::wrapper::ControllerWrapper`]. //! -//! TODO: Update as part of type-C refactoring -use core::{ - array::from_fn, - cell::{RefCell, RefMut}, -}; +//! TODO: update this documentation when the type-C service is refactored +//! +use core::array::from_fn; use cfu_service::component::CfuDevice; use embassy_sync::{ @@ -12,6 +10,7 @@ use embassy_sync::{ mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}, }; + use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::event; @@ -22,7 +21,6 @@ use crate::type_c::{ controller::PortStatus, event::{PortEvent, PortStatusChanged}, }; -use power_policy_interface::psu::DeviceId; use crate::{ PortEventStreamer, @@ -32,22 +30,6 @@ use crate::{ }, }; -/// Per-port state -pub struct PortState<'a> { - /// Cached port status - pub(crate) status: PortStatus, - /// Software status event - pub(crate) sw_status_event: PortStatusChanged, - /// Sink ready deadline instant - pub(crate) sink_ready_deadline: Option, - /// Pending events for the type-C service - pub(crate) pending_events: PortEvent, - /// PD alert channel for this port - // There's no direct immediate equivalent of a channel. PubSubChannel has immediate publisher behavior - // so we use that, but this requires us to keep separate publisher and subscriber objects. - pub(crate) pd_alerts: (DynImmediatePublisher<'a, Ado>, DynSubscriber<'a, Ado>), -} - /// Internal per-controller state #[derive(Copy, Clone)] pub struct ControllerState { @@ -66,97 +48,15 @@ impl Default for ControllerState { } } -/// Internal state containing all per-port and per-controller state -struct InternalState<'a, const N: usize, S: event::Sender> { - controller_state: ControllerState, - port_states: [PortState<'a>; N], - port_power: [PortPower; N], -} - -impl<'a, const N: usize, S: event::Sender> InternalState<'a, N, S> { - fn try_new(storage: &'a Storage, power_events: [S; N]) -> Option { - let port_states = storage.pd_alerts.each_ref().map(|pd_alert| { - Some(PortState { - status: PortStatus::new(), - sw_status_event: PortStatusChanged::none(), - sink_ready_deadline: None, - pending_events: PortEvent::none(), - pd_alerts: (pd_alert.dyn_immediate_publisher(), pd_alert.dyn_subscriber().ok()?), - }) - }); - - if port_states.iter().any(|s| s.is_none()) { - return None; - } - - Some(Self { - controller_state: ControllerState::default(), - // Panic safety: All array elements checked above - #[allow(clippy::unwrap_used)] - port_states: port_states.map(|s| s.unwrap()), - port_power: power_events.map(|sender| PortPower { - sender, - state: Default::default(), - }), - }) - } -} - -impl<'a, const N: usize, S: event::Sender> DynPortState<'a, S> - for InternalState<'a, N, S> -{ - fn num_ports(&self) -> usize { - self.port_states.len() - } - - fn port_states(&self) -> &[PortState<'a>] { - &self.port_states - } - - fn port_states_mut(&mut self) -> &mut [PortState<'a>] { - &mut self.port_states - } - - fn controller_state(&self) -> &ControllerState { - &self.controller_state - } - - fn controller_state_mut(&mut self) -> &mut ControllerState { - &mut self.controller_state - } - - fn port_power(&self) -> &[PortPower] { - &self.port_power - } - - fn port_power_mut(&mut self) -> &mut [PortPower] { - &mut self.port_power - } -} - -/// Trait to erase the generic port count argument -pub trait DynPortState<'a, S: event::Sender> { - fn num_ports(&self) -> usize; - - fn port_states(&self) -> &[PortState<'a>]; - fn port_states_mut(&mut self) -> &mut [PortState<'a>]; - - fn controller_state(&self) -> &ControllerState; - fn controller_state_mut(&mut self) -> &mut ControllerState; - - fn port_power(&self) -> &[PortPower]; - fn port_power_mut(&mut self) -> &mut [PortPower]; -} - /// Service registration objects -pub struct Registration<'a, M: RawMutex, R: event::Receiver> { +pub struct Registration<'a, M: RawMutex> { pub context: &'a crate::type_c::controller::Context, pub pd_controller: &'a crate::type_c::controller::Device<'a>, pub cfu_device: &'a CfuDevice, - pub power_devices: &'a [power_policy_interface::psu::RegistrationEntry<'a, Mutex>, R>], + pub power_devices: &'a [&'a Mutex>], } -impl<'a, M: RawMutex, R: event::Receiver> Registration<'a, M, R> { +impl<'a, M: RawMutex> Registration<'a, M> { pub fn num_ports(&self) -> usize { self.power_devices.len() } @@ -165,11 +65,6 @@ impl<'a, M: RawMutex, R: event::Receiver> { - pub sender: S, - pub state: power_policy_interface::psu::InternalState, -} - /// Base storage pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related @@ -201,26 +96,84 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { } /// Create intermediate storage from this storage - pub fn try_create_intermediate(&self) -> Option> { - IntermediateStorage::try_from_storage(self) + pub fn try_create_intermediate>( + &self, + power_policy_init: [(&'static str, S); N], + ) -> Option> { + IntermediateStorage::try_from_storage(self, power_policy_init) + } +} + +pub struct Port<'a, M: RawMutex, S: event::Sender> { + pub proxy: Mutex>, + pub state: Mutex>, +} + +pub struct PortState<'a, S: event::Sender> { + /// Cached port status + pub(crate) status: PortStatus, + /// Software status event + pub(crate) sw_status_event: PortStatusChanged, + /// Sink ready deadline instant + pub(crate) sink_ready_deadline: Option, + /// Pending events for the type-C service + pub(crate) pending_events: PortEvent, + /// PD alert channel for this port + // There's no direct immediate equivalent of a channel. PubSubChannel has immediate publisher behavior + // so we use that, but this requires us to keep separate publisher and subscriber objects. + pub(crate) pd_alerts: (DynImmediatePublisher<'a, Ado>, DynSubscriber<'a, Ado>), + /// Sender to send events to the power policy service + pub(crate) power_policy_sender: S, +} + +impl<'a, S: event::Sender> PortState<'a, S> { + pub fn new(pd_alerts: (DynImmediatePublisher<'a, Ado>, DynSubscriber<'a, Ado>), power_policy_sender: S) -> Self { + Self { + status: PortStatus::default(), + sw_status_event: PortStatusChanged::default(), + sink_ready_deadline: None, + pending_events: PortEvent::default(), + pd_alerts, + power_policy_sender, + } } } /// Intermediate storage that holds power proxy devices -pub struct IntermediateStorage<'a, const N: usize, M: RawMutex> { +pub struct IntermediateStorage< + 'a, + const N: usize, + M: RawMutex, + S: event::Sender, +> { storage: &'a Storage<'a, N, M>, - power_proxy_devices: [Mutex>; N], + ports: [Port<'a, M, S>; N], power_proxy_receivers: [Mutex>; N], } -impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { - fn try_from_storage(storage: &'a Storage<'a, N, M>) -> Option { - let mut power_proxy_devices = heapless::Vec::<_, N>::new(); +impl<'a, const N: usize, M: RawMutex, S: event::Sender> + IntermediateStorage<'a, N, M, S> +{ + fn try_from_storage(storage: &'a Storage<'a, N, M>, power_policy_init: [(&'static str, S); N]) -> Option { + let mut ports = heapless::Vec::<_, N>::new(); let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); - for power_proxy_channel in storage.power_proxy_channels.iter() { - power_proxy_devices - .push(Mutex::new(power_proxy_channel.get_device())) + for ((power_proxy_channel, pd_alert), (name, policy_sender)) in storage + .power_proxy_channels + .iter() + .zip(storage.pd_alerts.iter()) + .zip(power_policy_init.into_iter()) + { + let (device_sender, device_receiver) = power_proxy_channel.get_device_components(); + + ports + .push(Port { + proxy: Mutex::new(PowerProxyDevice::new(name, device_sender, device_receiver)), + state: Mutex::new(PortState::new( + (pd_alert.dyn_immediate_publisher(), pd_alert.dyn_subscriber().ok()?), + policy_sender, + )), + }) .ok()?; power_proxy_receivers .push(Mutex::new(power_proxy_channel.get_receiver())) @@ -229,24 +182,17 @@ impl<'a, const N: usize, M: RawMutex> IntermediateStorage<'a, N, M> { Some(Self { storage, - power_proxy_devices: power_proxy_devices.into_array().ok()?, + ports: ports.into_array().ok()?, power_proxy_receivers: power_proxy_receivers.into_array().ok()?, }) } /// Create referenced storage from this intermediate storage - pub fn try_create_referenced< - 'b, - S: event::Sender, - R: event::Receiver, - >( - &'b self, - policy_args: [(DeviceId, S, R); N], - ) -> Option> + pub fn try_create_referenced<'b>(&'b self) -> Option> where 'b: 'a, { - ReferencedStorage::try_from_intermediate(self, policy_args) + ReferencedStorage::try_from_intermediate(self) } } @@ -258,83 +204,51 @@ pub struct ReferencedStorage< 'a, const N: usize, M: RawMutex, - S: event::Sender, - R: event::Receiver, + S: event::Sender, > { - intermediate: &'a IntermediateStorage<'a, N, M>, - state: RefCell>, + intermediate: &'a IntermediateStorage<'a, N, M, S>, pd_controller: crate::type_c::controller::Device<'a>, - power_devices: [power_policy_interface::psu::RegistrationEntry<'a, Mutex>, R>; N], + power_devices: [&'a Mutex>; N], } -impl< - 'a, - const N: usize, - M: RawMutex, - S: event::Sender, - R: event::Receiver, -> ReferencedStorage<'a, N, M, S, R> +impl<'a, const N: usize, M: RawMutex, S: event::Sender> + ReferencedStorage<'a, N, M, S> { /// Create a new referenced storage from the given intermediate storage - fn try_from_intermediate( - intermediate: &'a IntermediateStorage<'a, N, M>, - policy_args: [(DeviceId, S, R); N], - ) -> Option { - let mut power_senders = heapless::Vec::<_, N>::new(); - let mut power_devices = heapless::Vec::<_, N>::new(); - - for (i, (device_id, policy_sender, policy_receiver)) in policy_args.into_iter().enumerate() { - power_senders.push(policy_sender).ok()?; - power_devices - .push(power_policy_interface::psu::RegistrationEntry::new( - device_id, - intermediate.power_proxy_devices.get(i)?, - policy_receiver, - )) - .ok()?; - } - + fn try_from_intermediate(intermediate: &'a IntermediateStorage<'a, N, M, S>) -> Option { Some(Self { intermediate, - state: RefCell::new(InternalState::try_new( - intermediate.storage, - // Safe because both have N elements - power_senders.into_array().ok()?, - )?), pd_controller: crate::type_c::controller::Device::new( intermediate.storage.controller_id, intermediate.storage.pd_ports.as_slice(), ), - power_devices: power_devices.into_array().ok()?, + // Panic safety: will not panic because array length is fixed by generic argument + #[allow(clippy::indexing_slicing)] + power_devices: from_fn(|i| &intermediate.ports[i].proxy), }) } /// Creates the backing, returns `None` if a backing has already been created - pub fn create_backing<'b>(&'b self) -> Option> + pub fn create_backing<'b>(&'b self) -> Backing<'b, M, S> where 'b: 'a, { - self.state.try_borrow_mut().ok().map(|state| Backing:: { + Backing { registration: Registration { context: self.intermediate.storage.context, pd_controller: &self.pd_controller, cfu_device: &self.intermediate.storage.cfu_device, power_devices: &self.power_devices, }, - state, + ports: &self.intermediate.ports, power_receivers: &self.intermediate.power_proxy_receivers, - }) + } } } /// Wrapper around registration and type-erased state -pub struct Backing< - 'a, - M: RawMutex, - S: event::Sender, - R: event::Receiver, -> { - pub(crate) registration: Registration<'a, M, R>, - pub(crate) state: RefMut<'a, dyn DynPortState<'a, S>>, +pub struct Backing<'a, M: RawMutex, S: event::Sender> { + pub(crate) registration: Registration<'a, M>, + pub(crate) ports: &'a [Port<'a, M, S>], pub(crate) power_receivers: &'a [Mutex>], } diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index d82f0c38e..beb786e90 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,6 +1,7 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation use crate::type_c::controller::Controller; +use crate::wrapper::backing::ControllerState; use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; @@ -32,10 +33,9 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, R, V> +> ControllerWrapper<'device, M, D, S, V> where D::Inner: Controller, { @@ -103,22 +103,22 @@ where async fn process_abort_update( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, ) -> InternalResponseData { // abort the update process match controller.abort_fw_update().await { Ok(_) => { debug!("FW update aborted successfully"); - state.controller_state_mut().fw_update_state = FwUpdateState::Idle; + controller_state.fw_update_state = FwUpdateState::Idle; } Err(Error::Pd(e)) => { error!("Failed to abort FW update: {:?}", e); - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; } Err(Error::Bus(_)) => { error!("Failed to abort FW update, bus error"); - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; } } @@ -128,8 +128,8 @@ where /// Process a GiveContent command async fn process_give_content( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, content: &FwUpdateContentCommand, ) -> InternalResponseData { let data = if let Some(data) = content.data.get(0..content.header.data_length as usize) { @@ -147,13 +147,16 @@ where // Detach from the power policy so it doesn't attempt to do anything while we are updating let controller_id = self.registration.pd_controller.id(); - for power in state.port_power_mut() { + for port in self.ports { + let psu_state = port.proxy.lock().await.psu_state.psu_state; info!("Controller{}: checking power device", controller_id.0); - if power.state.state() != power_policy_interface::psu::State::Detached { + if psu_state != power_policy_interface::psu::PsuState::Detached { info!("Controller{}: Detaching power device", controller_id.0); - power - .sender - .send(power_policy_interface::psu::event::RequestData::Detached) + port.state + .lock() + .await + .power_policy_sender + .send(power_policy_interface::psu::event::EventData::Detached) .await; } } @@ -166,7 +169,7 @@ where } Err(Error::Pd(e)) => { error!("Failed to start FW update: {:?}", e); - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( content.header.sequence_num, CfuUpdateContentResponseStatus::ErrorPrepare, @@ -174,7 +177,7 @@ where } Err(Error::Bus(_)) => { error!("Failed to start FW update, bus error"); - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( content.header.sequence_num, CfuUpdateContentResponseStatus::ErrorPrepare, @@ -182,7 +185,7 @@ where } } - state.controller_state_mut().fw_update_state = FwUpdateState::InProgress(0); + controller_state.fw_update_state = FwUpdateState::InProgress(0); } match controller @@ -212,16 +215,16 @@ where match controller.finalize_fw_update().await { Ok(_) => { debug!("FW update finalized successfully"); - state.controller_state_mut().fw_update_state = FwUpdateState::Idle; + controller_state.fw_update_state = FwUpdateState::Idle; } Err(Error::Pd(e)) => { error!("Failed to finalize FW update: {:?}", e); - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; return Self::create_offer_rejection(); } Err(Error::Bus(_)) => { error!("Failed to finalize FW update, bus error"); - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; return Self::create_offer_rejection(); } } @@ -234,8 +237,8 @@ where } /// Process a CFU tick - pub async fn process_cfu_tick(&self, controller: &mut D::Inner, state: &mut dyn DynPortState<'_, S>) { - match state.controller_state_mut().fw_update_state { + pub async fn process_cfu_tick(&self, controller_state: &mut ControllerState, controller: &mut D::Inner) { + match controller_state.fw_update_state { FwUpdateState::Idle => { // No FW update in progress, nothing to do return; @@ -243,7 +246,7 @@ where FwUpdateState::InProgress(ticks) => { if ticks + 1 < DEFAULT_FW_UPDATE_TIMEOUT_TICKS { trace!("CFU tick: {}", ticks); - state.controller_state_mut().fw_update_state = FwUpdateState::InProgress(ticks + 1); + controller_state.fw_update_state = FwUpdateState::InProgress(ticks + 1); return; } else { error!("FW update timed out after {} ticks", ticks); @@ -255,7 +258,7 @@ where }; // Update timed out, attempt to exit the FW update - state.controller_state_mut().fw_update_state = FwUpdateState::Recovery; + controller_state.fw_update_state = FwUpdateState::Recovery; match controller.abort_fw_update().await { Ok(_) => { debug!("FW update aborted successfully"); @@ -270,17 +273,17 @@ where } } - state.controller_state_mut().fw_update_state = FwUpdateState::Idle; + controller_state.fw_update_state = FwUpdateState::Idle; } /// Process a CFU command pub async fn process_cfu_command( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, command: &RequestData, ) -> InternalResponseData { - if state.controller_state().fw_update_state == FwUpdateState::Recovery { + if controller_state.fw_update_state == FwUpdateState::Recovery { debug!("FW update in recovery state, rejecting command"); return InternalResponseData::ComponentBusy; } @@ -296,11 +299,11 @@ where } RequestData::GiveContent(content) => { debug!("Got GiveContent"); - self.process_give_content(controller, state, content).await + self.process_give_content(controller_state, controller, content).await } RequestData::AbortUpdate => { debug!("Got AbortUpdate"); - self.process_abort_update(controller, state).await + self.process_abort_update(controller_state, controller).await } RequestData::FinalizeUpdate => { debug!("Got FinalizeUpdate"); @@ -332,7 +335,7 @@ where /// DROP SAFETY: No state that needs to be restored pub async fn wait_cfu_command(&self) -> EventCfu { // Only lock long enough to grab our state - let fw_update_state = self.state.lock().await.controller_state().fw_update_state; + let fw_update_state = self.controller_state.lock().await.fw_update_state; match fw_update_state { FwUpdateState::Idle => { // No FW update in progress, just wait for a command diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 0f634473a..0bf3165a0 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -9,10 +9,9 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, R, V> +> ControllerWrapper<'device, M, D, S, V> where D::Inner: Controller, { diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 15dd4d258..a724e92fd 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -16,12 +16,12 @@ //! [`ControllerWrapper::process_event`] consumes [`message::Output`] and responds to any deferred requests, performs //! any caching/buffering of data, and notifies the type-C service implementation of the event if needed. use core::array::from_fn; -use core::cell::RefMut; use core::future::pending; use core::ops::DerefMut; use crate::type_c::controller::{self, Controller, PortStatus}; use crate::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; +use crate::wrapper::backing::{ControllerState, PortState}; use cfu_service::CfuClient; use embassy_futures::select::{Either, Either5, select, select_array, select5}; use embassy_sync::blocking_mutex::raw::RawMutex; @@ -35,9 +35,8 @@ use embedded_services::{event, intrusive_list}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; -use crate::wrapper::backing::{DynPortState, PortPower}; use crate::wrapper::message::*; -use crate::wrapper::proxy::{PowerProxyDevice, PowerProxyReceiver}; +use crate::wrapper::proxy::PowerProxyReceiver; use crate::{PortEventStreamer, PortEventVariant}; pub mod backing; @@ -72,8 +71,7 @@ pub struct ControllerWrapper< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, > where D::Inner: Controller, @@ -84,41 +82,42 @@ pub struct ControllerWrapper< /// FW update ticker used to check for timeouts and recovery attempts fw_update_ticker: Mutex, /// Registration information for services - pub registration: backing::Registration<'device, M, R>, - /// State - state: Mutex>>, + pub registration: backing::Registration<'device, M>, /// SW port status event signal sw_status_event: Signal, /// General config config: config::Config, /// Power proxy receivers power_proxy_receivers: &'device [Mutex>], + /// Port proxies + pub ports: &'device [backing::Port<'device, M, S>], + /// Controller state + controller_state: Mutex, } impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, R, V> +> ControllerWrapper<'device, M, D, S, V> where D::Inner: Controller, { - /// Create a new controller wrapper, returns `None` if the backing storage is already in use - pub fn try_new( + /// Create a new controller wrapper + pub fn new( controller: &'device D, config: config::Config, - storage: &'device backing::ReferencedStorage<'device, N, M, S, R>, + storage: &'device backing::ReferencedStorage<'device, N, M, S>, fw_version_validator: V, - ) -> Option { + ) -> Self { const { assert!(N > 0 && N <= MAX_SUPPORTED_PORTS, "Invalid number of ports"); }; - let backing = storage.create_backing()?; - Some(Self { + let backing = storage.create_backing(); + Self { controller, config, fw_version_validator, @@ -126,38 +125,34 @@ where DEFAULT_FW_UPDATE_TICK_INTERVAL_MS, ))), registration: backing.registration, - state: Mutex::new(backing.state), sw_status_event: Signal::new(), power_proxy_receivers: backing.power_receivers, - }) + ports: backing.ports, + controller_state: Mutex::new(backing::ControllerState::default()), + } } /// Get the cached port status, returns None if the port is invalid pub async fn get_cached_port_status(&self, local_port: LocalPortId) -> Option { - self.state - .lock() - .await - .port_states() - .get(local_port.0 as usize) - .map(|s| s.status) + let port = self.ports.get(local_port.0 as usize)?; + Some(port.state.lock().await.status) } /// Synchronize the state between the controller and the internal state pub async fn sync_state(&self) -> Result<(), Error<::BusError>> { let mut controller = self.controller.lock().await; - let mut state = self.state.lock().await; - self.sync_state_internal(&mut controller, state.deref_mut().deref_mut()) - .await + self.sync_state_internal(&mut controller).await } /// Synchronize the state between the controller and the internal state async fn sync_state_internal( &self, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, ) -> Result<(), Error<::BusError>> { // Sync the controller state with the PD controller - for (i, port_state) in state.port_states_mut().iter_mut().enumerate() { + for (i, port) in self.ports.iter().enumerate() { + let mut port_state = port.state.lock().await; + let mut status_changed = port_state.sw_status_event; let local_port = LocalPortId(i as u8); let status = controller.get_port_status(local_port).await?; @@ -190,28 +185,21 @@ where /// Handle a plug event async fn process_plug_event( &self, - _controller: &mut D::Inner, - power: &mut PortPower, - port: LocalPortId, + port_state: &mut PortState<'_, S>, status: &PortStatus, ) -> Result<(), Error<::BusError>> { - if port.0 as usize >= self.registration.num_ports() { - error!("Invalid port {}", port.0); - return PdError::InvalidPort.into(); - } - info!("Plug event"); if status.is_connected() { info!("Plug inserted"); - power - .sender - .send(power_policy_interface::psu::event::RequestData::Attached) + port_state + .power_policy_sender + .send(power_policy_interface::psu::event::EventData::Attached) .await; } else { info!("Plug removed"); - power - .sender - .send(power_policy_interface::psu::event::RequestData::Detached) + port_state + .power_policy_sender + .send(power_policy_interface::psu::event::EventData::Detached) .await; } @@ -222,7 +210,6 @@ where async fn process_port_status_changed<'b>( &self, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, local_port_id: LocalPortId, status_event: PortStatusChanged, ) -> Result, Error<::BusError>> { @@ -232,30 +219,33 @@ where .lookup_global_port(local_port_id) .map_err(Error::Pd)?; + let mut port_state = self + .ports + .get(local_port_id.0 as usize) + .ok_or(Error::Pd(PdError::InvalidPort))? + .state + .lock() + .await; + let status = controller.get_port_status(local_port_id).await?; trace!("Port{} status: {:#?}", global_port_id.0, status); trace!("Port{} status events: {:#?}", global_port_id.0, status_event); - let power = state - .port_power_mut() - .get_mut(local_port_id.0 as usize) - .ok_or(PdError::InvalidPort)?; if status_event.plug_inserted_or_removed() { - self.process_plug_event(controller, power, local_port_id, &status) - .await?; + self.process_plug_event(&mut port_state, &status).await?; } // Only notify power policy of a contract after Sink Ready event (always after explicit or implicit contract) if status_event.sink_ready() { - self.process_new_consumer_contract(power, &status).await?; + self.process_new_consumer_contract(&mut port_state, &status).await?; } if status_event.new_power_contract_as_provider() { - self.process_new_provider_contract(power, &status).await?; + self.process_new_provider_contract(&mut port_state, &status).await?; } self.check_sink_ready_timeout( - state, + &mut port_state, &status, local_port_id, status_event.new_power_contract_as_consumer(), @@ -270,24 +260,26 @@ where } /// Finalize a port status change output - fn finalize_port_status_change( + async fn finalize_port_status_change( &self, - state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, status_event: PortStatusChanged, status: PortStatus, ) -> Result<(), Error<::BusError>> { - let port_index = local_port.0 as usize; let global_port_id = self .registration .pd_controller .lookup_global_port(local_port) .map_err(Error::Pd)?; - let port_state = state - .port_states_mut() - .get_mut(port_index) - .ok_or(Error::Pd(PdError::InvalidPort))?; + let mut port_state = self + .ports + .get(local_port.0 as usize) + .ok_or(Error::Pd(PdError::InvalidPort))? + .state + .lock() + .await; + let mut events = port_state.pending_events; events.status = events.status.union(status_event); port_state.pending_events = events; @@ -308,23 +300,25 @@ where } /// Finalize a PD alert output - fn finalize_pd_alert( + async fn finalize_pd_alert( &self, - state: &mut dyn DynPortState<'_, S>, local_port: LocalPortId, alert: Ado, ) -> Result<(), Error<::BusError>> { - let port_index = local_port.0 as usize; let global_port_id = self .registration .pd_controller .lookup_global_port(local_port) .map_err(Error::Pd)?; - let port_state = state - .port_states_mut() - .get_mut(port_index) - .ok_or(Error::Pd(PdError::InvalidPort))?; + let mut port_state = self + .ports + .get(local_port.0 as usize) + .ok_or(Error::Pd(PdError::InvalidPort))? + .state + .lock() + .await; + // Buffer the alert port_state.pd_alerts.0.publish_immediate(alert); @@ -347,15 +341,16 @@ where /// DROP SAFETY: No state that needs to be restored async fn wait_port_pending( &self, + controller_state: &ControllerState, controller: &mut D::Inner, ) -> Result::BusError>> { - if self.state.lock().await.controller_state().fw_update_state.in_progress() { + if controller_state.fw_update_state.in_progress() { // Don't process events while firmware update is in progress debug!("Firmware update in progress, ignoring port events"); return pending().await; } - let streaming_state = self.state.lock().await.controller_state().port_event_streaming_state; + let streaming_state = controller_state.port_event_streaming_state; if let Some(streamer) = streaming_state { // If we're converting the bitfields into an event stream yield first to prevent starving other tasks embassy_futures::yield_now().await; @@ -382,10 +377,11 @@ where // This loop is to ensure that if we finish streaming events we go back to waiting for the next port event loop { let event = { + let controller_state = self.controller_state.lock().await; let mut controller = self.controller.lock().await; // DROP SAFETY: Select over drop safe functions select5( - self.wait_port_pending(&mut controller), + self.wait_port_pending(&controller_state, &mut controller), self.wait_power_command(), self.registration.pd_controller.receive(), self.wait_cfu_command(), @@ -401,11 +397,14 @@ where // Combine the event read from the controller with any software generated events // Acquire the locks first to centralize the awaits here let mut controller = self.controller.lock().await; - let mut state = self.state.lock().await; - let port_state = state - .port_states_mut() - .get_mut(port_index) - .ok_or(Error::Pd(PdError::InvalidPort))?; + let mut port_state = self + .ports + .get(port_index) + .ok_or(Error::Pd(PdError::InvalidPort))? + .state + .lock() + .await; + let hw_event = controller.clear_port_events(LocalPortId(port_index as u8)).await?; // No more awaits, modify state here for drop safety @@ -416,11 +415,7 @@ where .await? { let port_id = LocalPortId(port_index as u8); - self.state - .lock() - .await - .controller_state_mut() - .port_event_streaming_state = Some(stream); + self.controller_state.lock().await.port_event_streaming_state = Some(stream); match event { PortEventVariant::StatusChanged(status_event) => { return Ok(Event::PortStatusChanged(EventPortStatusChanged { @@ -436,11 +431,7 @@ where } } } else { - self.state - .lock() - .await - .controller_state_mut() - .port_event_streaming_state = None; + self.controller_state.lock().await.port_event_streaming_state = None; } } Either5::Second((port, request)) => { @@ -451,12 +442,12 @@ where Either5::Fifth(port) => { // Sink ready timeout event debug!("Port{0}: Sink ready timeout", port.0); - self.state + self.ports + .get(port.0 as usize) + .ok_or(Error::Pd(PdError::InvalidPort))? + .state .lock() .await - .port_states_mut() - .get_mut(port.0 as usize) - .ok_or(Error::Pd(PdError::InvalidPort))? .sink_ready_deadline = None; let mut status_event = PortStatusChanged::none(); status_event.set_sink_ready(true); @@ -506,35 +497,34 @@ where event: Event<'b>, ) -> Result, Error<::BusError>> { let mut controller = self.controller.lock().await; - let mut state = self.state.lock().await; + let mut controller_state = self.controller_state.lock().await; match event { Event::PortStatusChanged(EventPortStatusChanged { port, status_event }) => { - self.process_port_status_changed(&mut controller, state.deref_mut().deref_mut(), port, status_event) + self.process_port_status_changed(&mut controller, port, status_event) .await } Event::PowerPolicyCommand(EventPowerPolicyCommand { port, request }) => { let response = self - .process_power_command(&mut controller, state.deref_mut().deref_mut(), port, &request) + .process_power_command(&mut controller_state, &mut controller, port, &request) .await; Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) } Event::ControllerCommand(request) => { let response = self - .process_pd_command(&mut controller, state.deref_mut().deref_mut(), &request.command) + .process_pd_command(&mut controller_state, &mut controller, &request.command) .await; Ok(Output::ControllerCommand(OutputControllerCommand { request, response })) } Event::CfuEvent(event) => match event { EventCfu::Request(request) => { let response = self - .process_cfu_command(&mut controller, state.deref_mut().deref_mut(), &request) + .process_cfu_command(&mut controller_state, &mut controller, &request) .await; Ok(Output::CfuResponse(response)) } EventCfu::RecoveryTick => { // FW Update tick, process timeouts and recovery attempts - self.process_cfu_tick(&mut controller, state.deref_mut().deref_mut()) - .await; + self.process_cfu_tick(&mut controller_state, &mut controller).await; Ok(Output::CfuRecovery) } }, @@ -547,22 +537,15 @@ where /// Event loop finalize pub async fn finalize<'b>(&self, output: Output<'b>) -> Result<(), Error<::BusError>> { - let mut state = self.state.lock().await; - match output { Output::Nop => Ok(()), Output::PortStatusChanged(OutputPortStatusChanged { port, status_event, status, - }) => self.finalize_port_status_change(state.deref_mut().deref_mut(), port, status_event, status), - Output::PdAlert(OutputPdAlert { port, ado }) => { - self.finalize_pd_alert(state.deref_mut().deref_mut(), port, ado) - } - Output::Vdm(vdm) => self - .finalize_vdm(state.deref_mut().deref_mut(), vdm) - .await - .map_err(Error::Pd), + }) => self.finalize_port_status_change(port, status_event, status).await, + Output::PdAlert(OutputPdAlert { port, ado }) => self.finalize_pd_alert(port, ado).await, + Output::Vdm(vdm) => self.finalize_vdm(vdm).await.map_err(Error::Pd), Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response }) => { self.power_proxy_receivers .get(port.0 as usize) @@ -611,20 +594,8 @@ where pub fn register( &'static self, controllers: &intrusive_list::IntrusiveList, - power_policy_context: &power_policy_service::service::context::Context>, R>, - cfu_client: &'static CfuClient, + cfu_client: &CfuClient, ) -> Result<(), Error<::BusError>> { - for device in self.registration.power_devices { - power_policy_context.register_psu(device).map_err(|_| { - error!( - "Controller{}: Failed to register power device {}", - self.registration.pd_controller.id().0, - device.id().0 - ); - Error::Pd(PdError::Failed) - })?; - } - controller::register_controller(controllers, self.registration.pd_controller).map_err(|_| { error!( "Controller{}: Failed to register PD controller", @@ -649,10 +620,9 @@ impl< 'device, M: RawMutex, C: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> Lockable for ControllerWrapper<'device, M, C, S, R, V> +> Lockable for ControllerWrapper<'device, M, C, S, V> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index 6a306d182..a842bb2b4 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,12 +1,13 @@ use crate::type_c::Cached; use crate::type_c::controller::{InternalResponseData, Response}; +use crate::wrapper::backing::ControllerState; use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; use embedded_services::debug; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; use embedded_usb_pd::ucsi::{self, lpm}; -use power_policy_interface::psu::State; +use power_policy_interface::psu::{self, PsuState}; use super::*; @@ -14,27 +15,19 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, R, V> +> ControllerWrapper<'device, M, D, S, V> where D::Inner: Controller, { async fn process_get_pd_alert( &self, - state: &mut dyn DynPortState<'_, S>, + port_state: &mut PortState<'_, S>, local_port: LocalPortId, ) -> Result, PdError> { loop { - match state - .port_states_mut() - .get_mut(local_port.0 as usize) - .ok_or(PdError::InvalidPort)? - .pd_alerts - .1 - .try_next_message() - { + match port_state.pd_alerts.1.try_next_message() { Some(WaitResult::Message(alert)) => return Ok(Some(alert)), None => return Ok(None), Some(WaitResult::Lagged(count)) => { @@ -53,21 +46,13 @@ where /// even for controllers that might not always broadcast sink ready events. pub(super) fn check_sink_ready_timeout( &self, - state: &mut dyn DynPortState<'_, S>, + port_state: &mut PortState<'_, S>, status: &PortStatus, port: LocalPortId, new_contract: bool, sink_ready: bool, ) -> Result<(), PdError> { - if port.0 as usize >= state.num_ports() { - return Err(PdError::InvalidPort); - } - - let deadline = &mut state - .port_states_mut() - .get_mut(port.0 as usize) - .ok_or(PdError::InvalidPort)? - .sink_ready_deadline; + let deadline = &mut port_state.sink_ready_deadline; if new_contract && !sink_ready { // Start the timeout @@ -97,22 +82,16 @@ where /// DROP SAFETY: No state to restore pub(super) async fn wait_sink_ready_timeout(&self) -> LocalPortId { let futures: [_; MAX_SUPPORTED_PORTS] = from_fn(|i| async move { - let deadline = self - .state - .lock() - .await - .port_states() - .get(i) - .and_then(|s| s.sink_ready_deadline); + let Some(port) = self.ports.get(i) else { + pending::<()>().await; + return; + }; + let deadline = port.state.lock().await.sink_ready_deadline; if let Some(deadline) = deadline { Timer::at(deadline).await; debug!("Port{}: Sink ready timeout reached", i); - if let Some(state) = self.state.lock().await.port_states_mut().get_mut(i) { - state.sink_ready_deadline = None; - } else { - error!("Invalid state array index {}", i); - } + port.state.lock().await.sink_ready_deadline = None; } else { pending::<()>().await; } @@ -127,30 +106,25 @@ where async fn process_set_max_sink_voltage( &self, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, + port_state: &mut PortState<'_, S>, + state: &psu::State, local_port: LocalPortId, voltage_mv: Option, ) -> Result { - let port_power = state - .port_power_mut() - .get_mut(local_port.0 as usize) - .ok_or(PdError::InvalidPort)?; - let state = port_power.state.state(); - debug!("Port{}: Current state: {:#?}", local_port.0, state); - if matches!(state, State::ConnectedConsumer(_)) { + let psu_state = state.psu_state; + debug!("Port{}: Current state: {:#?}", local_port.0, psu_state); + if matches!(psu_state, PsuState::ConnectedConsumer(_)) { debug!("Port{}: Set max sink voltage, connected consumer found", local_port.0); - if voltage_mv.is_some() - && voltage_mv < port_power.state.consumer_capability().map(|c| c.capability.voltage_mv) - { + if voltage_mv.is_some() && voltage_mv < state.consumer_capability.map(|c| c.capability.voltage_mv) { // New max voltage is lower than current consumer capability which will trigger a renegociation // So disconnect first debug!( "Port{}: Disconnecting consumer before setting max sink voltage", local_port.0 ); - port_power - .sender - .send(power_policy_interface::psu::event::RequestData::Disconnected) + port_state + .power_policy_sender + .send(power_policy_interface::psu::event::EventData::Disconnected) .await; } } @@ -167,18 +141,12 @@ where async fn process_get_port_status( &self, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, + port_state: &mut PortState<'_, S>, local_port: LocalPortId, cached: Cached, ) -> Result { if cached.0 { - Ok(controller::PortResponseData::PortStatus( - state - .port_states() - .get(local_port.0 as usize) - .ok_or(PdError::InvalidPort)? - .status, - )) + Ok(controller::PortResponseData::PortStatus(port_state.status)) } else { match controller.get_port_status(local_port).await { Ok(status) => Ok(controller::PortResponseData::PortStatus(status)), @@ -193,11 +161,11 @@ where /// Handle a port command async fn process_port_command( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, command: &controller::PortCommand, ) -> Response<'static> { - if state.controller_state().fw_update_state.in_progress() { + if controller_state.fw_update_state.in_progress() { debug!("FW update in progress, ignoring port command"); return controller::Response::Port(Err(PdError::Busy)); } @@ -209,20 +177,19 @@ where return controller::Response::Port(Err(PdError::InvalidPort)); }; + let Some(port) = self.ports.get(local_port.0 as usize) else { + debug!("Invalid port: {:?}", command.port); + return controller::Response::Port(Err(PdError::InvalidPort)); + }; + + let mut port_state = port.state.lock().await; controller::Response::Port(match command.data { controller::PortCommandData::PortStatus(cached) => { - self.process_get_port_status(controller, state, local_port, cached) + self.process_get_port_status(controller, &mut port_state, local_port, cached) .await } controller::PortCommandData::ClearEvents => { - let port_index = local_port.0 as usize; - let state = if let Some(state) = state.port_states_mut().get_mut(port_index) { - state - } else { - return controller::Response::Port(Err(PdError::InvalidPort)); - }; - - let event = core::mem::take(&mut state.pending_events); + let event = core::mem::take(&mut port_state.pending_events); Ok(controller::PortResponseData::ClearEvents(event)) } controller::PortCommandData::RetimerFwUpdateGetState => { @@ -266,15 +233,24 @@ where Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::GetPdAlert => match self.process_get_pd_alert(state, local_port).await { - Ok(alert) => Ok(controller::PortResponseData::PdAlert(alert)), - Err(e) => Err(e), - }, + controller::PortCommandData::GetPdAlert => { + match self.process_get_pd_alert(&mut port_state, local_port).await { + Ok(alert) => Ok(controller::PortResponseData::PdAlert(alert)), + Err(e) => Err(e), + } + } controller::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { match self.registration.pd_controller.lookup_local_port(command.port) { Ok(local_port) => { - self.process_set_max_sink_voltage(controller, state, local_port, voltage_mv) - .await + let psu_state = port.proxy.lock().await.psu_state; + self.process_set_max_sink_voltage( + controller, + &mut port_state, + &psu_state, + local_port, + voltage_mv, + ) + .await } Err(e) => Err(e), } @@ -402,11 +378,11 @@ where async fn process_controller_command( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, command: &controller::InternalCommandData, ) -> Response<'static> { - if state.controller_state().fw_update_state.in_progress() { + if controller_state.fw_update_state.in_progress() { debug!("FW update in progress, ignoring controller command"); return controller::Response::Controller(Err(PdError::Busy)); } @@ -417,7 +393,7 @@ where controller::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed)) } controller::InternalCommandData::SyncState => { - let result = self.sync_state_internal(controller, state).await; + let result = self.sync_state_internal(controller).await; controller::Response::Controller( result .map(|_| InternalResponseData::Complete) @@ -438,14 +414,17 @@ where /// Handle a PD controller command pub(super) async fn process_pd_command( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, command: &controller::Command, ) -> Response<'static> { match command { - controller::Command::Port(command) => self.process_port_command(controller, state, command).await, + controller::Command::Port(command) => { + self.process_port_command(controller_state, controller, command).await + } controller::Command::Controller(command) => { - self.process_controller_command(controller, state, command).await + self.process_controller_command(controller_state, controller, command) + .await } controller::Command::Lpm(_) => controller::Response::Ucsi(ucsi::Response { cci: ucsi::cci::Cci::new_error(), diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index e778b5042..fa9c33d34 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -9,6 +9,7 @@ use power_policy_interface::psu::CommandData as PowerCommand; use power_policy_interface::psu::Error as PowerError; use power_policy_interface::psu::{CommandData, InternalResponseData, ResponseData}; +use crate::wrapper::backing::ControllerState; use crate::wrapper::config::UnconstrainedSink; use super::*; @@ -17,17 +18,16 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, R, V> +> ControllerWrapper<'device, M, D, S, V> where D::Inner: Controller, { /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, - power: &mut PortPower, + port_state: &mut PortState<'_, S>, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); @@ -43,9 +43,9 @@ where c }); - power - .sender - .send(power_policy_interface::psu::event::RequestData::UpdatedConsumerCapability(available_sink_contract)) + port_state + .power_policy_sender + .send(power_policy_interface::psu::event::EventData::UpdatedConsumerCapability(available_sink_contract)) .await; Ok(()) } @@ -53,14 +53,14 @@ where /// Handle a new contract as provider pub(super) async fn process_new_provider_contract( &self, - power: &mut PortPower, + port_state: &mut PortState<'_, S>, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); - power - .sender + port_state + .power_policy_sender .send( - power_policy_interface::psu::event::RequestData::RequestedProviderCapability( + power_policy_interface::psu::event::EventData::RequestedProviderCapability( status.available_source_contract.map(|caps| { let mut caps = ProviderPowerCapability::from(caps); caps.flags.set_psu_type(PsuType::TypeC); @@ -126,13 +126,13 @@ where /// Returns no error because this is a top-level function pub(super) async fn process_power_command( &self, + controller_state: &mut ControllerState, controller: &mut D::Inner, - state: &mut dyn DynPortState<'_, S>, port: LocalPortId, command: &CommandData, ) -> InternalResponseData { trace!("Processing power command: device{} {:#?}", port.0, command); - if state.controller_state().fw_update_state.in_progress() { + if controller_state.fw_update_state.in_progress() { debug!("Port{}: Firmware update in progress", port.0); return Err(PowerError::Busy); } diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs index c8f95110a..a14dc82a9 100644 --- a/type-c-service/src/wrapper/proxy.rs +++ b/type-c-service/src/wrapper/proxy.rs @@ -15,11 +15,13 @@ impl PowerProxyChannel { } } - pub fn get_device(&self) -> PowerProxyDevice<'_> { - PowerProxyDevice { - sender: self.command_channel.dyn_sender(), - receiver: self.response_channel.dyn_receiver(), - } + pub fn get_device_components( + &self, + ) -> ( + DynamicSender<'_, PolicyCommandData>, + DynamicReceiver<'_, PolicyResponseData>, + ) { + (self.command_channel.dyn_sender(), self.response_channel.dyn_receiver()) } pub fn get_receiver(&self) -> PowerProxyReceiver<'_> { @@ -55,14 +57,23 @@ impl<'a> PowerProxyReceiver<'a> { pub struct PowerProxyDevice<'a> { sender: DynamicSender<'a, PolicyCommandData>, receiver: DynamicReceiver<'a, PolicyResponseData>, + /// Per-port PSU state + pub(crate) psu_state: power_policy_interface::psu::State, + name: &'static str, } impl<'a> PowerProxyDevice<'a> { pub fn new( + name: &'static str, sender: DynamicSender<'a, PolicyCommandData>, receiver: DynamicReceiver<'a, PolicyResponseData>, ) -> Self { - Self { sender, receiver } + Self { + name, + sender, + receiver, + psu_state: power_policy_interface::psu::State::default(), + } } async fn execute(&mut self, command: PolicyCommandData) -> PolicyResponseData { @@ -93,6 +104,18 @@ impl<'a> Psu for PowerProxyDevice<'a> { .await? .complete_or_err() } + + fn state(&self) -> &power_policy_interface::psu::State { + &self.psu_state + } + + fn state_mut(&mut self) -> &mut power_policy_interface::psu::State { + &mut self.psu_state + } + + fn name(&self) -> &'static str { + self.name + } } impl Default for PowerProxyChannel { diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 1ca951b50..baa088503 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -2,7 +2,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, sync::Lockable, trace}; use embedded_usb_pd::{Error, LocalPortId, PdError}; -use crate::wrapper::{DynPortState, message::vdm::OutputKind}; +use crate::wrapper::message::vdm::OutputKind; use crate::type_c::{ controller::Controller, @@ -15,10 +15,9 @@ impl< 'device, M: RawMutex, D: Lockable, - S: event::Sender, - R: event::Receiver, + S: event::Sender, V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, R, V> +> ControllerWrapper<'device, M, D, S, V> where D::Inner: Controller, { @@ -41,21 +40,18 @@ where } /// Finalize a VDM output by notifying the service. - pub(super) async fn finalize_vdm( - &self, - state: &mut dyn DynPortState<'_, S>, - output: Output, - ) -> Result<(), PdError> { + pub(super) async fn finalize_vdm(&self, output: Output) -> Result<(), PdError> { trace!("Finalizing VDM output: {:?}", output); let Output { port, kind } = output; let global_port_id = self.registration.pd_controller.lookup_global_port(port)?; - let port_index = port.0 as usize; - let notification = &mut state - .port_states_mut() - .get_mut(port_index) + let mut port_state = self + .ports + .get(port.0 as usize) .ok_or(PdError::InvalidPort)? - .pending_events - .notification; + .state + .lock() + .await; + let notification = &mut port_state.pending_events.notification; match kind { OutputKind::Entered(_) => notification.set_custom_mode_entered(true), OutputKind::Exited(_) => notification.set_custom_mode_exited(true), From 7f296bdeb8ae853cba9310df4dc621f09060a1a0 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:02:10 -0700 Subject: [PATCH 27/79] Uniform service spawning with external 'resources' on time-alarm, espi (#740) This is a proof-of-concept for a common pattern for initializing and starting the services in embedded-services. It demonstrates this pattern on the time-alarm and espi service. We'll start migrating other services in future changes. Currently, starting the various services in embedded-services is pretty complicated. Each service has a different set of 'worker tasks' that users have to declare and start, each of which has varying numbers of parameters of varying types. It's very easy to miss one, and whenever a change comes in that changes what state a given task needs to function, that's a breaking change that, in the best case, causes a build failure, and in the worst case causes the service to silently misbehave. This attempts to mitigate this complexity with a few tricks: 1. Rather than having N worker tasks, we can `select!()` over a list of worker tasks. This works because worker 'green threads' were already intended to never return, so the select never returns either. This lets us execute them all against a single embassy task. 2. Rather than having each of those worker tasks require a different set of parameters, the onus for figuring out which parameters need to go to what is placed on the service author. At service construction time, the service author must return a (`Service`, `ServiceRunner`) tuple. The `ServiceRunner` implementation which has a single method that takes no arguments and never returns. This has a few benefits: - It makes changes to the number and shape of 'green thread' functions transparent to the end user. - It makes it impossible for users to only launch some of the tasks - It makes it impossible for users to launch tasks in an order that might cause problems - It makes it difficult (but not impossible) to forget to launch the task at all - they get a `ServiceRunner` object back when the instantiate the service, and if they don't use it the compiler will complain about unused variables. They can bypass the warning with a leading `_`, but that's an explicit action the programmer has to take. I haven't found a way to outright prevent this class of error, unfortunately. 3. Require a `&'hw mut Resources` as an argument at service creation time. This Resources object is the 'real' service, and by taking it as a mutable borrow rather than a reference-to-oncelock or similar, we avoid prescribing any particular way of storing the memory required to run the service. 4. Create a `spawn_service!()` macro that does all the boilerplate of declaring storage/tasks and spawning the tasks on Embassy for a given service. In product use cases, this makes it outright impossible to forget to start the runner, and reduces the ~10 lines of somewhat verbose boilerplate required for each service down to zero. This approach aligns with a what a few other embassy services are doing - see [embassy_net::new](https://docs.embassy.dev/embassy-net/0.8.0/default/fn.new.html) for a good example. --- Cargo.lock | 14 +- Cargo.toml | 2 + docs/api-guidelines.md | 283 +++++++++ espi-service/Cargo.toml | 12 +- espi-service/src/espi_service.rs | 74 ++- espi-service/src/lib.rs | 3 - espi-service/src/task.rs | 7 - examples/rt633/Cargo.lock | 650 ++++----------------- examples/rt685s-evk/Cargo.lock | 10 + examples/rt685s-evk/Cargo.toml | 1 + examples/rt685s-evk/src/bin/time_alarm.rs | 34 +- odp-service-common/Cargo.toml | 13 + odp-service-common/src/lib.rs | 4 + odp-service-common/src/runnable_service.rs | 83 +++ time-alarm-service/Cargo.toml | 3 +- time-alarm-service/src/lib.rs | 191 ++++-- time-alarm-service/src/task.rs | 9 - time-alarm-service/tests/tad_test.rs | 47 +- 18 files changed, 768 insertions(+), 672 deletions(-) create mode 100644 docs/api-guidelines.md delete mode 100644 espi-service/src/task.rs create mode 100644 odp-service-common/Cargo.toml create mode 100644 odp-service-common/src/lib.rs create mode 100644 odp-service-common/src/runnable_service.rs delete mode 100644 time-alarm-service/src/task.rs diff --git a/Cargo.lock b/Cargo.lock index 341488c59..c875565c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1104,11 +1104,9 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ - "battery-service-messages", "bitfield 0.17.0", "cortex-m", "cortex-m-rt", - "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-imxrt", @@ -1118,8 +1116,7 @@ dependencies = [ "log", "mctp-rs", "num_enum", - "thermal-service-messages", - "time-alarm-service-messages", + "odp-service-common", ] [[package]] @@ -1714,6 +1711,14 @@ dependencies = [ "memchr", ] +[[package]] +name = "odp-service-common" +version = "0.1.0" +dependencies = [ + "embedded-services", + "static_cell", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2346,6 +2351,7 @@ dependencies = [ "embedded-mcu-hal", "embedded-services", "log", + "odp-service-common", "time-alarm-service-messages", "tokio", "zerocopy", diff --git a/Cargo.toml b/Cargo.toml index 410ee4a6d..ffe8c00b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "debug-service-messages", "keyboard-service", "power-policy-interface", + "odp-service-common", ] exclude = ["examples/*"] @@ -86,6 +87,7 @@ embedded-storage-async = "0.4.1" embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb-pd", default-features = false } mctp-rs = { git = "https://github.com/dymk/mctp-rs" } num_enum = { version = "0.7.5", default-features = false } +odp-service-common = { path = "./odp-service-common" } portable-atomic = { version = "1.11", default-features = false } power-policy-interface = { path = "./power-policy-interface" } paste = "1.0.15" diff --git a/docs/api-guidelines.md b/docs/api-guidelines.md new file mode 100644 index 000000000..b6e7e75af --- /dev/null +++ b/docs/api-guidelines.md @@ -0,0 +1,283 @@ +# Service API Guidelines + +This document establishes some guidelines that APIs in this repo should try to conform to, and explains the rationale behind those guidelines to help guide decisionmaking when tradeoffs need to be made. + +These guidelines attempt to make our APIs easier to compose, test, and use. + +## Guidelines + +### No 'static references + +References with lifetime `'static` in API functions should be avoided, even if the lifetime will always be `'static` in production use cases. Instead, make your type generic over a lifetime with the expectation that that lifetime will be `'static` in production use cases. + +__Reason__: Testability. If something needs to take a reference to an object 'O' with lifetime `'static`, that means that O can never be destroyed. This can make it pretty difficult to test things that use that API. + +__Example__: +Instead of this: +```rust +trait Subscriber {} +struct Notifier { subscriber: &'static dyn Subscriber } +// ^^^^^^^^ + +impl Notifier { + fn new(subscriber: &'static dyn Subscriber) -> Self { + // ^^^^^^^^ + Self { subscriber } + } +} +``` + +Consider something like this: +```rust +trait Subscriber {} +struct Notifier<'sub> { subscriber: &'sub dyn Subscriber } +// ^^^^^^ ^^^^^ + +impl<'sub> Notifier<'sub> { + fn new(subscriber: &'sub dyn Subscriber) -> Self { + // ^^^^^ + Self { subscriber } + } +} +``` +In cases like this, if you know that there will only be one concrete type for your reference, consider being generic over the type rather than taking it as `dyn`. This is particularly common for HAL trait implementations. This allows the compiler to inline and simplify code, which can result in performance and code size improvements in some circumstances. + +Alternatively, if you can take an owned `Subscriber` rather than a reference, something like this is probably better: +```rust +trait Subscriber {} +struct Notifier { + sub: S, +} + +impl Notifier { + const fn new(sub: S) -> Self { + Self { sub } + } +} +``` + +### External memory allocation / no static memory allocation + +Memory allocation should always be the role of the caller of the API. If you need memory, have your caller pass it into your constructor. Do not have things like `static INSTANCE: OnceLock` in your service module. + +If you don't need dynamic dispatch over user-provided types, additionally consider being generic over those user-provided types rather than taking `dyn` arguments - this is only possible if you have external memory allocation. + +Note that while this applies to code in this repo, it does not necessarily apply to other ODP repos (e.g. HAL crates that know exactly how many instances of peripheral X are available on the platform). + +__Reason__: Most code in this repo is expected to run primarily in environments that don't have a heap. In heapless environments, your options are either to have your caller provide you memory or to allocate it as a static variable in your module. Allocating it as a static variable in your module has negative impacts on flexibility, testability, performance, and code size. +Flexibility - Memory allocation in your module rather than by your caller means that the size of your object must be known when the module is compiled rather than when you're instantiated. This prevents you from storing any owned caller-provided types in your object (since you can't know those types when your module is compiled). +Testability - if you have a private singleton instance, tests can't arbitrarily destroy and recreate that state. This makes it difficult to test multiple startup paths. +Performance - if you can't be generic over a type, the only way you can interact with user-provided types is by dyn references to trait impls. External memory allocation allows you to be generic over a type, which means you don't have to pay for dynamic dispatch and the compiler can potentially inline code / optimize the interaction between your code and the user-provided type's code. +Code size - The compiler has to generate a bunch of code to handle dynamic dispatch, even if there's only ever a single concrete type that implements the trait you want to be generic over, which is common with HAL traits. + +__Example__: +Note that in the below example, the `OnceLock` / external `Resources` is only necessary if you need to hand out references to the contents of the `OnceLock` / `FooInner`. That's elided in the example and assumed to be implemented in the `/* .. */` blocks for simplicity. +```rust +pub struct Foo { /* .. */ } + +static INSTANCE: OnceLock = OnceLock::new(); + +impl Foo { + async fn init(/* .. */) -> &'static Foo { + let instance = INSTANCE.get_or_init(|| Foo{ /* .. */ }).await; + + // Create another reference to some state in 'inner' - perhaps by passing it to something in /* .. */ + + instance + } +} +``` + +Consider something like this: +```rust +struct FooInner<'hw> { /* .. */ } + +#[derive(Default)] +pub struct Resources<'hw> { + inner: Option +} + +pub struct Foo<'hw> { + inner: &'hw FooInner +} + +impl<'hw> Foo<'hw> { + fn new(resources: &'hw mut Resources, /* .. */) -> Self { + let inner = resources.insert(FooInner::new(/* .. */)); + + // Create another reference to some state in `inner` here that outlasts this function - perhaps by returning + // a `Runner` that contains a reference to `inner` or passing a reference to `inner` to one of the elided + // arguments in /* .. */. See the 'Use runner objects for concurrency' section for a concrete example of this. + // If you don't have a requirement to do this, you don't need the indirection / external `Resources` object at all. + + Self{ inner } + } +} +``` + + +### Use runner objects for concurrency + +Don't declare embassy tasks in your module - instead, have the constructor for your type return a `(Self, Runner)` tuple. The `Runner` object should have a single method `run(self) -> !` that the entity that instantiated your object must execute. You should have only one `Runner` object returned. Use the `odp-service-common::runnable_service::Service` trait to enforce this pattern. + +__Reason__: Declarations of embassy tasks are functionally static memory allocations. They can't be generic and you have to declare at declaration time a maximum number of instances that can be run concurrently. They also commit you to running on embassy, which is not necessarily desirable in test contexts. Pushing responsibility for the allocation out of your module allows your types to be generic. +However, it also means that your caller needs to be able to declare a task that can run your runner, and if you have multiple things that each need different pieces of state and need to run concurrently, setting up those tasks can make your API unwieldy and brittle. +Returning a simple `Runner` object at the same time as your object makes it difficult to forget to execute the runner. +Allowing only a single `Runner` with only one method that takes no external arguments makes it difficult to misuse the runner. + +__Example__: +Instead of this: +```rust +///// Your type's definition ///// +struct MyRunnableTypeInner { /* .. */ } + +impl<'hw> MyRunnableTypeInner<'hw> { + /* .. */ +} + +#[derive(Default)] +pub struct Resources<'hw> { + inner: Option +} + +pub struct MyRunnableType<'hw> { + inner: &'hw MyRunnableTypeInner +} + +impl<'hw> MyRunnableType<'hw> { + pub fn new(resources: &mut Resources, /* .. */ ) -> Self { + let inner = resources.insert(RunnableTypeInner::new(/* .. */)) + /* .. */ + Self { inner } + } +} + +mod tasks { + pub async fn run_task_1(runnable: &MyRunnableType, foo: Foo) -> ! { /* .. */ } + pub async fn run_task_2(runnable: &MyRunnableType, bar: Bar) -> ! { /* .. */ } + pub async fn run_task_3(runnable: &MyRunnableType, baz: Baz) -> ! { /* .. */ } +} + +///// End-user code ///// + +fn main() { + let instance = MyRunnableType::new(/* .. */); + #[embassy_task] + fn runner_1(runnable: &'static MyRunnableType, foo: Foo) -> ! { + my_runnable_type::tasks::run_task_1(runnable, foo).await + } + #[embassy_task] + fn runner_2(runnable: &'static MyRunnableType, bar: Bar) -> ! { + my_runnable_type::tasks::run_task_2(runnable, bar).await + } + #[embassy_task] + fn runner_3(runnable: &'static MyRunnableType, baz: Baz) -> ! { + my_runnable_type::tasks::run_task_3(runnable, baz).await + } + + spawner.must_spawn(runner_1(&instance, Foo::new( /* .. */ ))); + spawner.must_spawn(runner_2(&instance, Bar::new( /* .. */ ))); + spawner.must_spawn(runner_3(&instance, Baz::new( /* .. */ ))); +} + +``` + +Consider something like this: +```rust +///// Your type's definition ///// +struct MyRunnableTypeInner { /* .. */ } + +impl<'hw> MyRunnableTypeInner<'hw> { + async fn task_1(&self, foo: Foo) -> ! { /* .. */ } + async fn task_2(&self, bar: Bar) -> ! { /* .. */ } + async fn task_3(&self, baz: Baz) -> ! { /* .. */ } +} + +#[derive(Default)] +pub struct Resources<'hw> { + inner: Option +} + +pub struct MyRunnableType<'hw> { + inner: &'hw MyRunnableTypeInner +} + +pub struct Runner<'hw> { + inner: &'hw MyRunnableTypeInner, + foo: Foo, + bar: Bar, + baz: Baz +} + +impl<'hw> Runner<'hw> { + pub async fn run(self) -> ! { + loop { + embassy_sync::join::join3( + self.inner.task_1(self.foo), + self.inner.task_2(self.bar), + self.inner.task_3(self.baz) + ).await; + } + } +} + +impl<'hw> MyRunnableType<'hw> { + pub fn new(resources: &mut Resources, foo: Foo, bar: Bar, baz: Baz /* .. */ ) -> (Self, Runner) { + let inner = resources.insert(RunnableTypeInner::new( /* .. */ )); + (Self { inner }, Runner { inner, foo, bar, baz }) + } +} + +///// End-user code ///// + +fn main() { + let (instance, runner) = MyRunnableType::new(/* .. */); + #[embassy_task] + fn runner_fn(runner: Runner) { + runner.run().await + } + + spawner.must_spawn(runner_fn(runner)); +} +``` +Notice that most of the complexity has been moved into internal implementation details and the client doesn't have to think about it. Also notice that if you want to add a new 'green thread', or change what state is available to which 'green threads' you can do that entirely in private code in the `run()` method, without requiring changes to your client. + +### Use traits for public methods expected to be used at run time whenever possible + +In most cases, public APIs in this repo should be exposed in terms of traits rather than methods directly on the object, and objects that need to interact with other embedded-services objects should refer to them by trait rather than by name. This does not apply to public methods used to construct or initialize a service, because those generally need to know something about the concrete implementation type to properly initialize it. + +These traits should be defined in standalone 'interface' crates (i.e. `battery-service-interface`) alongside any support types needed for the interface (e.g. an Error enum) + +__Reason__: Improved testability and customizability. +Testability - if all our types interact with each other via traits rather than direct dependencies on the type, it makes it much easier to mock out individual components. +Customizability - if an OEM needs to insert a special behavior, they can substitute in a different implementation of that trait and continue using the rest of the embedded-services code without modification. + +__Example__: +Instead of +```rust +pub struct ExampleService { /* */ } +impl ExampleService { + fn foo(&mut self) -> Result<()> { /* .. */ } + fn bar(&mut self) -> Result<()> { /* .. */ } + fn baz(&mut self) -> Result<()> { /* .. */ } +} +``` + +Consider: +```rust +// In a standalone interface crate +pub trait ExampleService { + fn foo(&mut self) -> Result<()>; + fn bar(&mut self) -> Result<()>; + fn baz(&mut self) -> Result<()>; +} + +// In the reference implementation crate +pub struct OdpExampleService { /* .. */ } + +impl embedded_services::ExampleService for OdpExampleService { + fn foo(&mut self) -> Result<()> { /* .. */ } + fn bar(&mut self) -> Result<()> { /* .. */ } + fn baz(&mut self) -> Result<()> { /* .. */ } +} +``` \ No newline at end of file diff --git a/espi-service/Cargo.toml b/espi-service/Cargo.toml index 1c3214e78..91d91c5b9 100644 --- a/espi-service/Cargo.toml +++ b/espi-service/Cargo.toml @@ -21,13 +21,7 @@ embassy-imxrt = { workspace = true, features = ["mimxrt633s"] } embassy-futures.workspace = true mctp-rs = { workspace = true, features = ["espi"] } num_enum.workspace = true - -# TODO Service message type crates are a temporary dependency until we can parameterize -# the supported messages types at eSPI service creation time. -battery-service-messages.workspace = true -debug-service-messages.workspace = true -thermal-service-messages.workspace = true -time-alarm-service-messages.workspace = true +odp-service-common.workspace = true [target.'cfg(target_os = "none")'.dependencies] cortex-m-rt.workspace = true @@ -45,16 +39,12 @@ embassy-imxrt = { workspace = true, features = [ default = [] defmt = [ "dep:defmt", - "battery-service-messages/defmt", - "debug-service-messages/defmt", "embedded-services/defmt", "embassy-time/defmt", "embassy-time/defmt-timestamp-uptime", "embassy-sync/defmt", "embassy-imxrt/defmt", "mctp-rs/defmt", - "thermal-service-messages/defmt", - "time-alarm-service-messages/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 0a7c1d937..fdbdc1dec 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -4,7 +4,6 @@ use embassy_futures::select::select; use embassy_imxrt::espi; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; -use embassy_sync::once_lock::OnceLock; use embedded_services::{GlobalRawMutex, error, info, trace}; use mctp_rs::smbus_espi::SmbusEspiMedium; use mctp_rs::smbus_espi::SmbusEspiReplyContext; @@ -32,28 +31,75 @@ pub enum Error { Buffer(embedded_services::buffer::Error), } +/// The memory required by the eSPI service to run +pub struct Resources<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { + inner: Option>, +} + +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Default for Resources<'hw, RelayHandler> { + fn default() -> Self { + Self { inner: None } + } +} + +/// Service runner for the eSPI service. Users must call the run() method on the runner for the service to start processing events. +pub struct Runner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { + inner: &'hw ServiceInner<'hw, RelayHandler>, +} + +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> + odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw, RelayHandler> +{ + /// Run the service event loop. + async fn run(self) -> embedded_services::Never { + self.inner.run().await + } +} + pub struct Service<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { + _inner: &'hw ServiceInner<'hw, RelayHandler>, +} + +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> odp_service_common::runnable_service::Service<'hw> + for Service<'hw, RelayHandler> +{ + type Resources = Resources<'hw, RelayHandler>; + type Runner = Runner<'hw, RelayHandler>; + type ErrorType = core::convert::Infallible; + type InitParams = InitParams<'hw, RelayHandler>; + + async fn new( + resources: &'hw mut Self::Resources, + params: InitParams<'hw, RelayHandler>, + ) -> Result<(Self, Self::Runner), core::convert::Infallible> { + let inner = resources.inner.insert(ServiceInner::new(params).await); + Ok((Self { _inner: inner }, Runner { inner })) + } +} + +pub struct InitParams<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { + pub espi: espi::Espi<'hw>, + pub relay_handler: RelayHandler, +} + +struct ServiceInner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { espi: Mutex>, host_tx_queue: Channel, HOST_TX_QUEUE_SIZE>, relay_handler: RelayHandler, } -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'hw, RelayHandler> { - pub async fn init( - service_storage: &'hw OnceLock, - mut espi: espi::Espi<'hw>, - relay_handler: RelayHandler, - ) -> &'hw Self { - espi.wait_for_plat_reset().await; +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> ServiceInner<'hw, RelayHandler> { + async fn new(mut init_params: InitParams<'hw, RelayHandler>) -> Self { + init_params.espi.wait_for_plat_reset().await; - service_storage.get_or_init(|| Service { - espi: Mutex::new(espi), + Self { + espi: Mutex::new(init_params.espi), host_tx_queue: Channel::new(), - relay_handler, - }) + relay_handler: init_params.relay_handler, + } } - pub(crate) async fn run_service(&self) -> ! { + async fn run(&self) -> embedded_services::Never { let mut espi = self.espi.lock().await; loop { let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await; @@ -180,9 +226,7 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'h } Ok(()) } -} -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'hw, RelayHandler> { async fn process_request_to_ec( &self, (header, body): ( diff --git a/espi-service/src/lib.rs b/espi-service/src/lib.rs index c3f65e52d..ce0c51fd6 100644 --- a/espi-service/src/lib.rs +++ b/espi-service/src/lib.rs @@ -22,8 +22,5 @@ #[cfg(not(test))] mod espi_service; -#[cfg(not(test))] -pub mod task; - #[cfg(not(test))] pub use espi_service::*; diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs deleted file mode 100644 index 578551c5c..000000000 --- a/espi-service/src/task.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::Service; - -pub async fn espi_service<'hw, R: embedded_services::relay::mctp::RelayHandler>( - espi_service: &'hw Service<'hw, R>, -) -> Result { - espi_service.run_service().await -} diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index cfdc94d9b..10c51c85d 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -2,24 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - [[package]] name = "aquamarine" version = "0.6.0" @@ -31,50 +13,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] -name = "askama" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" -dependencies = [ - "askama_derive", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "syn 2.0.104", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", - "winnow 0.7.12", + "syn", ] [[package]] @@ -85,9 +24,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "az" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" [[package]] name = "bare-metal" @@ -107,7 +46,7 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", @@ -122,7 +61,7 @@ name = "battery-service-messages" version = "0.1.0" dependencies = [ "defmt 0.3.100", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-services", "num_enum", ] @@ -164,22 +103,22 @@ checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" [[package]] name = "bitfield" -version = "0.19.1" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db1bcd90f88eabbf0cadbfb87a45bceeaebcd3b4bc9e43da379cd2ef0162590d" +checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.19.1" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3787a07661997bfc05dd3431e379c0188573f78857080cf682e1393ab8e4d64c" +checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -190,7 +129,7 @@ checksum = "2be5a46ba01b60005ae2c51a36a29cfe134bcacae2dd5cedcd4615fbaad1494b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -201,7 +140,7 @@ checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -212,9 +151,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -230,23 +169,23 @@ dependencies = [ [[package]] name = "bq25773" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/bq25773#20bc26219b5372bc6146cdc509c21f6d43e257b3" +version = "0.1.1" +source = "git+https://github.com/OpenDevicePartnership/bq25773#0ca102dec24a617df5762cf0bf4217fd387d5c63" dependencies = [ "device-driver", - "embedded-batteries-async 0.2.1", + "embedded-batteries-async", "embedded-hal 1.0.0", "embedded-hal-async", ] [[package]] name = "bq40z50-rx" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55262eaa8d376e3db634b2cf851d2f255fbe86076abc3d4f8088248340836bb6" +checksum = "09b6faf600295f12c3fb99b45266bc9140af5c344b08f2705bc06bfa0e8b549e" dependencies = [ "device-driver", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-hal 1.0.0", "embedded-hal-async", "smbus-pec", @@ -254,9 +193,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -266,18 +205,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cortex-m" @@ -309,7 +239,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -345,7 +275,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn", ] [[package]] @@ -356,25 +286,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", -] - -[[package]] -name = "dd-manifest-tree" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" -dependencies = [ - "yaml-rust2", -] - -[[package]] -name = "debug-service-messages" -version = "0.1.0" -dependencies = [ - "defmt 0.3.100", - "embedded-services", - "num_enum", + "syn", ] [[package]] @@ -406,7 +318,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -415,7 +327,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -430,49 +342,19 @@ dependencies = [ [[package]] name = "device-driver" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1298272ea07037196af2fe8d1eb50792206f45476d79eefa435432b9323cf488" +checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ - "device-driver-macros", "embedded-io", "embedded-io-async", ] -[[package]] -name = "device-driver-generation" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86a17ed060a6119daeb05ad5596ac3bd945f7ab2213cc6260bf6a7623e73da1" -dependencies = [ - "anyhow", - "askama", - "bitvec", - "convert_case", - "dd-manifest-tree", - "itertools 0.14.0", - "kdl", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "device-driver-macros" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c29238099c66bf44098efaa772cae6f47d632aebb7ade8d3087bd565e8fae0" -dependencies = [ - "device-driver-generation", - "proc-macro2", - "syn 2.0.104", -] - [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -524,7 +406,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -646,7 +528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" dependencies = [ "bitfield-struct 0.10.1", - "bitflags 2.9.1", + "bitflags 2.11.0", "defmt 0.3.100", "embedded-hal 1.0.0", "zerocopy", @@ -654,28 +536,17 @@ dependencies = [ [[package]] name = "embedded-batteries" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8996d7168535579180a0eead82efaba718ebd598782f986bfd635458259df2" +checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" dependencies = [ - "bitfield-struct 0.10.1", - "bitflags 2.9.1", + "bitfield-struct 0.12.1", + "bitflags 2.11.0", "defmt 0.3.100", "embedded-hal 1.0.0", "zerocopy", ] -[[package]] -name = "embedded-batteries-async" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cb543f4eea7e2c57544f345a5cf40fd90e9d3593b96cb7515f6c1d62c7fc68" -dependencies = [ - "bitfield-struct 0.10.1", - "embedded-batteries 0.2.1", - "embedded-hal 1.0.0", -] - [[package]] name = "embedded-batteries-async" version = "0.3.4" @@ -684,14 +555,14 @@ checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" dependencies = [ "bitfield-struct 0.12.1", "defmt 0.3.100", - "embedded-batteries 0.3.1", + "embedded-batteries 0.3.4", "embedded-hal 1.0.0", ] [[package]] name = "embedded-cfu-protocol" version = "0.2.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" +source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" dependencies = [ "defmt 0.3.100", "embedded-io-async", @@ -767,7 +638,7 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitflags 2.9.1", + "bitflags 2.11.0", "bitvec", "cfg-if", "cortex-m", @@ -814,27 +685,18 @@ source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce dependencies = [ "aquamarine", "bincode", - "bitfield 0.19.1", + "bitfield 0.19.4", "defmt 0.3.100", "embedded-hal-async", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#e9c43ec493ba9c4e3db84c73530f919448c07b6d" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#9805f13c044b0e314d415410c57a8a59a40eabeb" dependencies = [ "bit-register", - "bitflags 2.9.1", + "bitflags 2.11.0", "num-traits", "num_enum", "static_assertions", @@ -845,11 +707,9 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ - "battery-service-messages", "bitfield 0.17.0", "cortex-m", "cortex-m-rt", - "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-imxrt", @@ -858,15 +718,14 @@ dependencies = [ "embedded-services", "mctp-rs", "num_enum", - "thermal-service-messages", - "time-alarm-service-messages", + "odp-service-common", ] [[package]] name = "fixed" -version = "1.29.0" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +checksum = "c566da967934c6c7ee0458a9773de9b2a685bd2ce26a3b28ddfc740e640182f5" dependencies = [ "az", "bytemuck", @@ -888,9 +747,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -902,9 +761,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -912,61 +771,61 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", "futures-sink", "futures-task", "pin-project-lite", - "pin-utils", ] [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -978,24 +837,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown", -] - [[package]] name = "heapless" version = "0.8.0" @@ -1008,9 +849,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "ident_case" @@ -1055,38 +896,11 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "kdl" -version = "6.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661358400b02cbbf1fbd05f0a483335490e8a6bd1867620f2eeb78f304a22f" -dependencies = [ - "miette", - "num", - "thiserror 1.0.69", - "winnow 0.6.24", -] - [[package]] name = "litrs" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "mctp-rs" @@ -1099,35 +913,7 @@ dependencies = [ "espi-device", "num_enum", "smbus-pec", - "thiserror 2.0.16", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "thiserror", ] [[package]] @@ -1180,70 +966,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1271,14 +993,16 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +name = "odp-service-common" +version = "0.1.0" +dependencies = [ + "embedded-services", + "static_cell", +] [[package]] name = "panic-probe" @@ -1296,29 +1020,17 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "power-policy-interface" @@ -1328,7 +1040,7 @@ dependencies = [ "defmt 0.3.100", "embassy-futures", "embassy-sync", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-services", "heapless", "num_enum", @@ -1353,23 +1065,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1418,7 +1130,7 @@ dependencies = [ "embassy-imxrt", "embassy-sync", "embassy-time", - "embedded-batteries-async 0.3.4", + "embedded-batteries-async", "embedded-hal-async", "embedded-services", "espi-service", @@ -1430,12 +1142,6 @@ dependencies = [ "static_cell", ] -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.2.3" @@ -1451,12 +1157,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "semver" version = "0.9.0" @@ -1474,34 +1174,32 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "serde_derive", ] [[package]] -name = "serde_json" -version = "1.0.141" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1515,9 +1213,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -1547,32 +1245,21 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subenum" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5d5dfb8556dd04017db5e318bbeac8ab2b0c67b76bf197bfb79e9b29f18ecf" +checksum = "ec3d08fe7078c57309d5c3d938e50eba95ba1d33b9c3a101a8465fc6861a5416" dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "syn" -version = "1.0.109" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1585,91 +1272,37 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "thermal-service-messages" -version = "0.1.0" -dependencies = [ - "defmt 0.3.100", - "embedded-services", - "num_enum", - "uuid", -] - [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" -dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "time-alarm-service-messages" -version = "0.1.0" -dependencies = [ - "bitfield 0.17.0", - "defmt 0.3.100", - "embedded-mcu-hal", - "embedded-services", - "num_enum", - "zerocopy", + "syn", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unty" @@ -1689,12 +1322,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "void" version = "1.0.2" @@ -1710,24 +1337,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "winnow" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" -dependencies = [ - "memchr", -] - [[package]] name = "wyz" version = "0.5.1" @@ -1737,33 +1346,22 @@ dependencies = [ "tap", ] -[[package]] -name = "yaml-rust2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink", -] - [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index b01a71d86..f227b319d 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1231,6 +1231,14 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "odp-service-common" +version = "0.1.0" +dependencies = [ + "embedded-services", + "static_cell", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1409,6 +1417,7 @@ dependencies = [ "futures", "mimxrt600-fcb 0.1.0", "mimxrt685s-pac 0.4.0", + "odp-service-common", "panic-probe", "platform-service", "power-button-service", @@ -1628,6 +1637,7 @@ dependencies = [ "embassy-time", "embedded-mcu-hal", "embedded-services", + "odp-service-common", "time-alarm-service-messages", "zerocopy", ] diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 4c5310c5d..18c0f7956 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -51,6 +51,7 @@ mimxrt685s-pac = { version = "*", features = ["rt", "critical-section"] } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } embedded-services = { path = "../../embedded-service", features = ["defmt"] } +odp-service-common = { path = "../../odp-service-common" } power-button-service = { path = "../../power-button-service", features = [ "defmt", ] } diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 9b1a8182a..b3ea08084 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] -use embassy_sync::once_lock::OnceLock; use embedded_mcu_hal::{ Nvram, time::{Datetime, Month, UncheckedDatetime}, @@ -24,25 +23,19 @@ async fn main(spawner: embassy_executor::Spawner) { embedded_services::init().await; info!("services initialized"); - static TIME_SERVICE: OnceLock = OnceLock::new(); - let time_service = time_alarm_service::Service::init( - &TIME_SERVICE, - dt_clock, - tz, - ac_expiration, - ac_policy, - dc_expiration, - dc_policy, + let time_service = odp_service_common::spawn_service!( + spawner, + time_alarm_service::Service<'static>, + time_alarm_service::InitParams { + backing_clock: dt_clock, + tz_storage: tz, + ac_expiration_storage: ac_expiration, + ac_policy_storage: ac_policy, + dc_expiration_storage: dc_expiration, + dc_policy_storage: dc_policy + } ) - .await - .expect("Failed to initialize time-alarm service"); - - #[embassy_executor::task] - async fn time_alarm_task(service: &'static time_alarm_service::Service<'static>) { - time_alarm_service::task::run_service(service).await - } - - spawner.must_spawn(time_alarm_task(time_service)); + .expect("Failed to spawn time alarm service"); use embedded_services::relay::mctp::impl_odp_mctp_relay_handler; impl_odp_mctp_relay_handler!( @@ -50,12 +43,11 @@ async fn main(spawner: embassy_executor::Spawner) { TimeAlarm, 0x0B, time_alarm_service::Service<'static>; ); - let _relay_handler = EspiRelayHandler::new(time_service); + let _relay_handler = EspiRelayHandler::new(&time_service); // Here, you'd normally pass _relay_handler to your relay service (e.g. eSPI service). // In this example, we're not leveraging a relay service, so we'll just demonstrate some direct calls. // - time_service .set_real_time(AcpiTimestamp { datetime: Datetime::new(UncheckedDatetime { diff --git a/odp-service-common/Cargo.toml b/odp-service-common/Cargo.toml new file mode 100644 index 000000000..ef6b5a46d --- /dev/null +++ b/odp-service-common/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "odp-service-common" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +embedded-services.workspace = true +static_cell.workspace = true + +[lints] +workspace = true diff --git a/odp-service-common/src/lib.rs b/odp-service-common/src/lib.rs new file mode 100644 index 000000000..6ae7498ca --- /dev/null +++ b/odp-service-common/src/lib.rs @@ -0,0 +1,4 @@ +//! This crate contains code that is common to multiple ODP service implementations. +#![no_std] + +pub mod runnable_service; diff --git a/odp-service-common/src/runnable_service.rs b/odp-service-common/src/runnable_service.rs new file mode 100644 index 000000000..d65100249 --- /dev/null +++ b/odp-service-common/src/runnable_service.rs @@ -0,0 +1,83 @@ +//! This module contains helper traits and functions for services that run on the EC. + +/// A trait for a service that requires the caller to launch a long-running task on its behalf to operate. +pub trait Service<'hw>: Sized { + /// A type that can be used to run the service. This is returned by the new() function and the user is + /// expected to call its run() method in an embassy task (or similar parallel execution context on other + /// async runtimes). + type Runner: ServiceRunner<'hw>; + + /// Any memory resources that your service needs. This is typically an opaque type that is only used by the service + /// and is not interacted with by users of the service. Must be default-constructible for spawn_service!() to work. + type Resources: Default; + + /// The error type that your `new` function can return on failure. + type ErrorType; + + /// Any initialization parameters that your service needs to run. + type InitParams; + + /// Initializes an instance of the service using the provided storage and returns a control handle for the service and + /// a runner that can be used to run the service. + fn new( + storage: &'hw mut Self::Resources, + params: Self::InitParams, + ) -> impl core::future::Future>; +} + +/// A trait for a run handle used to execute a service's event loop. This is returned by Service::new() +/// and the user is expected to call its run() method in an embassy task (or similar parallel execution context +/// on other async runtimes). +pub trait ServiceRunner<'hw> { + /// Run the service event loop. This future never completes. + fn run(self) -> impl core::future::Future + 'hw; +} + +/// Initializes a service, creates an embassy task to run it, and spawns that task. +/// +/// This macro handles the boilerplate of: +/// 1. Creating a `static` [`StaticCell`](static_cell::StaticCell) to hold the service +/// 2. Calling the service's `new()` method +/// 3. Defining an embassy_executor::task to run the service +/// 4. Spawning the task on the provided executor +/// +/// Returns a Result<&Service, Error> where Error is the error type of $service_ty::new(). +/// +/// Arguments +/// +/// - spawner: An embassy_executor::Spawner. +/// - service_ty: The service type that implements Service that you want to create and run. +/// - init_arg: The init argument type to pass to `Service::new()` +/// +/// Example: +/// +/// ```ignore +/// let time_service = odp_service_common::runnable_service::spawn_service!( +/// spawner, +/// time_alarm_service::Service<'static>, +/// time_alarm_service::ServiceInitParams { dt_clock, tz, ac_expiration, ac_policy, dc_expiration, dc_policy } +/// ).expect("failed to initialize time_alarm service"); +/// ``` +#[macro_export] +macro_rules! spawn_service { + ($spawner:expr, $service_ty:ty, $init_arg:expr) => {{ + use $crate::runnable_service::{Service, ServiceRunner}; + static SERVICE_RESOURCES: static_cell::StaticCell<(<$service_ty as Service>::Resources)> = + static_cell::StaticCell::new(); + let service_resources = SERVICE_RESOURCES.init(<<$service_ty as Service>::Resources as Default>::default()); + + #[embassy_executor::task] + async fn service_task_fn(runner: <$service_ty as $crate::runnable_service::Service<'static>>::Runner) { + runner.run().await; + } + + <$service_ty>::new(service_resources, $init_arg) + .await + .map(|(control_handle, runner)| { + $spawner.must_spawn(service_task_fn(runner)); + control_handle + }) + }}; +} + +pub use spawn_service; diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index a1e1d12b1..2b2c4af41 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -16,6 +16,7 @@ embassy-sync.workspace = true embassy-time.workspace = true embedded-mcu-hal.workspace = true embedded-services.workspace = true +odp-service-common.workspace = true time-alarm-service-messages.workspace = true zerocopy.workspace = true @@ -36,6 +37,6 @@ workspace = true [dev-dependencies] tokio = { workspace = true, features = ["rt", "macros", "time"] } -critical-section = { version = "1.1", features = ["std"]} +critical-section = { version = "1.1", features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } embassy-time-driver = { workspace = true } diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 333420ae6..84613cef2 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -2,7 +2,6 @@ use core::cell::RefCell; use embassy_sync::blocking_mutex::Mutex; -use embassy_sync::once_lock::OnceLock; use embassy_sync::signal::Signal; use embedded_mcu_hal::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; @@ -10,7 +9,6 @@ use embedded_services::GlobalRawMutex; use embedded_services::{info, warn}; use time_alarm_service_messages::*; -pub mod task; mod timer; use timer::Timer; @@ -104,7 +102,19 @@ impl<'hw> Timers<'hw> { // ------------------------------------------------- -pub struct Service<'hw> { +/// Parameters required to initialize the time/alarm service. +pub struct InitParams<'hw> { + pub backing_clock: &'hw mut dyn DatetimeClock, + pub tz_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub ac_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, +} + +/// The main service implementation. Users will interact with this via the Service struct, which is a thin wrapper around this that allows +/// the client to provide storage for the service. +struct ServiceInner<'hw> { clock_state: Mutex>>, // TODO [POWER_SOURCE] signal this whenever the power source changes @@ -115,29 +125,19 @@ pub struct Service<'hw> { capabilities: TimeAlarmDeviceCapabilities, } -impl<'hw> Service<'hw> { - pub async fn init( - service_storage: &'hw OnceLock>, - backing_clock: &'hw mut impl DatetimeClock, - tz_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ac_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, - dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, - dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ) -> Result<&'hw Service<'hw>, DatetimeClockError> { - info!("Starting time-alarm service task"); - - let service = service_storage.get_or_init(|| Service { +impl<'hw> ServiceInner<'hw> { + fn new(init_params: InitParams<'hw>) -> Self { + Self { clock_state: Mutex::new(RefCell::new(ClockState { - datetime_clock: backing_clock, - tz_data: TimeZoneData::new(tz_storage), + datetime_clock: init_params.backing_clock, + tz_data: TimeZoneData::new(init_params.tz_storage), })), power_source_signal: Signal::new(), timers: Timers::new( - ac_expiration_storage, - ac_policy_storage, - dc_expiration_storage, - dc_policy_storage, + init_params.ac_expiration_storage, + init_params.ac_policy_storage, + init_params.dc_expiration_storage, + init_params.dc_policy_storage, ), capabilities: { // TODO [CONFIG] We could consider making some of these user-configurable, e.g. if we want to support devices that don't have a battery @@ -153,23 +153,16 @@ impl<'hw> Service<'hw> { caps.set_dc_s5_wake_supported(true); caps }, - }); - - // TODO [POWER_SOURCE] we need to subscribe to messages that tell us if we're on AC or DC power so we can decide which alarms to trigger, but those notifications are not yet implemented - revisit when they are. - // TODO [POWER_SOURCE] if it's possible to learn which power source is active at init time, we should set that one active rather than defaulting to the AC timer. - service.timers.ac_timer.start(&service.clock_state, true)?; - service.timers.dc_timer.start(&service.clock_state, false)?; - - Ok(service) + } } /// Query clock capabilities. Analogous to ACPI TAD's _GRT method. - pub fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { + fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { self.capabilities } /// Query the current time. Analogous to ACPI TAD's _GRT method. - pub fn get_real_time(&self) -> Result { + fn get_real_time(&self) -> Result { self.clock_state.lock(|clock_state| { let clock_state = clock_state.borrow(); let datetime = clock_state.datetime_clock.get_current_datetime()?; @@ -183,7 +176,7 @@ impl<'hw> Service<'hw> { } /// Change the current time. Analogous to ACPI TAD's _SRT method. - pub fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { + fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { self.clock_state.lock(|clock_state| { let mut clock_state = clock_state.borrow_mut(); clock_state.datetime_clock.set_current_datetime(×tamp.datetime)?; @@ -193,17 +186,17 @@ impl<'hw> Service<'hw> { } /// Query the current wake status. Analogous to ACPI TAD's _GWS method. - pub fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus { + fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus { self.timers.get_timer(timer_id).get_wake_status() } /// Clear the current wake status. Analogous to ACPI TAD's _CWS method. - pub fn clear_wake_status(&self, timer_id: AcpiTimerId) { + fn clear_wake_status(&self, timer_id: AcpiTimerId) { self.timers.get_timer(timer_id).clear_wake_status(); } /// Configures behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _STP method. - pub fn set_expired_timer_policy( + fn set_expired_timer_policy( &self, timer_id: AcpiTimerId, policy: AlarmExpiredWakePolicy, @@ -215,16 +208,12 @@ impl<'hw> Service<'hw> { } /// Query current behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _TIP method. - pub fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy { + fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy { self.timers.get_timer(timer_id).get_timer_wake_policy() } /// Change the expiry time for the given timer. Analogous to ACPI TAD's _STV method. - pub fn set_timer_value( - &self, - timer_id: AcpiTimerId, - timer_value: AlarmTimerSeconds, - ) -> Result<(), DatetimeClockError> { + fn set_timer_value(&self, timer_id: AcpiTimerId, timer_value: AlarmTimerSeconds) -> Result<(), DatetimeClockError> { let new_expiration_time = match timer_value { AlarmTimerSeconds::DISABLED => None, AlarmTimerSeconds(secs) => { @@ -245,7 +234,7 @@ impl<'hw> Service<'hw> { } /// Query the expiry time for the given timer. Analogous to ACPI TAD's _TIV method. - pub fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result { + fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result { let expiration_time = self.timers.get_timer(timer_id).get_expiration_time(); match expiration_time { Some(expiration_time) => { @@ -263,17 +252,6 @@ impl<'hw> Service<'hw> { } } - pub(crate) async fn run_service(&'hw self) -> ! { - loop { - embassy_futures::select::select3( - self.handle_power_source_updates(), - self.handle_timer(AcpiTimerId::AcPower), - self.handle_timer(AcpiTimerId::DcPower), - ) - .await; - } - } - async fn handle_power_source_updates(&'hw self) -> ! { loop { let new_power_source = self.power_source_signal.wait().await; @@ -311,6 +289,111 @@ impl<'hw> Service<'hw> { } } +/// The memory resources required by the time/alarm service. +#[derive(Default)] +pub struct Resources<'hw> { + inner: Option>, +} + +/// A task runner for the time/alarm service. Users of the service must run this object in an embassy task or similar async execution context. +pub struct Runner<'hw> { + service: &'hw ServiceInner<'hw>, +} + +impl<'hw> odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw> { + /// Run the service. + async fn run(self) -> embedded_services::Never { + loop { + embassy_futures::select::select3( + self.service.handle_power_source_updates(), + self.service.handle_timer(AcpiTimerId::AcPower), + self.service.handle_timer(AcpiTimerId::DcPower), + ) + .await; + } + } +} + +/// Control handle for the time-alarm service. Use this to manipulate the time on the service. +pub struct Service<'hw> { + inner: &'hw ServiceInner<'hw>, +} + +impl<'hw> Service<'hw> { + pub fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { + self.inner.get_capabilities() + } + + /// Query the current time. Analogous to ACPI TAD's _GRT method. + pub fn get_real_time(&self) -> Result { + self.inner.get_real_time() + } + + /// Change the current time. Analogous to ACPI TAD's _SRT method. + pub fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { + self.inner.set_real_time(timestamp) + } + + /// Query the current wake status. Analogous to ACPI TAD's _GWS method. + pub fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus { + self.inner.get_wake_status(timer_id) + } + + /// Clear the current wake status. Analogous to ACPI TAD's _CWS method. + pub fn clear_wake_status(&self, timer_id: AcpiTimerId) { + self.inner.clear_wake_status(timer_id); + } + + /// Configures behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _STP method. + pub fn set_expired_timer_policy( + &self, + timer_id: AcpiTimerId, + policy: AlarmExpiredWakePolicy, + ) -> Result<(), DatetimeClockError> { + self.inner.set_expired_timer_policy(timer_id, policy) + } + + /// Query current behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _TIP method. + pub fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy { + self.inner.get_expired_timer_policy(timer_id) + } + + /// Change the expiry time for the given timer. Analogous to ACPI TAD's _STV method. + pub fn set_timer_value( + &self, + timer_id: AcpiTimerId, + timer_value: AlarmTimerSeconds, + ) -> Result<(), DatetimeClockError> { + self.inner.set_timer_value(timer_id, timer_value) + } + + /// Query the expiry time for the given timer. Analogous to ACPI TAD's _TIV method. + pub fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result { + self.inner.get_timer_value(timer_id) + } +} + +impl<'hw> odp_service_common::runnable_service::Service<'hw> for Service<'hw> { + type Runner = Runner<'hw>; + type ErrorType = DatetimeClockError; + type InitParams = InitParams<'hw>; + type Resources = Resources<'hw>; + + async fn new( + service_storage: &'hw mut Resources<'hw>, + init_params: Self::InitParams, + ) -> Result<(Self, Runner<'hw>), DatetimeClockError> { + let service = service_storage.inner.insert(ServiceInner::new(init_params)); + + // TODO [POWER_SOURCE] we need to subscribe to messages that tell us if we're on AC or DC power so we can decide which alarms to trigger, but those notifications are not yet implemented - revisit when they are. + // TODO [POWER_SOURCE] if it's possible to learn which power source is active at init time, we should set that one active rather than defaulting to the AC timer. + service.timers.ac_timer.start(&service.clock_state, true)?; + service.timers.dc_timer.start(&service.clock_state, false)?; + + Ok((Self { inner: service }, Runner { service })) + } +} + impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { type RequestType = AcpiTimeAlarmRequest; type ResultType = AcpiTimeAlarmResult; diff --git a/time-alarm-service/src/task.rs b/time-alarm-service/src/task.rs deleted file mode 100644 index a45fdf62f..000000000 --- a/time-alarm-service/src/task.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::Service; -use embedded_services::info; - -/// Call this from a dedicated async task. Must be called exactly once on each service instance. -/// Note that on-device, 'hw must be 'static. We're generic over 'hw to enable some test scenarios leveraging tokio and mocks. -pub async fn run_service<'hw>(service: &'hw Service<'hw>) -> ! { - info!("Starting time-alarm service task"); - service.run_service().await -} diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index dc3d43d8e..25deaa481 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -6,9 +6,9 @@ mod common; #[cfg(test)] mod test { - use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock}; + use odp_service_common::runnable_service::{Service, ServiceRunner}; use time_alarm_service_messages as msg; @@ -23,16 +23,18 @@ mod test { let mut dc_pol_storage = MockNvramStorage::new(0); let mut clock = MockDatetimeClock::new_running(); + let mut storage = Default::default(); - let storage = OnceLock::new(); - let service = time_alarm_service::Service::init( - &storage, - &mut clock, - &mut tz_storage, - &mut ac_exp_storage, - &mut ac_pol_storage, - &mut dc_exp_storage, - &mut dc_pol_storage, + let (service, runner) = time_alarm_service::Service::new( + &mut storage, + time_alarm_service::InitParams { + backing_clock: &mut clock, + tz_storage: &mut tz_storage, + ac_expiration_storage: &mut ac_exp_storage, + ac_policy_storage: &mut ac_pol_storage, + dc_expiration_storage: &mut dc_exp_storage, + dc_policy_storage: &mut dc_pol_storage, + }, ) .await .unwrap(); @@ -45,7 +47,7 @@ mod test { // return !, so we should go until the test arm completes and then shut down. // tokio::select! { - _ = time_alarm_service::task::run_service(service) => unreachable!("time alarm service task finished unexpectedly"), + _ = runner.run() => unreachable!("time alarm service task finished unexpectedly"), _ = async { let delay_secs = 2; let begin = service.get_real_time().unwrap(); @@ -73,21 +75,24 @@ mod test { .set_current_datetime(&Datetime::from_unix_time_seconds(TEST_UNIX_TIME)) .unwrap(); - let storage = OnceLock::new(); - let service = time_alarm_service::Service::init( - &storage, - &mut clock, - &mut tz_storage, - &mut ac_exp_storage, - &mut ac_pol_storage, - &mut dc_exp_storage, - &mut dc_pol_storage, + let mut storage = Default::default(); + + let (service, runner) = time_alarm_service::Service::new( + &mut storage, + time_alarm_service::InitParams { + backing_clock: &mut clock, + tz_storage: &mut tz_storage, + ac_expiration_storage: &mut ac_exp_storage, + ac_policy_storage: &mut ac_pol_storage, + dc_expiration_storage: &mut dc_exp_storage, + dc_policy_storage: &mut dc_pol_storage, + }, ) .await .unwrap(); tokio::select! { - _ = time_alarm_service::task::run_service(service) => unreachable!("time alarm service task finished unexpectedly"), + _ = runner.run() => unreachable!("time alarm service task finished unexpectedly"), _ = async { // Clock is paused, so time shouldn't advance unless we set it. let begin = service.get_real_time().unwrap(); From 0ebad079501b7736119bae68fac93b8c209a5d34 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:39:32 -0700 Subject: [PATCH 28/79] API guideline fixes (#746) A few fixes for some stuff that slipped through in https://github.com/OpenDevicePartnership/embedded-services/pull/740. Mostly style / example syntax stuff, and added a note about event ordering when adopting the runner pattern --- docs/api-guidelines.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/api-guidelines.md b/docs/api-guidelines.md index b6e7e75af..1918abf68 100644 --- a/docs/api-guidelines.md +++ b/docs/api-guidelines.md @@ -94,11 +94,11 @@ struct FooInner<'hw> { /* .. */ } #[derive(Default)] pub struct Resources<'hw> { - inner: Option + inner: Option> } pub struct Foo<'hw> { - inner: &'hw FooInner + inner: &'hw FooInner<'hw> } impl<'hw> Foo<'hw> { @@ -137,17 +137,17 @@ impl<'hw> MyRunnableTypeInner<'hw> { #[derive(Default)] pub struct Resources<'hw> { - inner: Option + inner: Option> } pub struct MyRunnableType<'hw> { - inner: &'hw MyRunnableTypeInner + inner: &'hw MyRunnableTypeInner<'hw> } impl<'hw> MyRunnableType<'hw> { pub fn new(resources: &mut Resources, /* .. */ ) -> Self { - let inner = resources.insert(RunnableTypeInner::new(/* .. */)) - /* .. */ + let inner = resources.insert(RunnableTypeInner::new(/* .. */)); + /* .. */ Self { inner } } } @@ -195,15 +195,15 @@ impl<'hw> MyRunnableTypeInner<'hw> { #[derive(Default)] pub struct Resources<'hw> { - inner: Option + inner: Option> } pub struct MyRunnableType<'hw> { - inner: &'hw MyRunnableTypeInner + inner: &'hw MyRunnableTypeInner<'hw> } pub struct Runner<'hw> { - inner: &'hw MyRunnableTypeInner, + inner: &'hw MyRunnableTypeInner<'hw>, foo: Foo, bar: Bar, baz: Baz @@ -242,11 +242,13 @@ fn main() { ``` Notice that most of the complexity has been moved into internal implementation details and the client doesn't have to think about it. Also notice that if you want to add a new 'green thread', or change what state is available to which 'green threads' you can do that entirely in private code in the `run()` method, without requiring changes to your client. +Note that this can change the order in which your tasks are scheduled compared to having dedicated tasks for each worker function. If your code was making any assumptions about a specific task scheduling order among these worker functions, this will break those assumptions. Ensure that if you need to emit events from your service that need to be emitted in a specific order, you explicitly enforce that ordering in your code (e.g. via emitting all events from the same task or perhaps coordinating between tasks with channels or queues). + ### Use traits for public methods expected to be used at run time whenever possible In most cases, public APIs in this repo should be exposed in terms of traits rather than methods directly on the object, and objects that need to interact with other embedded-services objects should refer to them by trait rather than by name. This does not apply to public methods used to construct or initialize a service, because those generally need to know something about the concrete implementation type to properly initialize it. -These traits should be defined in standalone 'interface' crates (i.e. `battery-service-interface`) alongside any support types needed for the interface (e.g. an Error enum) +These traits should be defined in standalone 'interface' crates (e.g. `battery-service-interface`) alongside any support types needed for the interface (e.g. an Error enum). __Reason__: Improved testability and customizability. Testability - if all our types interact with each other via traits rather than direct dependencies on the type, it makes it much easier to mock out individual components. @@ -280,4 +282,4 @@ impl embedded_services::ExampleService for OdpExampleService { fn bar(&mut self) -> Result<()> { /* .. */ } fn baz(&mut self) -> Result<()> { /* .. */ } } -``` \ No newline at end of file +``` From 663e243da824e54a7f14655689bce69cdbca4e9b Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Tue, 17 Mar 2026 14:47:46 -0700 Subject: [PATCH 29/79] time-alarm-service: Add no_std mock support (#755) This refactors the time and alarm service mocks slightly by making them both std friendly (for `cargo test`ing) and no_std friendly (for use on our EC platforms and testing with our ec-test-app), as well as following the same pattern as battery and thermal. So, mock was moved out of the test folder into the main crate src so it can be used externally too. --- Cargo.lock | 1 + time-alarm-service/Cargo.toml | 2 + time-alarm-service/src/lib.rs | 4 +- .../{tests/common/mocks.rs => src/mock.rs} | 73 ++++++++++--------- time-alarm-service/tests/common/mod.rs | 1 - time-alarm-service/tests/tad_test.rs | 4 +- 6 files changed, 45 insertions(+), 40 deletions(-) rename time-alarm-service/{tests/common/mocks.rs => src/mock.rs} (55%) delete mode 100644 time-alarm-service/tests/common/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c875565c7..7f1362236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2352,6 +2352,7 @@ dependencies = [ "embedded-services", "log", "odp-service-common", + "time-alarm-service", "time-alarm-service-messages", "tokio", "zerocopy", diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index 2b2c4af41..7109bf2f3 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -31,11 +31,13 @@ defmt = [ ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] +mock = [] [lints] workspace = true [dev-dependencies] +time-alarm-service = { path = ".", features = ["mock"] } tokio = { workspace = true, features = ["rt", "macros", "time"] } critical-section = { version = "1.1", features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 84613cef2..0ff0b9389 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] use core::cell::RefCell; use embassy_sync::blocking_mutex::Mutex; @@ -11,6 +11,8 @@ use time_alarm_service_messages::*; mod timer; use timer::Timer; +#[cfg(feature = "mock")] +pub mod mock; // ------------------------------------------------- diff --git a/time-alarm-service/tests/common/mocks.rs b/time-alarm-service/src/mock.rs similarity index 55% rename from time-alarm-service/tests/common/mocks.rs rename to time-alarm-service/src/mock.rs index 1b88661b0..2bc9f3086 100644 --- a/time-alarm-service/tests/common/mocks.rs +++ b/time-alarm-service/src/mock.rs @@ -2,50 +2,62 @@ use embedded_mcu_hal::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; -use std::time::SystemTime; -pub(crate) enum MockDatetimeClock { - Running { seconds_offset_from_system_time: i64 }, +// Used for `cargo test` runs in an std environment +#[cfg(test)] +fn now_seconds() -> u64 { + // Panic safety: Only used in tests so panicking is acceptable here + #[allow(clippy::expect_used)] + std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("System clock was adjusted during test") + .as_secs() +} + +// Allows us to use this mock in no_std contexts +// Note: `get_current_datetime` will always reflect time as starting from the beginning +// of UNIX time (1970), and not current wall-clock time. This is sufficient for its use case +// since it provides a consistent baseline and allows time to advance, but is something to be aware of. +#[cfg(not(test))] +fn now_seconds() -> u64 { + embassy_time::Instant::now().as_secs() +} + +pub enum MockDatetimeClock { + Running { seconds_offset: i64 }, Paused { frozen_time: Datetime }, } impl MockDatetimeClock { - pub(crate) fn new_running() -> Self { - Self::Running { - seconds_offset_from_system_time: 0, - } + /// New `MockDatetimeClock` in which time is advancing. + pub fn new_running() -> Self { + Self::Running { seconds_offset: 0 } } - pub(crate) fn new_paused() -> Self { + /// New `MockDatetimeClock` in which time is paused. + pub fn new_paused() -> Self { Self::Paused { - frozen_time: Datetime::from_unix_time_seconds( - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("System clock was adjusted during test") - .as_secs(), - ), + frozen_time: Datetime::from_unix_time_seconds(now_seconds()), } } /// Stop time from advancing. - pub(crate) fn pause(&mut self) { + pub fn pause(&mut self) { if let Self::Running { .. } = self { *self = MockDatetimeClock::Paused { + // Panic safety: Mocks aren't used in production code, so panicking is acceptable here + #[allow(clippy::unwrap_used)] frozen_time: self.get_current_datetime().unwrap(), }; } } /// Resume time advancing. - pub(crate) fn resume(&mut self) { + pub fn resume(&mut self) { if let Self::Paused { frozen_time } = self { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("System clock was adjusted during test"); let target_secs = frozen_time.to_unix_time_seconds() as i64; - let adjusted_seconds = now.as_secs() as i64; *self = MockDatetimeClock::Running { - seconds_offset_from_system_time: target_secs - adjusted_seconds, + seconds_offset: target_secs - now_seconds() as i64, }; } } @@ -55,13 +67,8 @@ impl DatetimeClock for MockDatetimeClock { fn get_current_datetime(&self) -> Result { match self { MockDatetimeClock::Paused { frozen_time } => Ok(*frozen_time), - MockDatetimeClock::Running { - seconds_offset_from_system_time, - } => { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("System clock was adjusted during test"); - let adjusted_seconds = now.as_secs() as i64 + seconds_offset_from_system_time; + MockDatetimeClock::Running { seconds_offset } => { + let adjusted_seconds = now_seconds() as i64 + seconds_offset; Ok(Datetime::from_unix_time_seconds(adjusted_seconds as u64)) } } @@ -75,12 +82,8 @@ impl DatetimeClock for MockDatetimeClock { } MockDatetimeClock::Running { .. } => { let target_secs = datetime.to_unix_time_seconds() as i64; - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("System clock was adjusted during test"); - *self = MockDatetimeClock::Running { - seconds_offset_from_system_time: target_secs - (now.as_secs() as i64), + seconds_offset: target_secs - now_seconds() as i64, }; Ok(()) } @@ -92,13 +95,13 @@ impl DatetimeClock for MockDatetimeClock { } } -pub(crate) struct MockNvramStorage<'a> { +pub struct MockNvramStorage<'a> { value: u32, _phantom: core::marker::PhantomData<&'a ()>, } impl<'a> MockNvramStorage<'a> { - pub(crate) fn new(initial_value: u32) -> Self { + pub fn new(initial_value: u32) -> Self { Self { value: initial_value, _phantom: core::marker::PhantomData, diff --git a/time-alarm-service/tests/common/mod.rs b/time-alarm-service/tests/common/mod.rs deleted file mode 100644 index 64bafca62..000000000 --- a/time-alarm-service/tests/common/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod mocks; diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index 25deaa481..73a92d09c 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -2,8 +2,6 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)] -mod common; - #[cfg(test)] mod test { use embassy_time::Timer; @@ -12,7 +10,7 @@ mod test { use time_alarm_service_messages as msg; - use crate::common::mocks::*; + use time_alarm_service::mock::*; #[tokio::test] async fn test_get_time() { From a83424bbf918d21a7432c90504350fcb755b95a4 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 11 Mar 2026 10:56:38 -0700 Subject: [PATCH 30/79] power-power-service: Loosen lifetimes Rename the current `'a` lifetime to `'device` for clarity. Introduce a separate `device_storage` lifetime since a single lifetime overconstrains things and can lead to situations where borrows live too long and lead to issues with drops. --- examples/rt685s-evk/src/bin/type_c.rs | 2 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 2 +- examples/std/src/bin/power_policy.rs | 7 +++--- examples/std/src/bin/type_c/service.rs | 2 +- examples/std/src/bin/type_c/ucsi.rs | 2 +- examples/std/src/bin/type_c/unconstrained.rs | 2 +- power-policy-service/src/service/consumer.rs | 11 ++++++--- power-policy-service/src/service/mod.rs | 26 +++++++++++--------- power-policy-service/src/service/provider.rs | 6 ++--- power-policy-service/src/service/task.rs | 10 +++++--- power-policy-service/tests/common/mod.rs | 10 ++++---- 11 files changed, 45 insertions(+), 35 deletions(-) diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 5232dcf07..9fb53158f 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -81,7 +81,7 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex>, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index f8ab5c200..df627a50b 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -165,7 +165,7 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex>, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 40e8a292f..f1b7406db 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -134,8 +134,9 @@ async fn run(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); - static SERVICE: StaticCell>> = - StaticCell::new(); + static SERVICE: StaticCell< + Mutex>, + > = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration.as_slice(), service_context, @@ -281,7 +282,7 @@ async fn power_policy_task( DeviceType, channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, - power_policy: &'static Mutex>, + power_policy: &'static Mutex>, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index a03d7c29e..c1665c7b5 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -144,7 +144,7 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex>, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 24d0cb9c6..8ac3e6138 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -180,7 +180,7 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex>, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 813e80527..b8dfce26e 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -284,7 +284,7 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex>, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index ea271d62c..4e0c3ea9f 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -46,12 +46,12 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl<'a, PSU: Lockable> Service<'a, PSU> +impl<'device, 'device_storage, PSU: Lockable> Service<'device, 'device_storage, PSU> where PSU::Inner: Psu, { /// Iterate over all devices to determine what is best power port provides the highest power - async fn find_best_consumer(&self) -> Result>, Error> { + async fn find_best_consumer(&self) -> Result>, Error> { let mut best_consumer = None; let current_consumer = self.state.current_consumer_state.as_ref().map(|f| f.psu); @@ -133,7 +133,10 @@ where } /// Common logic to execute after a consumer is connected - async fn post_consumer_connected(&mut self, connected_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + async fn post_consumer_connected( + &mut self, + connected_consumer: AvailableConsumer<'device, PSU>, + ) -> Result<(), Error> { self.state.current_consumer_state = Some(connected_consumer); // todo: review the delay time embassy_time::Timer::after_millis(800).await; @@ -191,7 +194,7 @@ where } /// Connect to a new consumer - async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'device, PSU>) -> Result<(), Error> { // Handle our current consumer if let Some(current_consumer) = self.state.current_consumer_state { if ptr::eq(current_consumer.psu, new_consumer.psu) diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 7ea2feee8..e8d8acd03 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -50,26 +50,30 @@ where } /// Power policy service -pub struct Service<'a, PSU: Lockable> +pub struct Service<'device, 'device_storage, PSU: Lockable> where PSU::Inner: Psu, { /// Power policy context - pub context: &'a context::Context, + pub context: &'device context::Context, /// PSU devices - psu_devices: &'a [&'a PSU], + psu_devices: &'device_storage [&'device PSU], /// State - state: InternalState<'a, PSU>, + state: InternalState<'device, PSU>, /// Config config: config::Config, } -impl<'a, PSU: Lockable> Service<'a, PSU> +impl<'device, 'device_storage, PSU: Lockable> Service<'device, 'device_storage, PSU> where PSU::Inner: Psu, { /// Create a new power policy - pub fn new(psu_devices: &'a [&'a PSU], context: &'a context::Context, config: config::Config) -> Self { + pub fn new( + psu_devices: &'device_storage [&'device PSU], + context: &'device context::Context, + config: config::Config, + ) -> Self { Self { context, psu_devices, @@ -137,7 +141,7 @@ where async fn process_request_provider_power_capabilities( &mut self, - requester: &'a PSU, + requester: &'device PSU, capability: Option, ) -> Result<(), Error> { { @@ -162,7 +166,7 @@ where self.connect_provider(requester).await } - async fn process_notify_disconnect(&mut self, device: &'a PSU) -> Result<(), Error> { + async fn process_notify_disconnect(&mut self, device: &'device PSU) -> Result<(), Error> { let mut locked_device = device.lock().await; info!("({}): Received notify disconnect", locked_device.name()); @@ -192,7 +196,7 @@ where } /// Send an event to all registered listeners - async fn broadcast_event(&mut self, _message: ServiceEvent<'a, PSU>) { + async fn broadcast_event(&mut self, _message: ServiceEvent<'device, PSU>) { // TODO: Add this back as part of the migration away from comms // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 } @@ -200,7 +204,7 @@ where /// Common logic for when a provider is disconnected /// /// Returns true if the device was operating as a provider - async fn remove_connected_provider(&mut self, psu: &'a PSU) -> bool { + async fn remove_connected_provider(&mut self, psu: &'device PSU) -> bool { if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; true @@ -209,7 +213,7 @@ where } } - pub async fn process_psu_event(&mut self, event: PsuEvent<'a, PSU>) -> Result<(), Error> { + pub async fn process_psu_event(&mut self, event: PsuEvent<'device, PSU>) -> Result<(), Error> { let device = event.psu; match event.event { PsuEventData::Attached => { diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index dd174d157..4671f595c 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -27,12 +27,12 @@ pub(super) struct State { state: PowerState, } -impl<'a, PSU: Lockable> Service<'a, PSU> +impl<'device, 'device_storage, PSU: Lockable> Service<'device, 'device_storage, PSU> where PSU::Inner: Psu, { /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&mut self, requester: &'a PSU) -> Result<(), Error> { + pub(super) async fn connect_provider(&mut self, requester: &'device PSU) -> Result<(), Error> { let requested_power_capability = { let requester = requester.lock().await; debug!("({}): Attempting to connect as provider", requester.name()); @@ -104,7 +104,7 @@ where } /// Common logic for after a provider has successfully connected - async fn post_provider_connected(&mut self, requester: &'a PSU, target_power: ProviderPowerCapability) { + async fn post_provider_connected(&mut self, requester: &'device PSU, target_power: ProviderPowerCapability) { let _ = self.state.connected_providers.insert(requester as *const PSU as usize); self.broadcast_event(ServiceEvent::ProviderConnected(requester, target_power)) .await; diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index d394957d9..aa9363ebd 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -8,17 +8,19 @@ use super::Service; /// Runs the power policy task. pub async fn task< - 'a, + 'device, + 'device_storage, const PSU_COUNT: usize, - S: Lockable>, + S: Lockable>, PSU: Lockable, R: Receiver, >( - mut psu_events: crate::psu::EventReceivers<'a, PSU_COUNT, PSU, R>, - policy: &'a S, + mut psu_events: crate::psu::EventReceivers<'device, PSU_COUNT, PSU, R>, + policy: &'device S, ) -> ! where PSU::Inner: Psu, + 'device: 'device_storage, { info!("Starting power policy task"); loop { diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 5ec320589..5c0ca9a68 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -38,12 +38,12 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); const EVENT_CHANNEL_SIZE: usize = 4; pub type DeviceType<'a> = Mutex>>; -pub type ServiceType<'a> = Service<'a, DeviceType<'a>>; +pub type ServiceType<'device, 'device_storage> = Service<'device, 'device_storage, DeviceType<'device>>; -async fn power_policy_task<'a, const N: usize>( - completion_signal: &'a Signal, - mut power_policy: ServiceType<'a>, - mut event_receivers: EventReceivers<'a, N, DeviceType<'a>, DynamicReceiver<'a, EventData>>, +async fn power_policy_task<'device, 'device_storage, const N: usize>( + completion_signal: &'device Signal, + mut power_policy: ServiceType<'device, 'device_storage>, + mut event_receivers: EventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { power_policy.process_psu_event(result).await.unwrap(); From cc1fb484aeafc25d35bcca193e6b9f01a729a2e4 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 11 Mar 2026 11:07:23 -0700 Subject: [PATCH 31/79] power-policy-interface: Introduce type-erased service event A receiver might only care that an event has happened and not what PSU generated the event. Currently, such a receiver would have to be generic over a PSU type that it never uses. Introduce `EventData` as a version of `Event` for use in these cases. Also rename `D` generic argument to `PSU` for clarity. --- power-policy-interface/src/service/event.rs | 59 ++++++++++++++++----- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/power-policy-interface/src/service/event.rs b/power-policy-interface/src/service/event.rs index 9888142e4..408741191 100644 --- a/power-policy-interface/src/service/event.rs +++ b/power-policy-interface/src/service/event.rs @@ -6,38 +6,73 @@ use crate::{ service::UnconstrainedState, }; +/// Event data broadcast from the service. +/// +/// This enum doesn't contain a reference to the device and is suitable +/// for receivers that don't need to know which device triggered the event +/// and allows for receivers that don't need to be generic over the device type. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EventData { + /// Consumer disconnected + ConsumerDisconnected, + /// Consumer connected + ConsumerConnected(ConsumerPowerCapability), + /// Provider disconnected + ProviderDisconnected, + /// Provider connected + ProviderConnected(ProviderPowerCapability), + /// Unconstrained state changed + Unconstrained(UnconstrainedState), +} + +impl<'device, PSU: Lockable> From> for EventData +where + PSU::Inner: Psu, +{ + fn from(value: Event<'device, PSU>) -> Self { + match value { + Event::ConsumerDisconnected(_) => EventData::ConsumerDisconnected, + Event::ConsumerConnected(_, capability) => EventData::ConsumerConnected(capability), + Event::ProviderDisconnected(_) => EventData::ProviderDisconnected, + Event::ProviderConnected(_, capability) => EventData::ProviderConnected(capability), + Event::Unconstrained(unconstrained) => EventData::Unconstrained(unconstrained), + } + } +} + /// Events broadcast from the service. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event<'device, D: Lockable> +pub enum Event<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Consumer disconnected - ConsumerDisconnected(&'device D), + ConsumerDisconnected(&'device PSU), /// Consumer connected - ConsumerConnected(&'device D, ConsumerPowerCapability), + ConsumerConnected(&'device PSU, ConsumerPowerCapability), /// Provider disconnected - ProviderDisconnected(&'device D), + ProviderDisconnected(&'device PSU), /// Provider connected - ProviderConnected(&'device D, ProviderPowerCapability), + ProviderConnected(&'device PSU, ProviderPowerCapability), /// Unconstrained state changed Unconstrained(UnconstrainedState), } -impl<'device, D> Clone for Event<'device, D> +impl<'device, PSU> Clone for Event<'device, PSU> where - D: Lockable, - D::Inner: Psu, + PSU: Lockable, + PSU::Inner: Psu, { fn clone(&self) -> Self { *self } } -impl<'device, D> Copy for Event<'device, D> +impl<'device, PSU> Copy for Event<'device, PSU> where - D: Lockable, - D::Inner: Psu, + PSU: Lockable, + PSU::Inner: Psu, { } From f247e2726380a773c5f2c62b98dbe246644a80ee Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 11 Mar 2026 11:13:35 -0700 Subject: [PATCH 32/79] power-policy-service: Rename existing `D` generics Rename for clarity. --- power-policy-service/src/service/consumer.rs | 12 ++++++------ power-policy-service/src/service/mod.rs | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index 4e0c3ea9f..c8d4ecbf4 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -11,26 +11,26 @@ use power_policy_interface::{capability::ConsumerPowerCapability, charger::Polic /// State of the current consumer #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AvailableConsumer<'device, D: Lockable> +pub struct AvailableConsumer<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Device reference - pub psu: &'device D, + pub psu: &'device PSU, /// The power capability of the currently connected consumer pub consumer_power_capability: ConsumerPowerCapability, } -impl<'device, D: Lockable> Clone for AvailableConsumer<'device, D> +impl<'device, PSU: Lockable> Clone for AvailableConsumer<'device, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { fn clone(&self) -> Self { *self } } -impl<'device, D: Lockable> Copy for AvailableConsumer<'device, D> where D::Inner: Psu {} +impl<'device, PSU: Lockable> Copy for AvailableConsumer<'device, PSU> where PSU::Inner: Psu {} /// Compare two consumer capabilities to determine which one is better /// diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index e8d8acd03..24ff33a15 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -21,12 +21,12 @@ use power_policy_interface::{ const MAX_CONNECTED_PROVIDERS: usize = 4; #[derive(Clone)] -struct InternalState<'device, D: Lockable> +struct InternalState<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Current consumer state, if any - current_consumer_state: Option>, + current_consumer_state: Option>, /// Current provider global state current_provider_state: provider::State, /// System unconstrained power @@ -35,9 +35,9 @@ where connected_providers: heapless::FnvIndexSet, } -impl Default for InternalState<'_, D> +impl Default for InternalState<'_, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { fn default() -> Self { Self { From 880a02402ba4fcd03435c02282a691244957c0c0 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 12 Mar 2026 11:04:58 -0700 Subject: [PATCH 33/79] power-policy-service: Add event broadcasting back Add event broadcasting based on `Sender<_>` trait, a few utility structs, and update tests to check broadcast events. --- embedded-service/src/event.rs | 41 +++++++++ examples/rt685s-evk/src/bin/type_c.rs | 18 +++- examples/rt685s-evk/src/bin/type_c_cfu.rs | 18 +++- examples/std/src/bin/power_policy.rs | 16 +++- examples/std/src/bin/type_c/service.rs | 18 +++- examples/std/src/bin/type_c/ucsi.rs | 15 +++- examples/std/src/bin/type_c/unconstrained.rs | 18 +++- power-policy-service/src/service/consumer.rs | 3 +- power-policy-service/src/service/mod.rs | 25 ++++-- power-policy-service/src/service/provider.rs | 3 +- power-policy-service/src/service/task.rs | 7 +- power-policy-service/tests/common/mod.rs | 53 ++++++++--- power-policy-service/tests/consumer.rs | 93 +++++++++++++++++--- 13 files changed, 271 insertions(+), 57 deletions(-) diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index 416a92c50..905f645cd 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -1,5 +1,7 @@ //! Common traits for event senders and receivers +use core::marker::PhantomData; + use embassy_sync::channel::{DynamicReceiver, DynamicSender}; /// Common event sender trait @@ -41,3 +43,42 @@ impl Receiver for DynamicReceiver<'_, E> { self.receive() } } + +/// A sender that discards all events sent to it. +pub struct DiscardSender; + +impl Sender for DiscardSender { + fn try_send(&mut self, _event: E) -> Option<()> { + Some(()) + } + + async fn send(&mut self, _event: E) {} +} + +/// Applies a function on events before passing them to the wrapped sender +pub struct MapSender, F: FnMut(I) -> O> { + sender: S, + map_fn: F, + _phantom: PhantomData<(I, O)>, +} + +impl, F: FnMut(I) -> O> MapSender { + /// Create a new self + pub fn new(sender: S, map_fn: F) -> Self { + Self { + sender, + map_fn, + _phantom: PhantomData, + } + } +} + +impl, F: FnMut(I) -> O> Sender for MapSender { + fn try_send(&mut self, event: I) -> Option<()> { + self.sender.try_send((self.map_fn)(event)) + } + + fn send(&mut self, event: I) -> impl Future { + self.sender.send((self.map_fn)(event)) + } +} diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 9fb53158f..77f7e8444 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -15,6 +15,7 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; +use embedded_services::event::DiscardSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; @@ -81,7 +82,10 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -207,10 +211,18 @@ async fn main(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + + static POWER_SERVICE: StaticCell< + Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, + > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration, + power_policy_event_senders.as_mut_slice(), power_service_context, power_policy_service::service::config::Config::default(), ))); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index df627a50b..eb03af50d 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -18,6 +18,7 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; +use embedded_services::event::DiscardSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; @@ -165,7 +166,10 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -285,10 +289,18 @@ async fn main(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + + static POWER_SERVICE: StaticCell< + Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, + > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration, + power_policy_event_senders.as_mut_slice(), power_service_context, power_policy_service::service::config::Config::default(), ))); diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index f1b7406db..a65c9afb2 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -5,7 +5,7 @@ use embassy_sync::{ mutex::Mutex, }; use embassy_time::{self as _, Timer}; -use embedded_services::GlobalRawMutex; +use embedded_services::{GlobalRawMutex, event::DiscardSender}; use log::*; use power_policy_interface::psu::{Error, Psu}; use power_policy_interface::{ @@ -134,11 +134,18 @@ async fn run(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static SERVICE: StaticCell< - Mutex>, + Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, > = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration.as_slice(), + power_policy_event_senders.as_mut_slice(), service_context, power_policy_service::service::config::Config::default(), ))); @@ -282,7 +289,10 @@ async fn power_policy_task( DeviceType, channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, - power_policy: &'static Mutex>, + power_policy: &'static Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index c1665c7b5..5d38cda8a 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,6 +5,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::event::DiscardSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; @@ -75,10 +76,18 @@ async fn task(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 1]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy]); - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + + static POWER_SERVICE: StaticCell< + Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, + > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration, + power_policy_event_senders.as_mut_slice(), power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -144,7 +153,10 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 8ac3e6138..ca8a8b182 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -7,6 +7,7 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; +use embedded_services::event::DiscardSender; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -180,7 +181,10 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -305,10 +309,15 @@ async fn task(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy]); - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + + static POWER_SERVICE: StaticCell< + Mutex>, + > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration, + power_policy_event_senders.as_mut_slice(), power_service_context, power_policy_service::service::config::Config::default(), ))); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index b8dfce26e..a3c0eeadb 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -8,6 +8,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::event::DiscardSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; @@ -179,10 +180,18 @@ async fn task(spawner: Spawner) { &wrapper2.ports[0].proxy, ]); - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + + static POWER_SERVICE: StaticCell< + Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, + > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration, + power_policy_event_senders.as_mut_slice(), power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -284,7 +293,10 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + power_policy: &'static Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index c8d4ecbf4..f4c73aa02 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -46,7 +46,8 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl<'device, 'device_storage, PSU: Lockable> Service<'device, 'device_storage, PSU> +impl<'device, 'device_storage, 'sender_storage, PSU: Lockable, EventSender: Sender>> + Service<'device, 'device_storage, 'sender_storage, PSU, EventSender> where PSU::Inner: Psu, { diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 24ff33a15..cf0fac306 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -7,7 +7,7 @@ pub mod context; pub mod provider; pub mod task; -use embedded_services::{error, info, sync::Lockable}; +use embedded_services::{error, event::Sender, info, sync::Lockable}; use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, @@ -50,8 +50,13 @@ where } /// Power policy service -pub struct Service<'device, 'device_storage, PSU: Lockable> -where +pub struct Service< + 'device, + 'device_storage, + 'sender_storage, + PSU: Lockable, + EventSender: Sender>, +> where PSU::Inner: Psu, { /// Power policy context @@ -62,15 +67,19 @@ where state: InternalState<'device, PSU>, /// Config config: config::Config, + /// Senders for service events + event_senders: &'sender_storage mut [EventSender], } -impl<'device, 'device_storage, PSU: Lockable> Service<'device, 'device_storage, PSU> +impl<'device, 'device_storage, 'sender_storage, PSU: Lockable, EventSender: Sender>> + Service<'device, 'device_storage, 'sender_storage, PSU, EventSender> where PSU::Inner: Psu, { /// Create a new power policy pub fn new( psu_devices: &'device_storage [&'device PSU], + event_senders: &'sender_storage mut [EventSender], context: &'device context::Context, config: config::Config, ) -> Self { @@ -79,6 +88,7 @@ where psu_devices, state: InternalState::default(), config, + event_senders, } } @@ -196,9 +206,10 @@ where } /// Send an event to all registered listeners - async fn broadcast_event(&mut self, _message: ServiceEvent<'device, PSU>) { - // TODO: Add this back as part of the migration away from comms - // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 + async fn broadcast_event(&mut self, event: ServiceEvent<'device, PSU>) { + for sender in self.event_senders.iter_mut() { + sender.send(event).await; + } } /// Common logic for when a provider is disconnected diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index 4671f595c..0f2d958bc 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -27,7 +27,8 @@ pub(super) struct State { state: PowerState, } -impl<'device, 'device_storage, PSU: Lockable> Service<'device, 'device_storage, PSU> +impl<'device, 'device_storage, 'sender_storage, PSU: Lockable, EventSender: Sender>> + Service<'device, 'device_storage, 'sender_storage, PSU, EventSender> where PSU::Inner: Psu, { diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index aa9363ebd..e871f5d62 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,8 +1,9 @@ use embedded_services::{error, info, sync::Lockable}; -use embedded_services::event::Receiver; +use embedded_services::event::{Receiver, Sender}; use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::EventData; +use power_policy_interface::service::event::Event as ServiceEvent; use super::Service; @@ -10,10 +11,12 @@ use super::Service; pub async fn task< 'device, 'device_storage, + 'sender_storage, const PSU_COUNT: usize, - S: Lockable>, + S: Lockable>, PSU: Lockable, R: Receiver, + EventSender: Sender> + 'sender_storage, >( mut psu_events: crate::psu::EventReceivers<'device, PSU_COUNT, PSU, R>, policy: &'device S, diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 5c0ca9a68..d71c45624 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -1,4 +1,6 @@ #![allow(clippy::unwrap_used)] +use std::mem::ManuallyDrop; + use embassy_futures::{ join::join, select::{Either, select}, @@ -11,8 +13,8 @@ use embassy_sync::{ }; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; -use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu::event::EventData; +use power_policy_interface::{capability::PowerCapability, service::event::Event as ServiceEvent}; use power_policy_service::psu::EventReceivers; use power_policy_service::service::Service; @@ -38,11 +40,17 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); const EVENT_CHANNEL_SIZE: usize = 4; pub type DeviceType<'a> = Mutex>>; -pub type ServiceType<'device, 'device_storage> = Service<'device, 'device_storage, DeviceType<'device>>; - -async fn power_policy_task<'device, 'device_storage, const N: usize>( +pub type ServiceType<'device, 'device_storage, 'sender_storage> = Service< + 'device, + 'device_storage, + 'sender_storage, + DeviceType<'device>, + DynamicSender<'sender_storage, ServiceEvent<'device, DeviceType<'device>>>, +>; + +async fn power_policy_task<'device, 'device_storage, 'sender_storage, const N: usize>( completion_signal: &'device Signal, - mut power_policy: ServiceType<'device, 'device_storage>, + mut power_policy: ServiceType<'device, 'device_storage, 'sender_storage>, mut event_receivers: EventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { @@ -63,15 +71,16 @@ async fn power_policy_task<'device, 'device_storage, const N: usize>( /// ``` /// However, `impl (Future + 'a)` is not real syntax. This could be done with the unstable feature type_alias_impl_trait, /// but we use this helper trait so as to not require use of nightly. -pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a>: - FnOnce(Arg0, Arg1, Arg2, Arg3) -> Self::Fut +pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a>: + FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Self::Fut { type Fut: Future; } -impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3> for F +impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3, Arg4> + for F where - F: FnOnce(Arg0, Arg1, Arg2, Arg3) -> Fut, + F: FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Fut, Fut: Future, { type Fut = Fut; @@ -81,9 +90,10 @@ pub async fn run_test(timeout: Duration, test: F) where for<'a> F: TestArgsFnOnce< 'a, - &'a Mutex>>, + DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + &'a DeviceType<'a>, &'a Signal, - &'a Mutex>>, + &'a DeviceType<'a>, &'a Signal, >, { @@ -112,8 +122,23 @@ where let psu_registration = [&device0, &device1]; let completion_signal = Signal::new(); - let power_policy = - power_policy_service::service::Service::new(psu_registration.as_slice(), &service_context, Default::default()); + // Ideally F would have two lifetime arguments: 'device and 'sender because the event type requires 'device: 'sender. + // But Rust doesn't currently support syntax like `for<'device, 'sender> ... where 'device: 'sender`. So we just + // use a single lifetime. However, the unified lifetime makes the drop-checker think that dropping the channel + // could be unsafe. We use ManuallyDrop to disable the drop and make the drop-checker happy. None of the types + // here do any clean-up in their Drop impls so we don't have to worry about any sort of leaks. + let service_event_channel: ManuallyDrop< + Channel>, EVENT_CHANNEL_SIZE>, + > = ManuallyDrop::new(Channel::new()); + let mut service_sender = [service_event_channel.dyn_sender()]; + let _service_receiver = service_event_channel.dyn_receiver(); + + let power_policy = power_policy_service::service::Service::new( + psu_registration.as_slice(), + &mut service_sender, + &service_context, + Default::default(), + ); with_timeout( timeout, @@ -124,7 +149,7 @@ where EventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { - test(&device0, &device0_signal, &device1, &device1_signal).await; + test(_service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; completion_signal.signal(()); }, ), diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 94feac601..83e61bb02 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,5 +1,6 @@ #![allow(clippy::unwrap_used)] -use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::info; @@ -8,21 +9,19 @@ use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability} mod common; use common::LOW_POWER; -use power_policy_interface::psu::event::EventData; +use power_policy_interface::service::event::Event as ServiceEvent; -use crate::common::{ - DEFAULT_TIMEOUT, HIGH_POWER, - mock::{FnCall, Mock}, - run_test, -}; +use crate::common::DeviceType; +use crate::common::{DEFAULT_TIMEOUT, HIGH_POWER, mock::FnCall, run_test}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic consumer flow with a single device. -async fn test_single( - device0: &Mutex>>, +async fn test_single<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, device0_signal: &Signal, - _device1: &Mutex>>, + _device1: &DeviceType<'a>, _device1_signal: &Signal, ) { info!("Running test_single"); @@ -41,6 +40,18 @@ async fn test_single( ) ); device0_signal.reset(); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + } + ); } // Test detach { @@ -52,14 +63,20 @@ async fn test_single( Err(TimeoutError) ); device0_signal.reset(); + + let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ConsumerDisconnect event"); + }; + assert_eq!(device as *const _, device0 as *const _); } } /// Test swapping to a higher powered device. -async fn test_swap_higher( - device0: &Mutex>>, +async fn test_swap_higher<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, device0_signal: &Signal, - device1: &Mutex>>, + device1: &DeviceType<'a>, device1_signal: &Signal, ) { info!("Running test_swap_higher"); @@ -78,6 +95,18 @@ async fn test_swap_higher( ) ); device0_signal.reset(); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + } + ); } // Device1 connection at high power { @@ -100,6 +129,24 @@ async fn test_swap_higher( ) ); device1_signal.reset(); + + // Should receive a disconnect event from device0 first + let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ConsumerDisconnect event"); + }; + assert_eq!(device as *const _, device0 as *const _); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device1 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + } + ); } // Test detach device1, should reconnect device0 { @@ -122,11 +169,29 @@ async fn test_swap_higher( ) ); device0_signal.reset(); + + // Should receive a disconnect event from device0 first + let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ConsumerDisconnect event"); + }; + assert_eq!(device as *const _, device1 as *const _); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + } + ); } } #[tokio::test] -async fn run_all_tests() { +async fn run_test_swap_higher() { run_test(DEFAULT_TIMEOUT, test_swap_higher).await; } From 0fd8aa4f6dbab18b25f478e9b92dcecf9817eedd Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 12 Mar 2026 11:15:47 -0700 Subject: [PATCH 34/79] embedded-service: Add `Named` trait --- embedded-service/src/lib.rs | 1 + embedded-service/src/named.rs | 7 +++++++ examples/std/src/bin/power_policy.rs | 4 +++- power-policy-interface/src/psu/mod.rs | 6 +++--- power-policy-service/src/service/consumer.rs | 1 + power-policy-service/src/service/mod.rs | 1 + power-policy-service/src/service/provider.rs | 1 + power-policy-service/tests/common/mock.rs | 4 +++- type-c-service/src/wrapper/proxy.rs | 3 +++ 9 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 embedded-service/src/named.rs diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index c53aba536..e62562e3a 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -21,6 +21,7 @@ pub mod hid; pub mod init; pub mod ipc; pub mod keyboard; +pub mod named; pub mod relay; pub mod sync; diff --git a/embedded-service/src/named.rs b/embedded-service/src/named.rs new file mode 100644 index 000000000..feebe6993 --- /dev/null +++ b/embedded-service/src/named.rs @@ -0,0 +1,7 @@ +//! Traits for things that have names. + +/// Trait for anything that has a name. +pub trait Named { + /// Return name + fn name(&self) -> &'static str; +} diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index a65c9afb2..ddc014452 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -5,7 +5,7 @@ use embassy_sync::{ mutex::Mutex, }; use embassy_time::{self as _, Timer}; -use embedded_services::{GlobalRawMutex, event::DiscardSender}; +use embedded_services::{GlobalRawMutex, event::DiscardSender, named::Named}; use log::*; use power_policy_interface::psu::{Error, Psu}; use power_policy_interface::{ @@ -96,7 +96,9 @@ impl Psu for ExampleDevice<'_> { fn state_mut(&mut self) -> &mut psu::State { &mut self.state } +} +impl Named for ExampleDevice<'_> { fn name(&self) -> &'static str { self.name } diff --git a/power-policy-interface/src/psu/mod.rs b/power-policy-interface/src/psu/mod.rs index 323b83183..bae5ced52 100644 --- a/power-policy-interface/src/psu/mod.rs +++ b/power-policy-interface/src/psu/mod.rs @@ -1,4 +1,6 @@ //! Device struct and methods +use embedded_services::named::Named; + use crate::capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}; pub mod event; @@ -280,7 +282,7 @@ pub struct Response { } /// Trait for PSU devices -pub trait Psu { +pub trait Psu: Named { /// Disconnect power from this device fn disconnect(&mut self) -> impl Future>; /// Connect this device to provide power to an external connection @@ -291,6 +293,4 @@ pub trait Psu { fn state(&self) -> &State; /// Return a mutable reference to the current PSU state fn state_mut(&mut self) -> &mut State; - /// Return the name of the PSU - fn name(&self) -> &'static str; } diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index f4c73aa02..a6aa88371 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -1,4 +1,5 @@ use core::cmp::Ordering; +use embedded_services::named::Named; use embedded_services::{debug, error}; use super::*; diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index cf0fac306..137d8e48a 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -7,6 +7,7 @@ pub mod context; pub mod provider; pub mod task; +use embedded_services::named::Named; use embedded_services::{error, event::Sender, info, sync::Lockable}; use power_policy_interface::{ diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index 0f2d958bc..f40c5b0b1 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -6,6 +6,7 @@ use core::ptr; use embedded_services::debug; +use embedded_services::named::Named; use super::*; diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index f117b73ba..91fb13be9 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,6 +1,6 @@ #![allow(clippy::unwrap_used)] use embassy_sync::signal::Signal; -use embedded_services::{GlobalRawMutex, event::Sender, info}; +use embedded_services::{GlobalRawMutex, event::Sender, info, named::Named}; use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, psu::{Error, Psu, State, event::EventData}, @@ -83,7 +83,9 @@ impl<'a, S: Sender> Psu for Mock<'a, S> { fn state_mut(&mut self) -> &mut State { &mut self.state } +} +impl<'a, S: Sender> Named for Mock<'a, S> { fn name(&self) -> &'static str { self.name } diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs index a14dc82a9..170fd55d3 100644 --- a/type-c-service/src/wrapper/proxy.rs +++ b/type-c-service/src/wrapper/proxy.rs @@ -1,5 +1,6 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embedded_services::named::Named; use power_policy_interface::psu::{CommandData as PolicyCommandData, InternalResponseData as PolicyResponseData, Psu}; pub struct PowerProxyChannel { @@ -112,7 +113,9 @@ impl<'a> Psu for PowerProxyDevice<'a> { fn state_mut(&mut self) -> &mut power_policy_interface::psu::State { &mut self.psu_state } +} +impl<'a> Named for PowerProxyDevice<'a> { fn name(&self) -> &'static str { self.name } From 2ccf151b14bd3f18a565cb4c68052f30e7e5720c Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 12 Mar 2026 13:48:20 -0700 Subject: [PATCH 35/79] power-policy-service: Add provider tests --- Cargo.lock | 1 + power-policy-service/Cargo.toml | 2 +- power-policy-service/src/service/mod.rs | 23 +- power-policy-service/src/service/provider.rs | 25 ++ power-policy-service/tests/common/mock.rs | 24 +- power-policy-service/tests/consumer.rs | 1 - power-policy-service/tests/provider.rs | 236 +++++++++++++++++++ 7 files changed, 293 insertions(+), 19 deletions(-) create mode 100644 power-policy-service/tests/provider.rs diff --git a/Cargo.lock b/Cargo.lock index 7f1362236..233bf192d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1877,6 +1877,7 @@ dependencies = [ "log", "num_enum", "power-policy-interface", + "power-policy-service", "static_cell", "tokio", ] diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 2f0552f04..8a55579e8 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -32,7 +32,7 @@ env_logger = "0.11.8" log = { workspace = true } # TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile # Uncomment this line to enable log output in tests -#power-policy-service = { workspace = true, features = ["log"] } +power-policy-service = { workspace = true, features = ["log"] } [features] default = [] diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 137d8e48a..d035faf91 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -109,7 +109,7 @@ where total } - async fn process_notify_attach(&self, device: &PSU) { + async fn process_notify_attach(&self, device: &'device PSU) { let mut device = device.lock().await; info!("({}): Received notify attached", device.name()); if let Err(e) = device.state_mut().attach() { @@ -117,18 +117,21 @@ where } } - async fn process_notify_detach(&mut self, device: &PSU) -> Result<(), Error> { + async fn process_notify_detach(&mut self, device: &'device PSU) -> Result<(), Error> { { let mut device = device.lock().await; info!("({}): Received notify detached", device.name()); device.state_mut().detach(); } - self.update_current_consumer().await + + self.remove_connected_provider(device).await; + self.update_current_consumer().await?; + Ok(()) } async fn process_notify_consumer_power_capability( &mut self, - device: &PSU, + device: &'device PSU, capability: Option, ) -> Result<(), Error> { { @@ -213,18 +216,6 @@ where } } - /// Common logic for when a provider is disconnected - /// - /// Returns true if the device was operating as a provider - async fn remove_connected_provider(&mut self, psu: &'device PSU) -> bool { - if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { - self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; - true - } else { - false - } - } - pub async fn process_psu_event(&mut self, event: PsuEvent<'device, PSU>) -> Result<(), Error> { let device = event.psu; match event.event { diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index f40c5b0b1..424209710 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -111,4 +111,29 @@ where self.broadcast_event(ServiceEvent::ProviderConnected(requester, target_power)) .await; } + + /// Common logic for when a provider is disconnected + /// + /// Returns true if the device was operating as a provider + pub(super) async fn remove_connected_provider(&mut self, psu: &'device PSU) -> bool { + if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { + // Determine total requested power draw + let mut total_power_mw = 0; + for psu in self.psu_devices.iter() { + let target_provider_cap = psu.lock().await.state().connected_provider_capability(); + total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); + } + + if total_power_mw > self.config.limited_power_threshold_mw { + self.state.current_provider_state.state = PowerState::Limited; + } else { + self.state.current_provider_state.state = PowerState::Unlimited; + } + + self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; + true + } else { + false + } + } } diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index 91fb13be9..e0ee41c8a 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,8 +1,9 @@ #![allow(clippy::unwrap_used)] +#![allow(dead_code)] use embassy_sync::signal::Signal; use embedded_services::{GlobalRawMutex, event::Sender, info, named::Named}; use power_policy_interface::{ - capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability}, psu::{Error, Psu, State, event::EventData}, }; @@ -55,6 +56,27 @@ impl<'a, S: Sender> Mock<'a, S> { pub async fn simulate_detach(&mut self) { self.sender.send(EventData::Detached).await; } + + pub async fn simulate_provider_connection(&mut self, capability: PowerCapability) { + self.sender.send(EventData::Attached).await; + + let capability = Some(ProviderPowerCapability { + capability, + flags: ProviderFlags::none(), + }); + self.sender + .send(EventData::RequestedProviderCapability(capability)) + .await; + } + + pub async fn simulate_update_requested_provider_power_capability( + &mut self, + capability: Option, + ) { + self.sender + .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) + .await + } } impl<'a, S: Sender> Psu for Mock<'a, S> { diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 83e61bb02..ba9868642 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -195,7 +195,6 @@ async fn run_test_swap_higher() { run_test(DEFAULT_TIMEOUT, test_swap_higher).await; } -/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. #[tokio::test] async fn run_test_single() { run_test(DEFAULT_TIMEOUT, test_single).await; diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs new file mode 100644 index 000000000..bcce34a85 --- /dev/null +++ b/power-policy-service/tests/provider.rs @@ -0,0 +1,236 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; +use embassy_time::{Duration, TimeoutError, with_timeout}; +use embedded_services::GlobalRawMutex; +use embedded_services::info; +use power_policy_interface::capability::ProviderFlags; +use power_policy_interface::capability::ProviderPowerCapability; + +mod common; + +use common::LOW_POWER; +use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::common::DeviceType; +use crate::common::HIGH_POWER; +use crate::common::{DEFAULT_TIMEOUT, mock::FnCall, run_test}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test the basic provider flow with a single device. +async fn test_single<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_single"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + } + ); + } + // Test detach + { + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + let ServiceEvent::ProviderDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ProviderDisconnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + } +} + +/// Test provider flow involving multiple devices and upgrading a provider's power capability. +async fn test_upgrade<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_upgrade"); + { + // Connect device0 at high power, default service config should allow this + device0.lock().await.simulate_provider_connection(HIGH_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + } + ); + } + + { + // Connect device1 at low power, default service config should allow this + device1.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, device1 as *const _); + assert_eq!( + capability, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + } + ); + } + + { + // Attempt to upgrade device1 to high power, power policy should reject this since device0 is already connected at high power + // Power policy will instead allow us to connect at low power + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, device1 as *const _); + assert_eq!( + capability, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + } + ); + } + + { + // Detach device0, this should allow us to upgrade device1 to high power + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + let ServiceEvent::ProviderDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ProviderDisconnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + } + + { + // Attempt to upgrade device1 to high power should now succeed + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, device1 as *const _); + assert_eq!( + capability, + ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + } + ); + } +} + +#[tokio::test] +async fn run_test_single() { + run_test(DEFAULT_TIMEOUT, test_single).await; +} + +#[tokio::test] +async fn run_test_upgrade() { + run_test(DEFAULT_TIMEOUT, test_upgrade).await; +} From cc8f9e86f6d3b2cbec24d77f18380b9f25630f14 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 12 Mar 2026 14:10:13 -0700 Subject: [PATCH 36/79] power-policy-service: Add unconstrained tests --- power-policy-service/tests/common/mock.rs | 13 +- power-policy-service/tests/consumer.rs | 18 +- power-policy-service/tests/unconstrained.rs | 182 ++++++++++++++++++++ 3 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 power-policy-service/tests/unconstrained.rs diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index e0ee41c8a..a6ff4fc56 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -3,7 +3,7 @@ use embassy_sync::signal::Signal; use embedded_services::{GlobalRawMutex, event::Sender, info, named::Named}; use power_policy_interface::{ - capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability}, + capability::{ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability}, psu::{Error, Psu, State, event::EventData}, }; @@ -43,14 +43,11 @@ impl<'a, S: Sender> Mock<'a, S> { self.fn_call.signal((num_fn_calls + 1, fn_call)); } - pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { + pub async fn simulate_consumer_connection(&mut self, capability: ConsumerPowerCapability) { self.sender.send(EventData::Attached).await; - - let capability = Some(ConsumerPowerCapability { - capability, - flags: ConsumerFlags::none(), - }); - self.sender.send(EventData::UpdatedConsumerCapability(capability)).await; + self.sender + .send(EventData::UpdatedConsumerCapability(Some(capability))) + .await; } pub async fn simulate_detach(&mut self) { diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index ba9868642..0a6b687fe 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -27,7 +27,11 @@ async fn test_single<'a>( info!("Running test_single"); // Test initial connection { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -82,7 +86,11 @@ async fn test_swap_higher<'a>( info!("Running test_swap_higher"); // Device0 connection at low power { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -110,7 +118,11 @@ async fn test_swap_higher<'a>( } // Device1 connection at high power { - device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs new file mode 100644 index 000000000..2abe3f8e4 --- /dev/null +++ b/power-policy-service/tests/unconstrained.rs @@ -0,0 +1,182 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; +use embassy_time::TimeoutError; +use embassy_time::{Duration, with_timeout}; +use embedded_services::GlobalRawMutex; +use embedded_services::info; +use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability}; + +mod common; + +use common::LOW_POWER; +use power_policy_interface::service::UnconstrainedState; +use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::common::DeviceType; +use crate::common::HIGH_POWER; +use crate::common::{DEFAULT_TIMEOUT, mock::FnCall, run_test}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test unconstrained consumer flow with multiple devices. +async fn test_unconstrained<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_unconstrained"); + { + // Connect device0, without unconstrained, + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + } + ); + + // Should not have any unconstrained events + assert!(service_receiver.try_receive().is_err()); + } + + { + // Connect device1 with unconstrained at HIGH_POWER to force power policy to select this consumer. + device1 + .lock() + .await + .simulate_consumer_connection(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ConsumerDisconnect event"); + }; + assert_eq!(device as *const _, device0 as *const _); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device1 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + } + ); + + let ServiceEvent::Unconstrained(unconstrained_state) = service_receiver.receive().await else { + panic!("Expected Unconstrained event"); + }; + assert_eq!( + unconstrained_state, + UnconstrainedState { + unconstrained: true, + available: 1, + } + ); + } + + { + // Test detach device1, unconstrained state should change + device1.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Should receive a disconnect event from device0 first + let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { + panic!("Expected ConsumerDisconnect event"); + }; + assert_eq!(device as *const _, device1 as *const _); + + let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, device0 as *const _); + assert_eq!( + capability, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + } + ); + + let ServiceEvent::Unconstrained(unconstrained_state) = service_receiver.receive().await else { + panic!("Expected Unconstrained event"); + }; + assert_eq!( + unconstrained_state, + UnconstrainedState { + unconstrained: false, + available: 0, + } + ); + } +} + +#[tokio::test] +async fn run_test_unconstrained() { + run_test(DEFAULT_TIMEOUT, test_unconstrained).await; +} From fd3df592530efe99eb345c57e7718d0a6bb8ea1a Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 12 Mar 2026 14:21:10 -0700 Subject: [PATCH 37/79] power-policy-service: Create helper functions for service event tests --- Cargo.lock | 1 - power-policy-service/Cargo.toml | 2 +- power-policy-service/src/service/mod.rs | 2 +- power-policy-service/tests/common/mod.rs | 61 ++++++++++++++- power-policy-service/tests/consumer.rs | 81 ++++++++----------- power-policy-service/tests/provider.rs | 82 ++++++++----------- power-policy-service/tests/unconstrained.rs | 87 +++++++++------------ 7 files changed, 165 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 233bf192d..7f1362236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1877,7 +1877,6 @@ dependencies = [ "log", "num_enum", "power-policy-interface", - "power-policy-service", "static_cell", "tokio", ] diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 8a55579e8..13d236118 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -32,7 +32,7 @@ env_logger = "0.11.8" log = { workspace = true } # TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile # Uncomment this line to enable log output in tests -power-policy-service = { workspace = true, features = ["log"] } +# power-policy-service = { workspace = true, features = ["log"] } [features] default = [] diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index d035faf91..7411f0d53 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -170,7 +170,7 @@ where .update_requested_provider_power_capability(capability) { error!( - "({}): Invalid state for notify consumer capability, catching up: {:#?}", + "({}): Invalid state for notify provider capability, catching up: {:#?}", requester.name(), e, ); diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index d71c45624..77ae2574d 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -1,4 +1,6 @@ #![allow(clippy::unwrap_used)] +#![allow(dead_code)] +#![allow(clippy::panic)] use std::mem::ManuallyDrop; use embassy_futures::{ @@ -14,7 +16,10 @@ use embassy_sync::{ use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; use power_policy_interface::psu::event::EventData; -use power_policy_interface::{capability::PowerCapability, service::event::Event as ServiceEvent}; +use power_policy_interface::{ + capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + service::{UnconstrainedState, event::Event as ServiceEvent}, +}; use power_policy_service::psu::EventReceivers; use power_policy_service::service::Service; @@ -157,3 +162,57 @@ where .await .unwrap(); } + +pub async fn assert_consumer_disconnected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, +) { + let ServiceEvent::ConsumerDisconnected(device) = receiver.receive().await else { + panic!("Expected ConsumerDisconnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); +} + +pub async fn assert_consumer_connected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, + expected_capability: ConsumerPowerCapability, +) { + let ServiceEvent::ConsumerConnected(device, capability) = receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); + assert_eq!(capability, expected_capability); +} + +pub async fn assert_provider_disconnected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, +) { + let ServiceEvent::ProviderDisconnected(device) = receiver.receive().await else { + panic!("Expected ProviderDisconnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); +} + +pub async fn assert_provider_connected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, + expected_capability: ProviderPowerCapability, +) { + let ServiceEvent::ProviderConnected(device, capability) = receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); + assert_eq!(capability, expected_capability); +} + +pub async fn assert_unconstrained<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_state: UnconstrainedState, +) { + let ServiceEvent::Unconstrained(state) = receiver.receive().await else { + panic!("Expected Unconstrained event"); + }; + assert_eq!(state, expected_state); +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 0a6b687fe..be9ed8641 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -12,7 +12,9 @@ use common::LOW_POWER; use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; -use crate::common::{DEFAULT_TIMEOUT, HIGH_POWER, mock::FnCall, run_test}; +use crate::common::{ + DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, run_test, +}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); @@ -45,17 +47,15 @@ async fn test_single<'a>( ); device0_signal.reset(); - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + assert_consumer_connected( + service_receiver, + device0, ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - } - ); + }, + ) + .await; } // Test detach { @@ -68,10 +68,7 @@ async fn test_single<'a>( ); device0_signal.reset(); - let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ConsumerDisconnect event"); - }; - assert_eq!(device as *const _, device0 as *const _); + assert_consumer_disconnected(service_receiver, device0).await; } } @@ -104,17 +101,15 @@ async fn test_swap_higher<'a>( ); device0_signal.reset(); - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + assert_consumer_connected( + service_receiver, + device0, ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - } - ); + }, + ) + .await; } // Device1 connection at high power { @@ -143,22 +138,17 @@ async fn test_swap_higher<'a>( device1_signal.reset(); // Should receive a disconnect event from device0 first - let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ConsumerDisconnect event"); - }; - assert_eq!(device as *const _, device0 as *const _); - - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device1 as *const _); - assert_eq!( - capability, + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, ConsumerPowerCapability { capability: HIGH_POWER, flags: ConsumerFlags::none(), - } - ); + }, + ) + .await; } // Test detach device1, should reconnect device0 { @@ -182,23 +172,18 @@ async fn test_swap_higher<'a>( ); device0_signal.reset(); - // Should receive a disconnect event from device0 first - let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ConsumerDisconnect event"); - }; - assert_eq!(device as *const _, device1 as *const _); - - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - } - ); + }, + ) + .await; } } diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs index bcce34a85..929f9dd9a 100644 --- a/power-policy-service/tests/provider.rs +++ b/power-policy-service/tests/provider.rs @@ -14,7 +14,7 @@ use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; use crate::common::HIGH_POWER; -use crate::common::{DEFAULT_TIMEOUT, mock::FnCall, run_test}; +use crate::common::{DEFAULT_TIMEOUT, assert_provider_connected, assert_provider_disconnected, mock::FnCall, run_test}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); @@ -43,17 +43,15 @@ async fn test_single<'a>( ); device0_signal.reset(); - let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ProviderConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + assert_provider_connected( + service_receiver, + device0, ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - } - ); + }, + ) + .await; } // Test detach { @@ -66,10 +64,7 @@ async fn test_single<'a>( ); device0_signal.reset(); - let ServiceEvent::ProviderDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ProviderDisconnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); + assert_provider_disconnected(service_receiver, device0).await; } } @@ -98,17 +93,15 @@ async fn test_upgrade<'a>( ); device0_signal.reset(); - let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ProviderConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + assert_provider_connected( + service_receiver, + device0, ProviderPowerCapability { capability: HIGH_POWER, flags: ProviderFlags::none(), - } - ); + }, + ) + .await; } { @@ -127,17 +120,15 @@ async fn test_upgrade<'a>( ); device1_signal.reset(); - let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ProviderConnected event"); - }; - assert_eq!(device as *const _, device1 as *const _); - assert_eq!( - capability, + assert_provider_connected( + service_receiver, + device1, ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - } - ); + }, + ) + .await; } { @@ -161,17 +152,15 @@ async fn test_upgrade<'a>( ); device1_signal.reset(); - let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ProviderConnected event"); - }; - assert_eq!(device as *const _, device1 as *const _); - assert_eq!( - capability, + assert_provider_connected( + service_receiver, + device1, ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - } - ); + }, + ) + .await; } { @@ -185,10 +174,7 @@ async fn test_upgrade<'a>( ); device0_signal.reset(); - let ServiceEvent::ProviderDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ProviderDisconnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); + assert_provider_disconnected(service_receiver, device0).await; } { @@ -211,17 +197,15 @@ async fn test_upgrade<'a>( ); device1_signal.reset(); - let ServiceEvent::ProviderConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ProviderConnected event"); - }; - assert_eq!(device as *const _, device1 as *const _); - assert_eq!( - capability, + assert_provider_connected( + service_receiver, + device1, ProviderPowerCapability { capability: HIGH_POWER, flags: ProviderFlags::none(), - } - ); + }, + ) + .await; } } diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs index 2abe3f8e4..7cfff7bc9 100644 --- a/power-policy-service/tests/unconstrained.rs +++ b/power-policy-service/tests/unconstrained.rs @@ -15,7 +15,10 @@ use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; use crate::common::HIGH_POWER; -use crate::common::{DEFAULT_TIMEOUT, mock::FnCall, run_test}; +use crate::common::{ + DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_unconstrained, mock::FnCall, + run_test, +}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); @@ -48,17 +51,15 @@ async fn test_unconstrained<'a>( ); device0_signal.reset(); - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + assert_consumer_connected( + service_receiver, + device0, ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - } - ); + }, + ) + .await; // Should not have any unconstrained events assert!(service_receiver.try_receive().is_err()); @@ -94,33 +95,26 @@ async fn test_unconstrained<'a>( device1_signal.reset(); // Should receive a disconnect event from device0 first - let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ConsumerDisconnect event"); - }; - assert_eq!(device as *const _, device0 as *const _); - - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device1 as *const _); - assert_eq!( - capability, + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, ConsumerPowerCapability { capability: HIGH_POWER, flags: ConsumerFlags::none().with_unconstrained_power(), - } - ); + }, + ) + .await; - let ServiceEvent::Unconstrained(unconstrained_state) = service_receiver.receive().await else { - panic!("Expected Unconstrained event"); - }; - assert_eq!( - unconstrained_state, + assert_unconstrained( + service_receiver, UnconstrainedState { unconstrained: true, available: 1, - } - ); + }, + ) + .await; } { @@ -145,34 +139,27 @@ async fn test_unconstrained<'a>( ); device0_signal.reset(); - // Should receive a disconnect event from device0 first - let ServiceEvent::ConsumerDisconnected(device) = service_receiver.receive().await else { - panic!("Expected ConsumerDisconnect event"); - }; - assert_eq!(device as *const _, device1 as *const _); - - let ServiceEvent::ConsumerConnected(device, capability) = service_receiver.receive().await else { - panic!("Expected ConsumerConnected event"); - }; - assert_eq!(device as *const _, device0 as *const _); - assert_eq!( - capability, + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - } - ); + }, + ) + .await; - let ServiceEvent::Unconstrained(unconstrained_state) = service_receiver.receive().await else { - panic!("Expected Unconstrained event"); - }; - assert_eq!( - unconstrained_state, + assert_unconstrained( + service_receiver, UnconstrainedState { unconstrained: false, available: 0, - } - ); + }, + ) + .await; } } From 2456124a9893b0426bc332dae3a9d941c4fb3fb8 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 16 Mar 2026 10:32:44 -0700 Subject: [PATCH 38/79] power-policy-service: Fix disconnect flow and add tests --- power-policy-service/src/service/mod.rs | 28 ++-- power-policy-service/src/service/provider.rs | 9 +- power-policy-service/tests/common/mock.rs | 4 + power-policy-service/tests/common/mod.rs | 4 + power-policy-service/tests/consumer.rs | 128 +++++++++++++++++++ power-policy-service/tests/provider.rs | 62 +++++++++ power-policy-service/tests/unconstrained.rs | 6 +- 7 files changed, 219 insertions(+), 22 deletions(-) diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 7411f0d53..d27193f3a 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -181,27 +181,17 @@ where } async fn process_notify_disconnect(&mut self, device: &'device PSU) -> Result<(), Error> { - let mut locked_device = device.lock().await; - info!("({}): Received notify disconnect", locked_device.name()); - - if let Err(e) = locked_device.state_mut().disconnect(true) { - error!( - "({}): Invalid state for notify disconnect, catching up: {:#?}", - locked_device.name(), - e, - ); - } - - if self - .state - .current_consumer_state - .as_ref() - .is_some_and(|current| ptr::eq(current.psu, device)) { - info!("({}): Connected consumer disconnected", locked_device.name()); - self.disconnect_chargers().await?; + let mut locked_device = device.lock().await; + info!("({}): Received notify disconnect", locked_device.name()); - self.broadcast_event(ServiceEvent::ConsumerDisconnected(device)).await; + if let Err(e) = locked_device.state_mut().disconnect(true) { + error!( + "({}): Invalid state for notify disconnect, catching up: {:#?}", + locked_device.name(), + e, + ); + } } self.remove_connected_provider(device).await; diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index 424209710..f59636a1d 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -107,7 +107,14 @@ where /// Common logic for after a provider has successfully connected async fn post_provider_connected(&mut self, requester: &'device PSU, target_power: ProviderPowerCapability) { - let _ = self.state.connected_providers.insert(requester as *const PSU as usize); + if self + .state + .connected_providers + .insert(requester as *const PSU as usize) + .is_err() + { + error!("Tracked providers set is full"); + } self.broadcast_event(ServiceEvent::ProviderConnected(requester, target_power)) .await; } diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index a6ff4fc56..ab3597878 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -66,6 +66,10 @@ impl<'a, S: Sender> Mock<'a, S> { .await; } + pub async fn simulate_disconnect(&mut self) { + self.sender.send(EventData::Disconnected).await; + } + pub async fn simulate_update_requested_provider_power_capability( &mut self, capability: Option, diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 77ae2574d..44ab1e9b4 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -216,3 +216,7 @@ pub async fn assert_unconstrained<'a>( }; assert_eq!(state, expected_state); } + +pub fn assert_no_event<'a>(receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>) { + assert!(receiver.try_receive().is_err()); +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index be9ed8641..9fd5ac299 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -12,6 +12,7 @@ use common::LOW_POWER; use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; +use crate::common::assert_no_event; use crate::common::{ DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, run_test, }; @@ -70,6 +71,8 @@ async fn test_single<'a>( assert_consumer_disconnected(service_receiver, device0).await; } + + assert_no_event(service_receiver); } /// Test swapping to a higher powered device. @@ -185,6 +188,126 @@ async fn test_swap_higher<'a>( ) .await; } + + assert_no_event(service_receiver); +} + +/// Test a disconnect initiated by the current consumer. +async fn test_disconnect<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_disconnect"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + // Device1 connection at high power + { + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + + // Test disconnect device1, should reconnect device0 + { + device1.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Consume the disconnect event generated by `simulate_disconnect` + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + + assert_no_event(service_receiver); } #[tokio::test] @@ -196,3 +319,8 @@ async fn run_test_swap_higher() { async fn run_test_single() { run_test(DEFAULT_TIMEOUT, test_single).await; } + +#[tokio::test] +async fn run_test_disconnect() { + run_test(DEFAULT_TIMEOUT, test_disconnect).await; +} diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs index 929f9dd9a..c5a789117 100644 --- a/power-policy-service/tests/provider.rs +++ b/power-policy-service/tests/provider.rs @@ -14,6 +14,7 @@ use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; use crate::common::HIGH_POWER; +use crate::common::assert_no_event; use crate::common::{DEFAULT_TIMEOUT, assert_provider_connected, assert_provider_disconnected, mock::FnCall, run_test}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); @@ -66,6 +67,8 @@ async fn test_single<'a>( assert_provider_disconnected(service_receiver, device0).await; } + + assert_no_event(service_receiver); } /// Test provider flow involving multiple devices and upgrading a provider's power capability. @@ -207,6 +210,60 @@ async fn test_upgrade<'a>( ) .await; } + + assert_no_event(service_receiver); +} + +/// Test the provider disconnect flow +async fn test_disconnect<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_disconnect"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + // Test disconnect + { + device0.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + assert_no_event(service_receiver); } #[tokio::test] @@ -218,3 +275,8 @@ async fn run_test_single() { async fn run_test_upgrade() { run_test(DEFAULT_TIMEOUT, test_upgrade).await; } + +#[tokio::test] +async fn run_test_disconnect() { + run_test(DEFAULT_TIMEOUT, test_disconnect).await; +} diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs index 7cfff7bc9..2bea43857 100644 --- a/power-policy-service/tests/unconstrained.rs +++ b/power-policy-service/tests/unconstrained.rs @@ -16,8 +16,8 @@ use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; use crate::common::HIGH_POWER; use crate::common::{ - DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_unconstrained, mock::FnCall, - run_test, + DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_no_event, assert_unconstrained, + mock::FnCall, run_test, }; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); @@ -161,6 +161,8 @@ async fn test_unconstrained<'a>( ) .await; } + + assert_no_event(service_receiver); } #[tokio::test] From 9191bd19df2d766a1dfe82b8020b8647c70e1baa Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Tue, 17 Mar 2026 09:30:27 -0700 Subject: [PATCH 39/79] embedded-service/event: Remove `DiscardSender` to `NoopSender` --- embedded-service/src/event.rs | 4 ++-- examples/rt685s-evk/src/bin/type_c.rs | 10 +++++----- examples/rt685s-evk/src/bin/type_c_cfu.rs | 10 +++++----- examples/std/src/bin/power_policy.rs | 10 +++++----- examples/std/src/bin/type_c/service.rs | 10 +++++----- examples/std/src/bin/type_c/ucsi.rs | 10 +++++----- examples/std/src/bin/type_c/unconstrained.rs | 10 +++++----- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index 905f645cd..dfabd3d8a 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -45,9 +45,9 @@ impl Receiver for DynamicReceiver<'_, E> { } /// A sender that discards all events sent to it. -pub struct DiscardSender; +pub struct NoopSender; -impl Sender for DiscardSender { +impl Sender for NoopSender { fn try_send(&mut self, _event: E) -> Option<()> { Some(()) } diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 77f7e8444..43e7fafc8 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -15,7 +15,7 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; -use embedded_services::event::DiscardSender; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; @@ -84,7 +84,7 @@ async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; @@ -211,13 +211,13 @@ async fn main(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); - static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); static POWER_SERVICE: StaticCell< Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index eb03af50d..d55a58f4c 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -18,7 +18,7 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::event::DiscardSender; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; @@ -168,7 +168,7 @@ async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; @@ -289,13 +289,13 @@ async fn main(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); - static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); static POWER_SERVICE: StaticCell< Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index ddc014452..19ce9259d 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -5,7 +5,7 @@ use embassy_sync::{ mutex::Mutex, }; use embassy_time::{self as _, Timer}; -use embedded_services::{GlobalRawMutex, event::DiscardSender, named::Named}; +use embedded_services::{GlobalRawMutex, event::NoopSender, named::Named}; use log::*; use power_policy_interface::psu::{Error, Psu}; use power_policy_interface::{ @@ -136,13 +136,13 @@ async fn run(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); - static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); static SERVICE: StaticCell< Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, > = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( @@ -293,7 +293,7 @@ async fn power_policy_task( >, power_policy: &'static Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 5d38cda8a..1d9cbf559 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,7 +5,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::event::DiscardSender; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; @@ -76,13 +76,13 @@ async fn task(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 1]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy]); - static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); static POWER_SERVICE: StaticCell< Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( @@ -155,7 +155,7 @@ async fn power_policy_task( psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index ca8a8b182..c4efb0ce2 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -7,7 +7,7 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; -use embedded_services::event::DiscardSender; +use embedded_services::event::NoopSender; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -183,7 +183,7 @@ async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; @@ -309,11 +309,11 @@ async fn task(spawner: Spawner) { static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy]); - static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); static POWER_SERVICE: StaticCell< - Mutex>, + Mutex>, > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( psu_registration, diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index a3c0eeadb..3b573411b 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -8,7 +8,7 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; -use embedded_services::event::DiscardSender; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; @@ -180,13 +180,13 @@ async fn task(spawner: Spawner) { &wrapper2.ports[0].proxy, ]); - static POWER_POLICY_EVENT_SENDERS: StaticCell<[DiscardSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([DiscardSender]); + static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); + let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); static POWER_SERVICE: StaticCell< Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, > = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( @@ -295,7 +295,7 @@ async fn power_policy_task( psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, DiscardSender>, + power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, >, ) { power_policy_service::service::task::task(psu_events, power_policy).await; From 5cf9ab2c593a08eddbd3a2d3f1b9b70930a947af Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Tue, 17 Mar 2026 10:24:08 -0700 Subject: [PATCH 40/79] power-policy-service: Introduce registration trait --- examples/rt685s-evk/src/bin/type_c.rs | 30 +++++----- examples/rt685s-evk/src/bin/type_c_cfu.rs | 30 +++++----- examples/std/src/bin/power_policy.rs | 31 +++++------ power-policy-service/src/service/consumer.rs | 31 ++++------- power-policy-service/src/service/mod.rs | 55 +++++++------------ power-policy-service/src/service/provider.rs | 24 ++++---- .../src/service/registration.rs | 48 ++++++++++++++++ power-policy-service/src/service/task.rs | 23 +++----- power-policy-service/tests/common/mod.rs | 38 +++++++------ 9 files changed, 157 insertions(+), 153 deletions(-) create mode 100644 power-policy-service/src/service/registration.rs diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 43e7fafc8..d8f805072 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -21,6 +21,7 @@ use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; use power_policy_service::psu::EventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; @@ -65,6 +66,11 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { loop { @@ -82,10 +88,7 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -208,21 +211,14 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + service_senders: [NoopSender], + }; - static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); - - static POWER_SERVICE: StaticCell< - Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, - > = StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, - power_policy_event_senders.as_mut_slice(), + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index d55a58f4c..c75b79496 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -24,6 +24,7 @@ use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; use power_policy_service::psu::EventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; @@ -63,6 +64,11 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; @@ -166,10 +172,7 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -286,21 +289,14 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); - - static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell< - Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, - > = StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, - power_policy_event_senders.as_mut_slice(), + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 19ce9259d..efa7d3d3b 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -12,9 +12,14 @@ use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, psu, }; -use power_policy_service::psu::EventReceivers; +use power_policy_service::{psu::EventReceivers, service::registration::ArrayRegistration}; use static_cell::StaticCell; +type ServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + const LOW_POWER: PowerCapability = PowerCapability { voltage_mv: 5000, current_ma: 1500, @@ -133,21 +138,14 @@ async fn run(spawner: Spawner) { static SERVICE_CONTEXT: StaticCell = StaticCell::new(); let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); - - static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); + let registration = ArrayRegistration { + psus: [device0, device1], + service_senders: [NoopSender], + }; - static SERVICE: StaticCell< - Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, - > = StaticCell::new(); + static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration.as_slice(), - power_policy_event_senders.as_mut_slice(), + registration, service_context, power_policy_service::service::config::Config::default(), ))); @@ -291,10 +289,7 @@ async fn power_policy_task( DeviceType, channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, - power_policy: &'static Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, + power_policy: &'static ServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index a6aa88371..b52b0e2b7 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -6,32 +6,27 @@ use super::*; use power_policy_interface::capability::ConsumerFlags; use power_policy_interface::charger::Device as ChargerDevice; +use power_policy_interface::psu; use power_policy_interface::service::event::Event as ServiceEvent; use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::PsuState}; /// State of the current consumer #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AvailableConsumer<'device, PSU: Lockable> -where - PSU::Inner: Psu, -{ +pub struct AvailableConsumer<'device, Psu: Lockable> { /// Device reference - pub psu: &'device PSU, + pub psu: &'device Psu, /// The power capability of the currently connected consumer pub consumer_power_capability: ConsumerPowerCapability, } -impl<'device, PSU: Lockable> Clone for AvailableConsumer<'device, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Psu: Lockable> Clone for AvailableConsumer<'device, Psu> { fn clone(&self) -> Self { *self } } -impl<'device, PSU: Lockable> Copy for AvailableConsumer<'device, PSU> where PSU::Inner: Psu {} +impl<'device, Psu: Lockable> Copy for AvailableConsumer<'device, Psu> {} /// Compare two consumer capabilities to determine which one is better /// @@ -47,17 +42,13 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl<'device, 'device_storage, 'sender_storage, PSU: Lockable, EventSender: Sender>> - Service<'device, 'device_storage, 'sender_storage, PSU, EventSender> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Iterate over all devices to determine what is best power port provides the highest power - async fn find_best_consumer(&self) -> Result>, Error> { + async fn find_best_consumer(&self) -> Result>, Error> { let mut best_consumer = None; let current_consumer = self.state.current_consumer_state.as_ref().map(|f| f.psu); - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let locked_psu = psu.lock().await; let consumer_capability = locked_psu.state().consumer_capability; // Don't consider consumers below minimum threshold @@ -110,7 +101,7 @@ where async fn update_unconstrained_state(&mut self) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { if let Some(capability) = psu.lock().await.state().consumer_capability { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; @@ -137,7 +128,7 @@ where /// Common logic to execute after a consumer is connected async fn post_consumer_connected( &mut self, - connected_consumer: AvailableConsumer<'device, PSU>, + connected_consumer: AvailableConsumer<'device, Reg::Psu>, ) -> Result<(), Error> { self.state.current_consumer_state = Some(connected_consumer); // todo: review the delay time @@ -196,7 +187,7 @@ where } /// Connect to a new consumer - async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'device, PSU>) -> Result<(), Error> { + async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'device, Reg::Psu>) -> Result<(), Error> { // Handle our current consumer if let Some(current_consumer) = self.state.current_consumer_state { if ptr::eq(current_consumer.psu, new_consumer.psu) diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index d27193f3a..bf48f173e 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -5,6 +5,7 @@ pub mod config; pub mod consumer; pub mod context; pub mod provider; +pub mod registration; pub mod task; use embedded_services::named::Named; @@ -19,6 +20,8 @@ use power_policy_interface::{ service::{UnconstrainedState, event::Event as ServiceEvent}, }; +use crate::service::registration::Registration; + const MAX_CONNECTED_PROVIDERS: usize = 4; #[derive(Clone)] @@ -51,45 +54,25 @@ where } /// Power policy service -pub struct Service< - 'device, - 'device_storage, - 'sender_storage, - PSU: Lockable, - EventSender: Sender>, -> where - PSU::Inner: Psu, -{ +pub struct Service<'device, Reg: Registration<'device>> { + /// Service registration + registration: Reg, /// Power policy context pub context: &'device context::Context, - /// PSU devices - psu_devices: &'device_storage [&'device PSU], /// State - state: InternalState<'device, PSU>, + state: InternalState<'device, Reg::Psu>, /// Config config: config::Config, - /// Senders for service events - event_senders: &'sender_storage mut [EventSender], } -impl<'device, 'device_storage, 'sender_storage, PSU: Lockable, EventSender: Sender>> - Service<'device, 'device_storage, 'sender_storage, PSU, EventSender> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Create a new power policy - pub fn new( - psu_devices: &'device_storage [&'device PSU], - event_senders: &'sender_storage mut [EventSender], - context: &'device context::Context, - config: config::Config, - ) -> Self { + pub fn new(registration: Reg, context: &'device context::Context, config: config::Config) -> Self { Self { + registration, context, - psu_devices, state: InternalState::default(), config, - event_senders, } } @@ -97,7 +80,7 @@ where pub async fn compute_total_provider_power_mw(&self) -> u32 { let mut total = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let psu = psu.lock().await; total += psu .state() @@ -109,7 +92,7 @@ where total } - async fn process_notify_attach(&self, device: &'device PSU) { + async fn process_notify_attach(&self, device: &'device Reg::Psu) { let mut device = device.lock().await; info!("({}): Received notify attached", device.name()); if let Err(e) = device.state_mut().attach() { @@ -117,7 +100,7 @@ where } } - async fn process_notify_detach(&mut self, device: &'device PSU) -> Result<(), Error> { + async fn process_notify_detach(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { { let mut device = device.lock().await; info!("({}): Received notify detached", device.name()); @@ -131,7 +114,7 @@ where async fn process_notify_consumer_power_capability( &mut self, - device: &'device PSU, + device: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { { @@ -155,7 +138,7 @@ where async fn process_request_provider_power_capabilities( &mut self, - requester: &'device PSU, + requester: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { { @@ -180,7 +163,7 @@ where self.connect_provider(requester).await } - async fn process_notify_disconnect(&mut self, device: &'device PSU) -> Result<(), Error> { + async fn process_notify_disconnect(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { { let mut locked_device = device.lock().await; info!("({}): Received notify disconnect", locked_device.name()); @@ -200,13 +183,13 @@ where } /// Send an event to all registered listeners - async fn broadcast_event(&mut self, event: ServiceEvent<'device, PSU>) { - for sender in self.event_senders.iter_mut() { + async fn broadcast_event(&mut self, event: ServiceEvent<'device, Reg::Psu>) { + for sender in self.registration.event_senders() { sender.send(event).await; } } - pub async fn process_psu_event(&mut self, event: PsuEvent<'device, PSU>) -> Result<(), Error> { + pub async fn process_psu_event(&mut self, event: PsuEvent<'device, Reg::Psu>) -> Result<(), Error> { let device = event.psu; match event.event { PsuEventData::Attached => { diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index f59636a1d..cdc424fbc 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -28,13 +28,9 @@ pub(super) struct State { state: PowerState, } -impl<'device, 'device_storage, 'sender_storage, PSU: Lockable, EventSender: Sender>> - Service<'device, 'device_storage, 'sender_storage, PSU, EventSender> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&mut self, requester: &'device PSU) -> Result<(), Error> { + pub(super) async fn connect_provider(&mut self, requester: &'device Reg::Psu) -> Result<(), Error> { let requested_power_capability = { let requester = requester.lock().await; debug!("({}): Attempting to connect as provider", requester.name()); @@ -50,7 +46,7 @@ where // Determine total requested power draw let mut total_power_mw = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let target_provider_cap = if ptr::eq(*psu, requester) { // Use the requester's requested power capability // this handles both new connections and upgrade requests @@ -106,11 +102,11 @@ where } /// Common logic for after a provider has successfully connected - async fn post_provider_connected(&mut self, requester: &'device PSU, target_power: ProviderPowerCapability) { + async fn post_provider_connected(&mut self, requester: &'device Reg::Psu, target_power: ProviderPowerCapability) { if self .state .connected_providers - .insert(requester as *const PSU as usize) + .insert(requester as *const Reg::Psu as usize) .is_err() { error!("Tracked providers set is full"); @@ -122,11 +118,15 @@ where /// Common logic for when a provider is disconnected /// /// Returns true if the device was operating as a provider - pub(super) async fn remove_connected_provider(&mut self, psu: &'device PSU) -> bool { - if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { + pub(super) async fn remove_connected_provider(&mut self, psu: &'device Reg::Psu) -> bool { + if self + .state + .connected_providers + .remove(&(psu as *const Reg::Psu as usize)) + { // Determine total requested power draw let mut total_power_mw = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let target_provider_cap = psu.lock().await.state().connected_provider_capability(); total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); } diff --git a/power-policy-service/src/service/registration.rs b/power-policy-service/src/service/registration.rs new file mode 100644 index 000000000..e7de916ec --- /dev/null +++ b/power-policy-service/src/service/registration.rs @@ -0,0 +1,48 @@ +//! Code related to registration with the power policy service. +use embedded_services::{event::Sender, sync::Lockable}; +use power_policy_interface::{psu, service::event::Event as ServiceEvent}; + +/// Registration trait that abstracts over various registration details. +pub trait Registration<'device> { + type Psu: Lockable + 'device; + type ServiceSender: Sender>; + + /// Returns a slice to access PSU devices + fn psus(&self) -> &[&'device Self::Psu]; + /// Returns a slice to access power policy event senders + fn event_senders(&mut self) -> &mut [Self::ServiceSender]; +} + +/// A registration implementation based around arrays +pub struct ArrayRegistration< + 'device, + Psu: Lockable + 'device, + const PSU_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> { + /// Array of registered PSUs + pub psus: [&'device Psu; PSU_COUNT], + /// Array of power policy service event senders + pub service_senders: [ServiceSender; SERVICE_SENDER_COUNT], +} + +impl< + 'device, + Psu: Lockable + 'device, + const PSU_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> Registration<'device> for ArrayRegistration<'device, Psu, PSU_COUNT, ServiceSender, SERVICE_SENDER_COUNT> +{ + type Psu = Psu; + type ServiceSender = ServiceSender; + + fn psus(&self) -> &[&'device Self::Psu] { + &self.psus + } + + fn event_senders(&mut self) -> &mut [Self::ServiceSender] { + &mut self.service_senders + } +} diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index e871f5d62..a65d853a8 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,30 +1,23 @@ use embedded_services::{error, info, sync::Lockable}; -use embedded_services::event::{Receiver, Sender}; -use power_policy_interface::psu::Psu; +use embedded_services::event::Receiver; use power_policy_interface::psu::event::EventData; -use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::service::registration::Registration; use super::Service; /// Runs the power policy task. pub async fn task< 'device, - 'device_storage, - 'sender_storage, const PSU_COUNT: usize, - S: Lockable>, - PSU: Lockable, - R: Receiver, - EventSender: Sender> + 'sender_storage, + S: Lockable>, + Reg: Registration<'device>, + PsuReceiver: Receiver, >( - mut psu_events: crate::psu::EventReceivers<'device, PSU_COUNT, PSU, R>, + mut psu_events: crate::psu::EventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, policy: &'device S, -) -> ! -where - PSU::Inner: Psu, - 'device: 'device_storage, -{ +) -> ! { info!("Starting power policy task"); loop { let event = psu_events.wait_event().await; diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 44ab1e9b4..0a17a0f6b 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -20,8 +20,8 @@ use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, service::{UnconstrainedState, event::Event as ServiceEvent}, }; -use power_policy_service::psu::EventReceivers; use power_policy_service::service::Service; +use power_policy_service::{psu::EventReceivers, service::registration::ArrayRegistration}; pub mod mock; @@ -45,17 +45,20 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); const EVENT_CHANNEL_SIZE: usize = 4; pub type DeviceType<'a> = Mutex>>; -pub type ServiceType<'device, 'device_storage, 'sender_storage> = Service< +pub type ServiceType<'device, 'sender> = Service< 'device, - 'device_storage, - 'sender_storage, - DeviceType<'device>, - DynamicSender<'sender_storage, ServiceEvent<'device, DeviceType<'device>>>, + ArrayRegistration< + 'device, + DeviceType<'device>, + 2, + DynamicSender<'sender, ServiceEvent<'device, DeviceType<'device>>>, + 1, + >, >; -async fn power_policy_task<'device, 'device_storage, 'sender_storage, const N: usize>( +async fn power_policy_task<'device, 'sender, const N: usize>( completion_signal: &'device Signal, - mut power_policy: ServiceType<'device, 'device_storage, 'sender_storage>, + mut power_policy: ServiceType<'device, 'sender>, mut event_receivers: EventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { @@ -124,7 +127,6 @@ where let device1 = Mutex::new(Mock::new("PSU1", device1_sender, &device1_signal)); let service_context = power_policy_service::service::context::Context::new(); - let psu_registration = [&device0, &device1]; let completion_signal = Signal::new(); // Ideally F would have two lifetime arguments: 'device and 'sender because the event type requires 'device: 'sender. @@ -135,15 +137,15 @@ where let service_event_channel: ManuallyDrop< Channel>, EVENT_CHANNEL_SIZE>, > = ManuallyDrop::new(Channel::new()); - let mut service_sender = [service_event_channel.dyn_sender()]; - let _service_receiver = service_event_channel.dyn_receiver(); + let service_receiver = service_event_channel.dyn_receiver(); - let power_policy = power_policy_service::service::Service::new( - psu_registration.as_slice(), - &mut service_sender, - &service_context, - Default::default(), - ); + let power_policy_registration = ArrayRegistration { + psus: [&device0, &device1], + service_senders: [service_event_channel.dyn_sender()], + }; + + let power_policy = + power_policy_service::service::Service::new(power_policy_registration, &service_context, Default::default()); with_timeout( timeout, @@ -154,7 +156,7 @@ where EventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { - test(_service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; + test(service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; completion_signal.signal(()); }, ), From 799b585a18b1effa2c7b555fd24e8f8798e7eeb7 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Tue, 17 Mar 2026 10:45:59 -0700 Subject: [PATCH 41/79] power-policy-service: Rename EventReceivers Rename to match corresponding registration struct. --- examples/rt685s-evk/src/bin/type_c.rs | 6 +-- examples/rt685s-evk/src/bin/type_c_cfu.rs | 6 +-- examples/std/src/bin/power_policy.rs | 6 +-- examples/std/src/bin/type_c/service.rs | 36 +++++++-------- examples/std/src/bin/type_c/ucsi.rs | 33 +++++++------- examples/std/src/bin/type_c/unconstrained.rs | 46 +++++++++----------- power-policy-service/src/psu.rs | 4 +- power-policy-service/src/service/mod.rs | 4 +- power-policy-service/src/service/provider.rs | 4 +- power-policy-service/src/service/task.rs | 2 +- power-policy-service/tests/common/mod.rs | 6 +-- 11 files changed, 72 insertions(+), 81 deletions(-) diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index d8f805072..2d7ac8724 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -20,7 +20,7 @@ use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; @@ -87,7 +87,7 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; @@ -241,7 +241,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index c75b79496..20f5b65d7 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -23,7 +23,7 @@ use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; @@ -171,7 +171,7 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; @@ -329,7 +329,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index efa7d3d3b..6cf7e51fe 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -12,7 +12,7 @@ use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, psu, }; -use power_policy_service::{psu::EventReceivers, service::registration::ArrayRegistration}; +use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; use static_cell::StaticCell; type ServiceType = Mutex< @@ -151,7 +151,7 @@ async fn run(spawner: Spawner) { ))); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [device0, device1], [ device0_event_channel.dyn_receiver(), @@ -283,7 +283,7 @@ async fn run(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers< + psu_events: ArrayEventReceivers< 'static, 2, DeviceType, diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 1d9cbf559..c11d91a77 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -12,7 +12,8 @@ use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; @@ -31,6 +32,11 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 1, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn controller_task( wrapper: &'static Wrapper<'static>, @@ -73,21 +79,14 @@ async fn task(spawner: Spawner) { let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 1]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy]); - - static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell< - Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, - > = StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, - power_policy_event_senders.as_mut_slice(), + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -120,7 +119,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), + ArrayEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), power_service, )); spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); @@ -152,11 +151,8 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, + psu_events: ArrayEventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index c4efb0ce2..ba6d9cd07 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -17,7 +17,8 @@ use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -38,6 +39,11 @@ const CFU1_ID: u8 = 0x01; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { @@ -180,11 +186,8 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -306,18 +309,14 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy]); - - static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell< - Mutex>, - > = StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, - power_policy_event_senders.as_mut_slice(), + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -370,7 +369,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 3b573411b..a11b38ee2 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -14,7 +14,8 @@ use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -40,6 +41,11 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 3, NoopSender, 1>>, +>; + #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -173,25 +179,18 @@ async fn task(spawner: Spawner) { crate::mock_controller::Validator, )); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 3]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([ - &wrapper0.ports[0].proxy, - &wrapper1.ports[0].proxy, - &wrapper2.ports[0].proxy, - ]); - - static POWER_POLICY_EVENT_SENDERS: StaticCell<[NoopSender; 1]> = StaticCell::new(); - let power_policy_event_senders = POWER_POLICY_EVENT_SENDERS.init([NoopSender]); - - static POWER_SERVICE: StaticCell< - Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, - > = StaticCell::new(); + let power_policy_registration = ArrayRegistration { + psus: [ + &wrapper0.ports[0].proxy, + &wrapper1.ports[0].proxy, + &wrapper2.ports[0].proxy, + ], + service_senders: [NoopSender], + }; + + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, - power_policy_event_senders.as_mut_slice(), + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -221,7 +220,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [ &wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy, @@ -292,11 +291,8 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex< - GlobalRawMutex, - power_policy_service::service::Service<'static, 'static, 'static, DeviceType, NoopSender>, - >, + psu_events: ArrayEventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/power-policy-service/src/psu.rs b/power-policy-service/src/psu.rs index 47ce355d6..81bd2133c 100644 --- a/power-policy-service/src/psu.rs +++ b/power-policy-service/src/psu.rs @@ -7,7 +7,7 @@ use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::{Event, EventData}; /// Struct used to contain PSU event receivers and manage mapping from a receiver to its corresponding device. -pub struct EventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> +pub struct ArrayEventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> where PSU::Inner: Psu, { @@ -15,7 +15,7 @@ where pub receivers: [R; N], } -impl<'a, const N: usize, PSU: Lockable, R: Receiver> EventReceivers<'a, N, PSU, R> +impl<'a, const N: usize, PSU: Lockable, R: Receiver> ArrayEventReceivers<'a, N, PSU, R> where PSU::Inner: Psu, { diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index bf48f173e..3934bc34f 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -107,7 +107,7 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { device.state_mut().detach(); } - self.remove_connected_provider(device).await; + self.post_provider_removed(device).await; self.update_current_consumer().await?; Ok(()) } @@ -177,7 +177,7 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { } } - self.remove_connected_provider(device).await; + self.post_provider_removed(device).await; self.update_current_consumer().await?; Ok(()) } diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index cdc424fbc..02029e61e 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -115,10 +115,10 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { .await; } - /// Common logic for when a provider is disconnected + /// Common logic for when a provider is removed /// /// Returns true if the device was operating as a provider - pub(super) async fn remove_connected_provider(&mut self, psu: &'device Reg::Psu) -> bool { + pub(super) async fn post_provider_removed(&mut self, psu: &'device Reg::Psu) -> bool { if self .state .connected_providers diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index a65d853a8..41c3e2462 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -15,7 +15,7 @@ pub async fn task< Reg: Registration<'device>, PsuReceiver: Receiver, >( - mut psu_events: crate::psu::EventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, + mut psu_events: crate::psu::ArrayEventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, policy: &'device S, ) -> ! { info!("Starting power policy task"); diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 0a17a0f6b..692619092 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -21,7 +21,7 @@ use power_policy_interface::{ service::{UnconstrainedState, event::Event as ServiceEvent}, }; use power_policy_service::service::Service; -use power_policy_service::{psu::EventReceivers, service::registration::ArrayRegistration}; +use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; pub mod mock; @@ -59,7 +59,7 @@ pub type ServiceType<'device, 'sender> = Service< async fn power_policy_task<'device, 'sender, const N: usize>( completion_signal: &'device Signal, mut power_policy: ServiceType<'device, 'sender>, - mut event_receivers: EventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, + mut event_receivers: ArrayEventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { power_policy.process_psu_event(result).await.unwrap(); @@ -153,7 +153,7 @@ where power_policy_task( &completion_signal, power_policy, - EventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), + ArrayEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { test(service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; From cabc36e535e6516cbec5d0f99dee51ce6feedd91 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Fri, 20 Mar 2026 13:30:30 -0700 Subject: [PATCH 42/79] thermal-service: Implement runnable service traits (#758) This PR implements the recently introduced `odp_common::runnable_service` traits for the thermal service. However, this required a little bit of refactoring and rethinking. There was some initial difficulty in making this work for the thermal service as-is due to the fact that we don't know upfront how many sensors and fans will be instantiated by the user, making the `ServiceRunner` trait tricky. Speaking with @williampMSFT, he pointed out that the thermal-service itself is not really a runnable service, but it's better to think of each sensor and fan as a standalone runnable service which is a good idea. Thus, I implement the traits for sensor and fans instead (with the thermal-service struct acting as a coordination and relay handler). However, this still does point out some other little things I want to revisit and refactor. The plan is to move away from IPCs which I currently make use of with the `Device` struct as an intermediary between the thermal-service handler and individual sensor and fan service handlers. Also want to revisit how I can potentially remove the need for the fan service to need a thermal service handler to just talk to a sensor. This will require a bit more thought and I think I'll try to address some of this in the follow-up refactor where we start separating the interface from the implementation (see: #748). This serves as a good start to unblock us and keep moving. --- Cargo.lock | 1 + examples/std/Cargo.lock | 10 ++++ examples/std/Cargo.toml | 1 + examples/std/src/bin/thermal.rs | 33 +++++++------ thermal-service/Cargo.toml | 1 + thermal-service/src/fan.rs | 88 +++++++++++++++++++++++++++++++++ thermal-service/src/lib.rs | 1 - thermal-service/src/sensor.rs | 87 ++++++++++++++++++++++++++++++++ thermal-service/src/task.rs | 18 ------- 9 files changed, 207 insertions(+), 33 deletions(-) delete mode 100644 thermal-service/src/task.rs diff --git a/Cargo.lock b/Cargo.lock index 7f1362236..8e2f5d390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2273,6 +2273,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "odp-service-common", "thermal-service-messages", "uuid", ] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index aa554b464..cf4b71933 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1319,6 +1319,14 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "odp-service-common" +version = "0.1.0" +dependencies = [ + "embedded-services", + "static_cell", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1667,6 +1675,7 @@ dependencies = [ "env_logger", "heapless", "log", + "odp-service-common", "power-policy-interface", "power-policy-service", "static_cell", @@ -1736,6 +1745,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "odp-service-common", "thermal-service-messages", "uuid", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index b63e125f9..cbac45e83 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -43,6 +43,7 @@ embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } thermal-service-messages = { path = "../../thermal-service-messages" } +odp-service-common = { path = "../../odp-service-common" } env_logger = "0.11.8" log = "0.4.14" diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 93f899e7e..f7585071e 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -22,11 +22,26 @@ async fn run(spawner: Spawner) { let fans = FANS.init([fan.device()]); static STORAGE: OnceLock> = OnceLock::new(); - let service = ts::Service::init(&STORAGE, sensors, fans).await; + let thermal_service = ts::Service::init(&STORAGE, sensors, fans).await; - spawner.must_spawn(sensor_task(service, sensor)); - spawner.must_spawn(fan_task(service, fan)); - spawner.must_spawn(monitor(service)); + let _fan_service = odp_service_common::spawn_service!( + spawner, + ts::fan::Service<'static, ts::mock::fan::MockFan, 16>, + ts::fan::InitParams { fan, thermal_service } + ) + .expect("Failed to spawn fan service"); + + let _sensor_service = odp_service_common::spawn_service!( + spawner, + ts::sensor::Service<'static, ts::mock::sensor::MockSensor, 16>, + ts::sensor::InitParams { + sensor, + thermal_service + } + ) + .expect("Failed to spawn sensor service"); + + spawner.must_spawn(monitor(thermal_service)); } fn main() { @@ -39,16 +54,6 @@ fn main() { }); } -#[embassy_executor::task] -async fn sensor_task(service: &'static ts::Service<'static>, sensor: &'static ts::mock::TsMockSensor) { - ts::task::sensor_task(sensor, service).await -} - -#[embassy_executor::task] -async fn fan_task(service: &'static ts::Service<'static>, fan: &'static ts::mock::TsMockFan) { - ts::task::fan_task(fan, service).await; -} - #[embassy_executor::task] async fn monitor(service: &'static ts::Service<'static>) { loop { diff --git a/thermal-service/Cargo.toml b/thermal-service/Cargo.toml index 97eacd2fc..1da4f8e66 100644 --- a/thermal-service/Cargo.toml +++ b/thermal-service/Cargo.toml @@ -17,6 +17,7 @@ embedded-hal-async.workspace = true embedded-hal.workspace = true embedded-services.workspace = true heapless.workspace = true +odp-service-common.workspace = true thermal-service-messages.workspace = true uuid.workspace = true embedded-fans-async = "0.2.0" diff --git a/thermal-service/src/fan.rs b/thermal-service/src/fan.rs index 9f90cc8f6..7ba11fafe 100644 --- a/thermal-service/src/fan.rs +++ b/thermal-service/src/fan.rs @@ -516,3 +516,91 @@ impl Fan { } } } + +/// The memory resources required by the fan. +pub struct Resources<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: Option>, +} + +// Note: We can't derive Default unless we trait bound T by Default, +// but we don't want that restriction since the default is just the None case +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Default for Resources<'hw, T, SAMPLE_BUF_LEN> { + fn default() -> Self { + Self { inner: None } + } +} + +struct ServiceInner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + fan: &'hw Fan, + thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> ServiceInner<'hw, T, SAMPLE_BUF_LEN> { + fn new(init_params: InitParams<'hw, T, SAMPLE_BUF_LEN>) -> Self { + Self { + fan: init_params.fan, + thermal_service: init_params.thermal_service, + } + } + + fn fan(&self) -> &Fan { + self.fan + } +} + +/// A task runner for a fan. Users must run this in an embassy task or similar async execution context. +pub struct Runner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + service: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::ServiceRunner<'hw> + for Runner<'hw, T, SAMPLE_BUF_LEN> +{ + async fn run(self) -> embedded_services::Never { + loop { + let _ = embassy_futures::join::join3( + self.service.fan.handle_rx(), + self.service.fan.handle_sampling(), + self.service.fan.handle_auto_control(self.service.thermal_service), + ) + .await; + } + } +} + +/// Fan service control handle. +pub struct Service<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Service<'hw, T, SAMPLE_BUF_LEN> { + /// Get a reference to the inner fan. + pub fn fan(&self) -> &Fan { + self.inner.fan() + } +} + +/// Parameters required to initialize a fan service. +pub struct InitParams<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + /// The underlying `Fan` wrapper this service will control. + pub fan: &'hw Fan, + /// The thermal service handle for this fan to communicate with a sensor. + pub thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::Service<'hw> + for Service<'hw, T, SAMPLE_BUF_LEN> +{ + type Runner = Runner<'hw, T, SAMPLE_BUF_LEN>; + type Resources = Resources<'hw, T, SAMPLE_BUF_LEN>; + type ErrorType = Error; + type InitParams = InitParams<'hw, T, SAMPLE_BUF_LEN>; + + async fn new( + service_storage: &'hw mut Self::Resources, + init_params: Self::InitParams, + ) -> Result<(Self, Self::Runner), Self::ErrorType> { + let service = service_storage.inner.insert(ServiceInner::new(init_params)); + Ok((Self { inner: service }, Runner { service })) + } +} diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index dc8f70c9a..55d816f99 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -12,7 +12,6 @@ pub mod fan; pub mod mock; pub mod mptf; pub mod sensor; -pub mod task; pub mod utils; /// Thermal error diff --git a/thermal-service/src/sensor.rs b/thermal-service/src/sensor.rs index 69562f34d..1771d14de 100644 --- a/thermal-service/src/sensor.rs +++ b/thermal-service/src/sensor.rs @@ -484,3 +484,90 @@ impl Sensor { } } } + +/// The memory resources required by the sensor. +pub struct Resources<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: Option>, +} + +// Note: We can't derive Default unless we trait bound T by Default, +// but we don't want that restriction since the default is just the None case +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Default for Resources<'hw, T, SAMPLE_BUF_LEN> { + fn default() -> Self { + Self { inner: None } + } +} + +struct ServiceInner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + sensor: &'hw Sensor, + thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> ServiceInner<'hw, T, SAMPLE_BUF_LEN> { + fn new(init_params: InitParams<'hw, T, SAMPLE_BUF_LEN>) -> Self { + Self { + sensor: init_params.sensor, + thermal_service: init_params.thermal_service, + } + } + + fn sensor(&self) -> &Sensor { + self.sensor + } +} + +/// A task runner for a sensor. Users must run this in an embassy task or similar async execution context. +pub struct Runner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + service: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::ServiceRunner<'hw> + for Runner<'hw, T, SAMPLE_BUF_LEN> +{ + async fn run(self) -> embedded_services::Never { + loop { + let _ = embassy_futures::join::join( + self.service.sensor.handle_rx(), + self.service.sensor.handle_sampling(self.service.thermal_service), + ) + .await; + } + } +} + +/// Sensor service control handle. +pub struct Service<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Service<'hw, T, SAMPLE_BUF_LEN> { + /// Get a reference to the inner sensor. + pub fn sensor(&self) -> &Sensor { + self.inner.sensor() + } +} + +/// Parameters required to initialize a sensor service. +pub struct InitParams<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + /// The underlying `Sensor` wrapper this service will control. + pub sensor: &'hw Sensor, + /// The thermal service handle for this sensor to communicate events to. + pub thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::Service<'hw> + for Service<'hw, T, SAMPLE_BUF_LEN> +{ + type Runner = Runner<'hw, T, SAMPLE_BUF_LEN>; + type Resources = Resources<'hw, T, SAMPLE_BUF_LEN>; + type ErrorType = Error; + type InitParams = InitParams<'hw, T, SAMPLE_BUF_LEN>; + + async fn new( + service_storage: &'hw mut Self::Resources, + init_params: Self::InitParams, + ) -> Result<(Self, Self::Runner), Self::ErrorType> { + let service = service_storage.inner.insert(ServiceInner::new(init_params)); + Ok((Self { inner: service }, Runner { service })) + } +} diff --git a/thermal-service/src/task.rs b/thermal-service/src/task.rs deleted file mode 100644 index 29e413f47..000000000 --- a/thermal-service/src/task.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub async fn fan_task<'hw, T: crate::fan::Controller, const SAMPLE_BUF_LEN: usize>( - fan: &crate::fan::Fan, - thermal_service: &crate::Service<'hw>, -) { - let _ = embassy_futures::join::join3( - fan.handle_rx(), - fan.handle_sampling(), - fan.handle_auto_control(thermal_service), - ) - .await; -} - -pub async fn sensor_task<'hw, T: crate::sensor::Controller, const SAMPLE_BUF_LEN: usize>( - sensor: &crate::sensor::Sensor, - thermal_service: &crate::Service<'hw>, -) { - let _ = embassy_futures::join::join(sensor.handle_rx(), sensor.handle_sampling(thermal_service)).await; -} From 71414df072fdb45efdcac23d60f84e40f7e27dce Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Sun, 22 Mar 2026 20:09:27 -0700 Subject: [PATCH 43/79] embedded-services: Fix cortex-m dependencies (#760) Removes the cortex-m-rt dependency entirely since this is not needed and risks pulling in conflicting linker scripts depending on where they are in relation to each other in the search graph when embedded-services is used on a different architecture. Then also made cortex-m only included if the architecture is arm to be extra-safe. Fixes a minor issue I was running into trying to work on a risc-v platform. --- Cargo.lock | 1 - embedded-service/Cargo.toml | 3 +-- examples/pico-de-gallo/Cargo.lock | 21 --------------------- examples/rt633/Cargo.lock | 1 - examples/rt685s-evk/Cargo.lock | 1 - examples/std/Cargo.lock | 21 --------------------- 6 files changed, 1 insertion(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e2f5d390..e7df13d9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,7 +996,6 @@ dependencies = [ "cfg-if", "chrono", "cortex-m", - "cortex-m-rt", "critical-section", "defmt 0.3.100", "document-features", diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index 8185264d0..3211cbe94 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -44,8 +44,7 @@ features = ["serde_derive"] [target.'cfg(not(target_has_atomic = "ptr"))'.dependencies] portable-atomic.workspace = true -[target.'cfg(target_os = "none")'.dependencies] -cortex-m-rt.workspace = true +[target.'cfg(all(target_os = "none", target_arch = "arm"))'.dependencies] cortex-m.workspace = true [dev-dependencies] diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 87248daf5..af0255c9f 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -376,26 +376,6 @@ dependencies = [ "volatile-register", ] -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "critical-section" version = "1.2.0" @@ -623,7 +603,6 @@ dependencies = [ "bitvec", "cfg-if", "cortex-m", - "cortex-m-rt", "critical-section", "document-features", "embassy-futures", diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 10c51c85d..ea72d5a3d 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -642,7 +642,6 @@ dependencies = [ "bitvec", "cfg-if", "cortex-m", - "cortex-m-rt", "critical-section", "defmt 0.3.100", "document-features", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index f227b319d..e8ffda556 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -728,7 +728,6 @@ dependencies = [ "bitvec", "cfg-if", "cortex-m", - "cortex-m-rt", "critical-section", "defmt 0.3.100", "document-features", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index cf4b71933..03b0167f4 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -370,26 +370,6 @@ dependencies = [ "volatile-register", ] -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "critical-section" version = "1.2.0" @@ -791,7 +771,6 @@ dependencies = [ "bitvec", "cfg-if", "cortex-m", - "cortex-m-rt", "critical-section", "document-features", "embassy-futures", From 1332d70f0410544aee09758ef4dce1c8e9cdc2a2 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:59:03 -0700 Subject: [PATCH 44/79] workflows/check: Add test code coverage report (#764) Upload a full report as an artifact and provide a summary --- .github/workflows/check.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1defa5dd0..b3ba009cd 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -147,6 +147,9 @@ jobs: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable + with: + # For grcov + components: llvm-tools - name: Download Cargo.lock files if: ${{ inputs.download-lockfiles }} uses: actions/download-artifact@v4 @@ -154,10 +157,25 @@ jobs: name: updated-lock-files - name: cargo test run: cargo test --locked + env: + RUSTFLAGS: '-C instrument-coverage' + CARGO_INCREMENTAL: '0' # After ensuring tests pass, finally ensure the test code itself contains no clippy warnings - name: cargo clippy run: | cargo clippy --locked --tests + # Generate and upload test coverage report + - name: Install grcov + uses: taiki-e/install-action@grcov + - name: Generate test coverage report + run: | + grcov . --binary-path ./target/debug/deps -s . --branch --ignore-not-existing --ignore "/home/runner/*" -t html -o ./target/debug/coverage + grcov . --binary-path ./target/debug/deps -s . --branch --ignore-not-existing --ignore "/home/runner/*" -t markdown >> $GITHUB_STEP_SUMMARY + - name: Upload test coverage report + uses: actions/upload-artifact@v4 + with: + name: test-coverage-report + path: ./target/debug/coverage msrv: # check that we can build using the minimal rust version that is specified by this crate From 5b2f9d834fe6d064b518168254610d5482048f55 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:56:39 -0700 Subject: [PATCH 45/79] power-policy-service: Add test coverage for provider power (#762) --- power-policy-service/tests/common/mod.rs | 37 +++++++++++++++------ power-policy-service/tests/consumer.rs | 28 +++++++++++++++- power-policy-service/tests/provider.rs | 15 ++++++++- power-policy-service/tests/unconstrained.rs | 3 +- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 692619092..2e080c422 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -56,13 +56,15 @@ pub type ServiceType<'device, 'sender> = Service< >, >; +pub type ServiceMutex<'device, 'sender> = Mutex>; + async fn power_policy_task<'device, 'sender, const N: usize>( completion_signal: &'device Signal, - mut power_policy: ServiceType<'device, 'sender>, + power_policy: &ServiceMutex<'device, 'sender>, mut event_receivers: ArrayEventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { - power_policy.process_psu_event(result).await.unwrap(); + power_policy.lock().await.process_psu_event(result).await.unwrap(); } } @@ -71,6 +73,7 @@ async fn power_policy_task<'device, 'sender, const N: usize>( /// The trait we want to express for `run_test` is something like: /// ``` /// for<'a> F: FnOnce( +/// &'a ServiceMutex<'a, 'a>, /// &'a Mutex>>, /// &'a Signal, /// &'a Mutex>>, @@ -79,16 +82,16 @@ async fn power_policy_task<'device, 'sender, const N: usize>( /// ``` /// However, `impl (Future + 'a)` is not real syntax. This could be done with the unstable feature type_alias_impl_trait, /// but we use this helper trait so as to not require use of nightly. -pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a>: - FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Self::Fut +pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, Arg5: 'a>: + FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) -> Self::Fut { type Fut: Future; } -impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3, Arg4> - for F +impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, Arg5: 'a, F, Fut> + TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5> for F where - F: FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Fut, + F: FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) -> Fut, Fut: Future, { type Fut = Fut; @@ -98,6 +101,7 @@ pub async fn run_test(timeout: Duration, test: F) where for<'a> F: TestArgsFnOnce< 'a, + &'a ServiceMutex<'a, 'a>, DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, &'a DeviceType<'a>, &'a Signal, @@ -144,19 +148,30 @@ where service_senders: [service_event_channel.dyn_sender()], }; - let power_policy = - power_policy_service::service::Service::new(power_policy_registration, &service_context, Default::default()); + let power_policy = Mutex::new(power_policy_service::service::Service::new( + power_policy_registration, + &service_context, + Default::default(), + )); with_timeout( timeout, join( power_policy_task( &completion_signal, - power_policy, + &power_policy, ArrayEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { - test(service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; + test( + &power_policy, + service_receiver, + &device0, + &device0_signal, + &device1, + &device1_signal, + ) + .await; completion_signal.signal(()); }, ), diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 9fd5ac299..01e9960d3 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -8,7 +8,7 @@ use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability} mod common; -use common::LOW_POWER; +use common::{LOW_POWER, ServiceMutex}; use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; @@ -21,6 +21,7 @@ const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic consumer flow with a single device. async fn test_single<'a>( + service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, @@ -57,6 +58,9 @@ async fn test_single<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } // Test detach { @@ -70,6 +74,8 @@ async fn test_single<'a>( device0_signal.reset(); assert_consumer_disconnected(service_receiver, device0).await; + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } assert_no_event(service_receiver); @@ -77,6 +83,7 @@ async fn test_single<'a>( /// Test swapping to a higher powered device. async fn test_swap_higher<'a>( + service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, @@ -113,6 +120,9 @@ async fn test_swap_higher<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } // Device1 connection at high power { @@ -152,6 +162,9 @@ async fn test_swap_higher<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } // Test detach device1, should reconnect device0 { @@ -187,6 +200,9 @@ async fn test_swap_higher<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } assert_no_event(service_receiver); @@ -194,6 +210,7 @@ async fn test_swap_higher<'a>( /// Test a disconnect initiated by the current consumer. async fn test_disconnect<'a>( + service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, @@ -230,6 +247,9 @@ async fn test_disconnect<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } // Device1 connection at high power { @@ -269,6 +289,9 @@ async fn test_disconnect<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } // Test disconnect device1, should reconnect device0 @@ -305,6 +328,9 @@ async fn test_disconnect<'a>( }, ) .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } assert_no_event(service_receiver); diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs index c5a789117..505ce09b0 100644 --- a/power-policy-service/tests/provider.rs +++ b/power-policy-service/tests/provider.rs @@ -9,7 +9,7 @@ use power_policy_interface::capability::ProviderPowerCapability; mod common; -use common::LOW_POWER; +use common::{LOW_POWER, ServiceMutex}; use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; @@ -21,6 +21,7 @@ const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic provider flow with a single device. async fn test_single<'a>( + _service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, @@ -73,6 +74,7 @@ async fn test_single<'a>( /// Test provider flow involving multiple devices and upgrading a provider's power capability. async fn test_upgrade<'a>( + service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, @@ -105,6 +107,8 @@ async fn test_upgrade<'a>( }, ) .await; + + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 15000); } { @@ -132,6 +136,8 @@ async fn test_upgrade<'a>( }, ) .await; + + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 22500); } { @@ -164,6 +170,8 @@ async fn test_upgrade<'a>( }, ) .await; + + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 22500); } { @@ -178,6 +186,7 @@ async fn test_upgrade<'a>( device0_signal.reset(); assert_provider_disconnected(service_receiver, device0).await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); } { @@ -209,6 +218,7 @@ async fn test_upgrade<'a>( }, ) .await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 15000); } assert_no_event(service_receiver); @@ -216,6 +226,7 @@ async fn test_upgrade<'a>( /// Test the provider disconnect flow async fn test_disconnect<'a>( + service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, @@ -248,6 +259,7 @@ async fn test_disconnect<'a>( }, ) .await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); } // Test disconnect { @@ -261,6 +273,7 @@ async fn test_disconnect<'a>( device0_signal.reset(); assert_provider_disconnected(service_receiver, device0).await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); } assert_no_event(service_receiver); diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs index 2bea43857..e4be7e507 100644 --- a/power-policy-service/tests/unconstrained.rs +++ b/power-policy-service/tests/unconstrained.rs @@ -13,17 +13,18 @@ use common::LOW_POWER; use power_policy_interface::service::UnconstrainedState; use power_policy_interface::service::event::Event as ServiceEvent; -use crate::common::DeviceType; use crate::common::HIGH_POWER; use crate::common::{ DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_no_event, assert_unconstrained, mock::FnCall, run_test, }; +use crate::common::{DeviceType, ServiceMutex}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test unconstrained consumer flow with multiple devices. async fn test_unconstrained<'a>( + _service: &ServiceMutex<'a, 'a>, service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, device0: &DeviceType<'a>, device0_signal: &Signal, From 114c68cad67b2199f17c173b37a1e790be30494a Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:03:07 -0700 Subject: [PATCH 46/79] Battery-service: convert to uniform init / external resources (#759) Update battery service to implement the uniform init trait. This is a precursor to splitting the public interface from the relay code. --- Cargo.lock | 1 + battery-service/Cargo.toml | 1 + battery-service/src/lib.rs | 162 +++++++++++++++++----- battery-service/src/mock.rs | 6 +- battery-service/src/task.rs | 42 ------ examples/pico-de-gallo/Cargo.lock | 10 ++ examples/pico-de-gallo/Cargo.toml | 1 + examples/pico-de-gallo/src/bin/battery.rs | 73 +++++----- examples/rt633/Cargo.lock | 1 + examples/std/Cargo.lock | 1 + examples/std/Cargo.toml | 2 +- examples/std/src/bin/battery.rs | 50 ++++--- 12 files changed, 205 insertions(+), 145 deletions(-) delete mode 100644 battery-service/src/task.rs diff --git a/Cargo.lock b/Cargo.lock index e7df13d9b..ce5ddac49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "odp-service-common", "power-policy-interface", "zerocopy", ] diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 6d867a81e..0483e8162 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -21,6 +21,7 @@ embedded-hal-async.workspace = true embedded-hal.workspace = true embedded-services.workspace = true log = { workspace = true, optional = true } +odp-service-common.workspace = true zerocopy.workspace = true mctp-rs = { workspace = true, features = ["espi"] } heapless.workspace = true diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index 8a7391692..c0fbcb3a8 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -6,7 +6,7 @@ use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest, AcpiBattery use context::BatteryEvent; use embedded_services::{ comms::{self, EndpointID}, - error, trace, + error, info, trace, }; mod acpi; @@ -15,104 +15,192 @@ pub mod controller; pub mod device; #[cfg(feature = "mock")] pub mod mock; -pub mod task; pub mod wrapper; -/// Standard Battery Service. -pub struct Service { - pub endpoint: comms::Endpoint, - pub context: context::Context, +/// Parameters required to initialize the battery service. +pub struct InitParams<'hw, const N: usize> { + pub devices: [&'hw device::Device; N], + pub config: context::Config, } -impl Service { - /// Create a new battery service instance. - pub const fn new() -> Self { - Self::new_inner(context::Config::new()) - } - - /// Create a new battery service instance with context configuration. - pub const fn new_with_ctx_config(config: context::Config) -> Self { - Self::new_inner(config) - } +/// The main service implementation. +struct ServiceInner { + endpoint: comms::Endpoint, + context: context::Context, +} - const fn new_inner(config: context::Config) -> Self { - Service { +impl ServiceInner { + fn new(config: context::Config) -> Self { + Self { endpoint: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Battery)), context: context::Context::new_with_config(config), } } /// Main battery service processing function. - pub async fn process_next(&self) { + async fn process_next(&self) { let event = self.wait_next().await; self.process_event(event).await } /// Wait for next event. - pub async fn wait_next(&self) -> BatteryEvent { + async fn wait_next(&self) -> BatteryEvent { self.context.wait_event().await } /// Process battery service event. - pub async fn process_event(&self, event: BatteryEvent) { + async fn process_event(&self, event: BatteryEvent) { trace!("Battery service: state machine event recvd {:?}", event); self.context.process(event).await } /// Register fuel gauge device with the battery service. - /// - /// Must be done before sending the battery service commands so that hardware device is visible - /// to the battery service. - pub(crate) fn register_fuel_gauge( + fn register_fuel_gauge( &self, device: &'static device::Device, ) -> Result<(), embedded_services::intrusive_list::Error> { self.context.register_fuel_gauge(device)?; - Ok(()) } /// Use the battery service endpoint to send data to other subsystems and services. - pub async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { + async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { self.endpoint.send(endpoint_id, data).await } + /// Send the battery service state machine an event and await a response. + async fn execute_event(&self, event: BatteryEvent) -> context::BatteryResponse { + self.context.execute_event(event).await + } + + /// Wait for a response from the battery service. + async fn wait_for_battery_response(&self) -> context::BatteryResponse { + self.context.wait_response().await + } + + /// Asynchronously query the state from the state machine. + async fn get_state(&self) -> context::State { + self.context.get_state().await + } +} + +/// The memory resources required by the battery service. +#[derive(Default)] +pub struct Resources { + inner: Option>, +} + +/// A task runner for the battery service. Users of the service must run this object in an embassy task or similar async execution context. +pub struct Runner<'hw, const N: usize> { + service: &'hw ServiceInner, +} + +impl<'hw, const N: usize> odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw, N> { + /// Run the service. + async fn run(self) -> embedded_services::Never { + info!("Starting battery-service"); + loop { + self.service.process_next().await; + } + } +} + +/// Control handle for the battery service. Use this to interact with the battery service. +#[derive(Clone, Copy)] +pub struct Service<'hw, const N: usize> { + inner: &'hw ServiceInner, +} + +impl<'hw, const N: usize> Service<'hw, N> { + /// Main battery service processing function. + pub async fn process_next(&self) { + self.inner.process_next().await + } + + /// Wait for next event. + pub async fn wait_next(&self) -> BatteryEvent { + self.inner.wait_next().await + } + + /// Process battery service event. + pub async fn process_event(&self, event: BatteryEvent) { + self.inner.process_event(event).await + } + + /// Use the battery service endpoint to send data to other subsystems and services. + pub async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> { + self.inner.comms_send(endpoint_id, data).await + } + /// Send the battery service state machine an event and await a response. /// /// This is an alternative method of interacting with the battery service (instead of using the comms service), /// and is a useful fn if you want to send an event and await a response sequentially. pub async fn execute_event(&self, event: BatteryEvent) -> context::BatteryResponse { - self.context.execute_event(event).await + self.inner.execute_event(event).await } /// Wait for a response from the battery service. /// /// Use this function after sending the battery service a message via the comms system. pub async fn wait_for_battery_response(&self) -> context::BatteryResponse { - self.context.wait_response().await + self.inner.wait_for_battery_response().await } /// Asynchronously query the state from the state machine. pub async fn get_state(&self) -> context::State { - self.context.get_state().await + self.inner.get_state().await } } -impl Default for Service { - fn default() -> Self { - Self::new() +/// Errors that can occur during battery service initialization. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InitError { + DeviceRegistrationFailed(crate::device::DeviceId), + CommsRegistrationFailed, +} + +impl<'hw, const N: usize> odp_service_common::runnable_service::Service<'hw> for Service<'hw, N> +where + 'hw: 'static, // TODO relax this 'static requirement when we drop usages of IntrusiveList (including comms) +{ + type Runner = Runner<'hw, N>; + type ErrorType = InitError; + type InitParams = InitParams<'hw, N>; + type Resources = Resources; + + async fn new( + service_storage: &'hw mut Resources, + init_params: Self::InitParams, + ) -> Result<(Self, Runner<'hw, N>), InitError> { + let service = service_storage.inner.insert(ServiceInner::new(init_params.config)); + + for device in init_params.devices { + if service.register_fuel_gauge(device).is_err() { + error!("Failed to register battery device with DeviceId {:?}", device.id()); + return Err(InitError::DeviceRegistrationFailed(device.id())); + } + } + + if comms::register_endpoint(service, &service.endpoint).await.is_err() { + error!("Failed to register battery service endpoint"); + return Err(InitError::CommsRegistrationFailed); + } + + Ok((Self { inner: service }, Runner { service })) } } -impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service { +impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'_, N> { type RequestType = AcpiBatteryRequest; type ResultType = AcpiBatteryResult; } -impl embedded_services::relay::mctp::RelayServiceHandler for Service { +impl embedded_services::relay::mctp::RelayServiceHandler for Service<'_, N> { async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { trace!("Battery service: ACPI cmd recvd"); - let response = self.context.process_acpi_cmd(&request).await; + let response = self.inner.context.process_acpi_cmd(&request).await; if let Err(e) = response { error!("Battery service command failed: {:?}", e) } @@ -120,7 +208,7 @@ impl embedded_services::relay::mctp::RelayServiceHandler for Service { } } -impl comms::MailboxDelegate for Service { +impl comms::MailboxDelegate for ServiceInner { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { if let Some(event) = message.data.get::() { self.context.send_event_no_wait(*event).map_err(|e| match e { diff --git a/battery-service/src/mock.rs b/battery-service/src/mock.rs index 2fafe5e53..25ba29dea 100644 --- a/battery-service/src/mock.rs +++ b/battery-service/src/mock.rs @@ -6,7 +6,9 @@ use embedded_batteries_async::{ use embedded_services::{GlobalRawMutex, error, info}; // Convenience fns -pub async fn init_state_machine(battery_service: &'static crate::Service) -> Result<(), crate::context::ContextError> { +pub async fn init_state_machine( + battery_service: &crate::Service<'_, N>, +) -> Result<(), crate::context::ContextError> { battery_service .execute_event(crate::context::BatteryEvent { event: crate::context::BatteryEventInner::DoInit, @@ -34,7 +36,7 @@ pub async fn init_state_machine(battery_service: &'static crate::Service) -> Res Ok(()) } -pub async fn recover_state_machine(battery_service: &'static crate::Service) -> Result<(), ()> { +pub async fn recover_state_machine(battery_service: &crate::Service<'_, N>) -> Result<(), ()> { loop { match battery_service .execute_event(crate::context::BatteryEvent { diff --git a/battery-service/src/task.rs b/battery-service/src/task.rs deleted file mode 100644 index 8f38285a4..000000000 --- a/battery-service/src/task.rs +++ /dev/null @@ -1,42 +0,0 @@ -use battery_service_messages::DeviceId; -use embedded_services::{comms, error, info}; - -use crate::{Service, device::Device}; - -/// Standard dynamic battery data cache -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InitError { - DeviceRegistrationFailed(heapless::Vec), - CommsRegistrationFailed, -} - -/// Battery service task. -pub async fn task( - service: &'static Service, - devices: [&'static Device; N], -) -> Result<(), InitError> { - info!("Starting battery-service task"); - - let mut failed_devices = heapless::Vec::new(); - for device in devices { - if service.register_fuel_gauge(device).is_err() { - error!("Failed to register battery device with DeviceId {:?}", device.id()); - // Infallible as the Vec is as large as the list of devices passed in. - let _ = failed_devices.push(device.id()); - } - } - - if !failed_devices.is_empty() { - return Err(InitError::DeviceRegistrationFailed(failed_devices)); - } - - if comms::register_endpoint(service, &service.endpoint).await.is_err() { - error!("Failed to register battery service endpoint"); - return Err(InitError::CommsRegistrationFailed); - } - - loop { - service.process_next().await; - } -} diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index af0255c9f..46376776a 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -181,6 +181,7 @@ dependencies = [ "heapless 0.8.0", "log", "mctp-rs", + "odp-service-common", "power-policy-interface", "zerocopy", ] @@ -1148,6 +1149,14 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "odp-service-common" +version = "0.1.0" +dependencies = [ + "embedded-services", + "static_cell", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1187,6 +1196,7 @@ dependencies = [ "embedded-services", "env_logger", "log", + "odp-service-common", "pico-de-gallo-hal", "static_cell", "tokio", diff --git a/examples/pico-de-gallo/Cargo.toml b/examples/pico-de-gallo/Cargo.toml index 2528ce370..f86ebbcf4 100644 --- a/examples/pico-de-gallo/Cargo.toml +++ b/examples/pico-de-gallo/Cargo.toml @@ -25,6 +25,7 @@ embassy-futures = "0.1.2" embedded-batteries-async = "0.3" battery-service = { path = "../../battery-service", features = ["log"] } +odp-service-common = { path = "../../odp-service-common" } pico-de-gallo-hal = "0.1.0" diff --git a/examples/pico-de-gallo/src/bin/battery.rs b/examples/pico-de-gallo/src/bin/battery.rs index e79b2edda..b4d165c0e 100644 --- a/examples/pico-de-gallo/src/bin/battery.rs +++ b/examples/pico-de-gallo/src/bin/battery.rs @@ -19,6 +19,7 @@ use battery_service as bs; use bq40z50_rx::{BQ40Z50Error, Bq40z50R5}; use embedded_batteries_async::smart_battery::{BatteryModeFields, SmartBattery}; +use odp_service_common::runnable_service::{Service, ServiceRunner}; use static_cell::StaticCell; /// Platform specific battery errors. @@ -151,35 +152,7 @@ impl bs::controller::Controller for Battery { } } -async fn init_and_run_service( - battery_service: &'static battery_service::Service, - i2c: pico_de_gallo_hal::I2c, - delay: pico_de_gallo_hal::Delay, -) -> ! { - embedded_services::debug!("Initializing battery service"); - embedded_services::init().await; - - static BATTERY_DEVICE: StaticCell = StaticCell::new(); - static BATTERY_WRAPPER: StaticCell> = StaticCell::new(); - let device = BATTERY_DEVICE.init(bs::device::Device::new(bs::device::DeviceId(0))); - - let wrapper = BATTERY_WRAPPER.init(bs::wrapper::Wrapper::new( - device, - Battery { - driver: Bq40z50R5::new(i2c, delay), - }, - )); - - // Run battery service - let _ = embassy_futures::join::join( - tokio::spawn(battery_service::task::task(battery_service, [device])), - tokio::spawn(wrapper.process()), - ) - .await; - unreachable!() -} - -async fn init_state_machine(battery_service: &'static bs::Service) -> Result<(), bs::context::ContextError> { +async fn init_state_machine(battery_service: &bs::Service<'static, 1>) -> Result<(), bs::context::ContextError> { battery_service .execute_event(battery_service::context::BatteryEvent { event: battery_service::context::BatteryEventInner::DoInit, @@ -207,7 +180,7 @@ async fn init_state_machine(battery_service: &'static bs::Service) -> Result<(), Ok(()) } -async fn recover_state_machine(battery_service: &'static battery_service::Service) -> Result<(), ()> { +async fn recover_state_machine(battery_service: &battery_service::Service<'static, 1>) -> Result<(), ()> { loop { match battery_service .execute_event(battery_service::context::BatteryEvent { @@ -238,10 +211,10 @@ async fn recover_state_machine(battery_service: &'static battery_service::Servic } } -pub async fn run_app(battery_service: &'static battery_service::Service) { +pub async fn run_app(battery_service: battery_service::Service<'static, 1>) { // Initialize battery state machine. let mut retries = 5; - while let Err(e) = init_state_machine(battery_service).await { + while let Err(e) = init_state_machine(&battery_service).await { retries -= 1; if retries <= 0 { embedded_services::error!("Failed to initialize Battery: {:?}", e); @@ -281,7 +254,7 @@ pub async fn run_app(battery_service: &'static battery_service::Service) { failures = 0; count = 0; embedded_services::error!("FG: Too many errors, timing out and starting recovery..."); - if recover_state_machine(battery_service).await.is_err() { + if recover_state_machine(&battery_service).await.is_err() { embedded_services::error!("FG: Fatal error"); return; } @@ -296,13 +269,39 @@ async fn main() { env_logger::builder().filter_level(log::LevelFilter::Info).init(); embedded_services::info!("host: battery example started"); - static BATTERY_SERVICE: bs::Service = bs::Service::new(); + embedded_services::debug!("Initializing battery service"); + embedded_services::init().await; let p = pico_de_gallo_hal::Hal::new(); - let _ = embassy_futures::join::join( - tokio::spawn(run_app(&BATTERY_SERVICE)), - tokio::spawn(init_and_run_service(&BATTERY_SERVICE, p.i2c(), p.delay())), + static BATTERY_DEVICE: StaticCell = StaticCell::new(); + let device = BATTERY_DEVICE.init(bs::device::Device::new(bs::device::DeviceId(0))); + + static BATTERY_WRAPPER: StaticCell> = StaticCell::new(); + let wrapper = BATTERY_WRAPPER.init(bs::wrapper::Wrapper::new( + device, + Battery { + driver: Bq40z50R5::new(p.i2c(), p.delay()), + }, + )); + + static BATTERY_SERVICE: StaticCell> = StaticCell::new(); + let (battery_service, runner) = bs::Service::new( + BATTERY_SERVICE.init(Default::default()), + bs::InitParams { + config: Default::default(), + devices: [device], + }, + ) + .await + .expect("failed to initialize battery service"); + + // Run battery service + let _ = embassy_futures::join::join3( + tokio::spawn(runner.run()), + tokio::spawn(wrapper.process()), + tokio::spawn(run_app(battery_service)), ) .await; + unreachable!(); } diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index ea72d5a3d..aca2363b1 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -52,6 +52,7 @@ dependencies = [ "embedded-services", "heapless", "mctp-rs", + "odp-service-common", "power-policy-interface", "zerocopy", ] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 03b0167f4..a032962ee 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -166,6 +166,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "odp-service-common", "power-policy-interface", "zerocopy", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index cbac45e83..21aa1c4aa 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -26,6 +26,7 @@ defmt = "0.3" embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb-pd" } embedded-services = { path = "../../embedded-service", features = ["log"] } +odp-service-common = { path = "../../odp-service-common" } power-policy-service = { path = "../../power-policy-service", features = [ "log", ] } @@ -43,7 +44,6 @@ embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } thermal-service-messages = { path = "../../thermal-service-messages" } -odp-service-common = { path = "../../odp-service-common" } env_logger = "0.11.8" log = "0.4.14" diff --git a/examples/std/src/bin/battery.rs b/examples/std/src/bin/battery.rs index abd2d0df8..0e3ea6f29 100644 --- a/examples/std/src/bin/battery.rs +++ b/examples/std/src/bin/battery.rs @@ -7,45 +7,46 @@ use embassy_executor::{Executor, Spawner}; use embassy_time::{Duration, Timer}; use static_cell::StaticCell; -#[embassy_executor::task] -async fn battery_service_task( - service: &'static battery_service::Service, - devices: [&'static battery_service::device::Device; 1], -) { - battery_service::task::task(service, devices) - .await - .expect("Failed to init battery service"); -} +use odp_service_common::runnable_service::spawn_service; #[embassy_executor::task] -async fn battery_wrapper_process(battery_wrapper: &'static battery_service::mock::MockBattery<'static>) { - battery_wrapper.process().await -} - -#[embassy_executor::task] -async fn init_and_run_service(spawner: Spawner, battery_service: &'static battery_service::Service) { +async fn embassy_main(spawner: Spawner) { embedded_services::debug!("Initializing battery service"); embedded_services::init().await; static BATTERY_DEVICE: StaticCell = StaticCell::new(); - static BATTERY_WRAPPER: StaticCell = StaticCell::new(); - let device = BATTERY_DEVICE.init(bs::device::Device::new(bs::device::DeviceId::default())); + let device = BATTERY_DEVICE.init(bs::device::Device::new(Default::default())); + let battery_service = spawn_service!( + spawner, + battery_service::Service<'static, 1>, + battery_service::InitParams { + config: Default::default(), + devices: [device], + } + ) + .expect("Failed to initialize battery service"); + + static BATTERY_WRAPPER: StaticCell = StaticCell::new(); let wrapper = BATTERY_WRAPPER.init(bs::wrapper::Wrapper::new( device, battery_service::mock::MockBatteryDriver::new(), )); - // Run battery service - spawner.must_spawn(battery_service_task(battery_service, [device])); + #[embassy_executor::task] + async fn battery_wrapper_process(battery_wrapper: &'static battery_service::mock::MockBattery<'static>) { + battery_wrapper.process().await + } + spawner.must_spawn(battery_wrapper_process(wrapper)); + spawner.must_spawn(run_app(battery_service)); } #[embassy_executor::task] -pub async fn run_app(battery_service: &'static battery_service::Service) { +pub async fn run_app(battery_service: battery_service::Service<'static, 1>) { // Initialize battery state machine. let mut retries = 5; - while let Err(e) = bs::mock::init_state_machine(battery_service).await { + while let Err(e) = bs::mock::init_state_machine(&battery_service).await { retries -= 1; if retries <= 0 { embedded_services::error!("Failed to initialize Battery: {:?}", e); @@ -85,7 +86,7 @@ pub async fn run_app(battery_service: &'static battery_service::Service) { failures = 0; count = 0; embedded_services::error!("FG: Too many errors, timing out and starting recovery..."); - if bs::mock::recover_state_machine(battery_service).await.is_err() { + if bs::mock::recover_state_machine(&battery_service).await.is_err() { embedded_services::error!("FG: Fatal error"); return; } @@ -99,13 +100,10 @@ fn main() { env_logger::builder().filter_level(log::LevelFilter::Debug).init(); embedded_services::info!("battery example started"); - static BATTERY_SERVICE: bs::Service = bs::Service::new(); - static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); // Run battery service executor.run(|spawner| { - spawner.must_spawn(run_app(&BATTERY_SERVICE)); - spawner.must_spawn(init_and_run_service(spawner, &BATTERY_SERVICE)); + spawner.must_spawn(embassy_main(spawner)); }); } From d3347e339b3f074ae0da6ce583ff519023579fd3 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 23 Feb 2026 10:15:59 -0800 Subject: [PATCH 47/79] type-c-service: Remove external message types --- examples/rt685s-evk/src/bin/type_c.rs | 29 +- examples/std/src/bin/type_c/ucsi.rs | 8 +- type-c-service/src/service/controller.rs | 61 --- type-c-service/src/service/mod.rs | 46 +-- type-c-service/src/service/port.rs | 306 -------------- type-c-service/src/service/power.rs | 2 +- type-c-service/src/service/ucsi.rs | 27 +- type-c-service/src/type_c/controller.rs | 67 +-- type-c-service/src/type_c/external.rs | 495 ----------------------- type-c-service/src/type_c/mod.rs | 1 - 10 files changed, 35 insertions(+), 1007 deletions(-) delete mode 100644 type-c-service/src/service/controller.rs delete mode 100644 type-c-service/src/type_c/external.rs diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 2d7ac8724..91a3d9c29 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -26,7 +26,7 @@ use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; -use type_c_service::type_c::{Cached, ControllerId}; +use type_c_service::type_c::ControllerId; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -249,31 +249,4 @@ async fn main(spawner: Spawner) { )); spawner.must_spawn(pd_controller_task(wrapper)); - - // Sync our internal state with the hardware - controller_context - .sync_controller_state_external(CONTROLLER0_ID) - .await - .unwrap(); - - embassy_time::Timer::after_secs(10).await; - - let status = controller_context - .get_controller_status_external(CONTROLLER0_ID) - .await - .unwrap(); - - info!("Controller status: {:?}", status); - - let status = controller_context - .get_port_status_external(PORT0_ID, Cached(true)) - .await - .unwrap(); - info!("Port status: {:?}", status); - - let status = controller_context - .get_port_status_external(PORT1_ID, Cached(true)) - .await - .unwrap(); - info!("Port status: {:?}", status); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index ba6d9cd07..b39e84b38 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,3 +1,4 @@ +#![allow(unused_imports)] use crate::mock_controller::Wrapper; use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; @@ -25,7 +26,6 @@ use type_c_service::service::Service; use type_c_service::service::config::Config; use type_c_service::type_c::ControllerId; use type_c_service::type_c::controller::Context; -use type_c_service::type_c::external::UcsiResponseResult; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -45,8 +45,8 @@ type PowerPolicyServiceType = Mutex< >; #[embassy_executor::task] -async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { - const CAPABILITY: PowerCapability = PowerCapability { +async fn opm_task(_context: &'static Context, _state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { + /*const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, current_ma: 5000, }; @@ -172,7 +172,7 @@ async fn opm_task(context: &'static Context, state: [&'static mock_controller::C "Sending command complete ack successful, connector change: {:?}", response.cci.connector_change() ); - } + }*/ } #[embassy_executor::task(pool_size = 2)] diff --git a/type-c-service/src/service/controller.rs b/type-c-service/src/service/controller.rs deleted file mode 100644 index 74debf8c0..000000000 --- a/type-c-service/src/service/controller.rs +++ /dev/null @@ -1,61 +0,0 @@ -use embedded_services::{debug, error}; - -use super::*; -use crate::type_c::{ - ControllerId, - external::{self, ControllerCommandData}, -}; - -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ - /// Process external controller status command - pub(super) async fn process_external_controller_status( - &self, - controller: ControllerId, - ) -> external::Response<'static> { - let status = self.context.get_controller_status(self.controllers, controller).await; - if let Err(e) = status { - error!("Error getting controller status: {:#?}", e); - } - external::Response::Controller(status.map(external::ControllerResponseData::ControllerStatus)) - } - - /// Process external controller sync state command - pub(super) async fn process_external_controller_sync_state( - &self, - controller: ControllerId, - ) -> external::Response<'static> { - let status = self.context.sync_controller_state(self.controllers, controller).await; - if let Err(e) = status { - error!("Error getting controller sync state: {:#?}", e); - } - external::Response::Controller(status.map(|_| external::ControllerResponseData::Complete)) - } - - /// Process external controller reset command - pub(super) async fn process_external_controller_reset( - &self, - controller: ControllerId, - ) -> external::Response<'static> { - let status = self.context.reset_controller(self.controllers, controller).await; - if let Err(e) = status { - error!("Error resetting controller: {:#?}", e); - } - external::Response::Controller(status.map(|_| external::ControllerResponseData::Complete)) - } - - /// Process external controller commands - pub(super) async fn process_external_controller_command( - &self, - command: &external::ControllerCommand, - ) -> external::Response<'static> { - debug!("Processing external controller command: {:#?}", command); - match command.data { - ControllerCommandData::ControllerStatus => self.process_external_controller_status(command.id).await, - ControllerCommandData::SyncState => self.process_external_controller_sync_state(command.id).await, - ControllerCommandData::Reset => self.process_external_controller_reset(command.id).await, - } - } -} diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 68124cc56..b466e1cb7 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -1,9 +1,9 @@ -use embassy_futures::select::{Either3, select3}; +use embassy_futures::select::{Either, select}; use embassy_sync::{ mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber}, }; -use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, ipc::deferred, sync::Lockable, trace}; +use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, sync::Lockable, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; use power_policy_interface::psu; @@ -12,13 +12,11 @@ use crate::type_c::{ self, Cached, comms, controller::PortStatus, event::{PortNotificationSingle, PortStatusChanged}, - external, }; use crate::{PortEventStreamer, PortEventVariant}; pub mod config; -mod controller; pub mod pd; mod port; mod power; @@ -82,13 +80,11 @@ pub enum PowerPolicyEvent { } /// Type-C service events -pub enum Event<'a> { +pub enum Event { /// Port event PortStatusChanged(GlobalPortId, PortStatusChanged, PortStatus), /// A controller notified of an event that occurred. PortNotification(GlobalPortId, PortNotificationSingle), - /// External command - ExternalCommand(deferred::Request<'a, GlobalRawMutex, external::Command, external::Response<'static>>), /// Power policy event PowerPolicy(PowerPolicyEvent), } @@ -167,28 +163,11 @@ where Ok(()) } - /// Process external commands - async fn process_external_command(&self, command: &external::Command) -> external::Response<'static> { - match command { - external::Command::Controller(command) => self.process_external_controller_command(command).await, - external::Command::Port(command) => self.process_external_port_command(command, self.controllers).await, - external::Command::Ucsi(command) => { - external::Response::Ucsi(self.process_ucsi_command(self.controllers, command).await) - } - } - } - /// Wait for the next event - pub async fn wait_next(&self) -> Result, Error> { + pub async fn wait_next(&self) -> Result { loop { - match select3( - self.wait_port_flags(), - self.context.wait_external_command(), - self.wait_power_policy_event(), - ) - .await - { - Either3::First(mut stream) => { + match select(self.wait_port_flags(), self.wait_power_policy_event()).await { + Either::First(mut stream) => { if let Some((port_id, event)) = stream .next(|port_id| { self.context @@ -217,16 +196,13 @@ where self.state.lock().await.port_event_streaming_state = None; } } - Either3::Second(request) => { - return Ok(Event::ExternalCommand(request)); - } - Either3::Third(event) => return Ok(event), + Either::Second(event) => return Ok(event), } } } /// Process the given event - pub async fn process_event(&self, event: Event<'_>) -> Result<(), Error> { + pub async fn process_event(&self, event: Event) -> Result<(), Error> { match event { Event::PortStatusChanged(port, event_kind, status) => { trace!("Port{}: Processing port status changed", port.0); @@ -237,12 +213,6 @@ where info!("Port{}: Got port notification: {:?}", port.0, notification); Ok(()) } - Event::ExternalCommand(request) => { - trace!("Processing external command"); - let response = self.process_external_command(&request.command).await; - request.respond(response); - Ok(()) - } Event::PowerPolicy(event) => { trace!("Processing power policy event"); self.process_power_policy_event(&event).await diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs index ccf1bdc4f..b1aa047e7 100644 --- a/type-c-service/src/service/port.rs +++ b/type-c-service/src/service/port.rs @@ -1,15 +1,6 @@ -use embedded_services::{debug, error}; -use embedded_usb_pd::GlobalPortId; - use super::*; use crate::PortEventStreamer; -use crate::type_c::controller::SendVdm; -use crate::type_c::{ - controller::{DpConfig, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, - external, -}; - impl<'a, PSU: Lockable> Service<'a, PSU> where PSU::Inner: psu::Psu, @@ -26,301 +17,4 @@ where PortEventStreamer::new(self.context.get_unhandled_events().await.into_iter()) } } - - /// Process external port commands - pub(super) async fn process_external_port_command( - &self, - command: &external::PortCommand, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - debug!("Processing external port command: {:#?}", command); - match command.data { - external::PortCommandData::PortStatus(cached) => { - self.process_external_port_status(command.port, cached, controllers) - .await - } - external::PortCommandData::RetimerFwUpdateGetState => { - self.process_get_rt_fw_update_status(command.port, controllers).await - } - external::PortCommandData::RetimerFwUpdateSetState => { - self.process_set_rt_fw_update_state(command.port, controllers).await - } - external::PortCommandData::RetimerFwUpdateClearState => { - self.process_clear_rt_fw_update_state(command.port, controllers).await - } - external::PortCommandData::SetRetimerCompliance => { - self.process_set_rt_compliance(command.port, controllers).await - } - external::PortCommandData::ReconfigureRetimer => { - self.process_reconfigure_retimer(command.port, controllers).await - } - external::PortCommandData::SetMaxSinkVoltage { max_voltage_mv } => { - self.process_set_max_sink_voltage(command.port, max_voltage_mv, controllers) - .await - } - external::PortCommandData::ClearDeadBatteryFlag => { - self.process_clear_dead_battery_flag(command.port, controllers).await - } - external::PortCommandData::SendVdm(tx_vdm) => { - self.process_send_vdm(command.port, tx_vdm, controllers).await - } - external::PortCommandData::SetUsbControl(config) => { - self.process_set_usb_control(command.port, config, controllers).await - } - external::PortCommandData::GetDpStatus => self.process_get_dp_status(command.port, controllers).await, - external::PortCommandData::SetDpConfig(config) => { - self.process_set_dp_config(command.port, config, controllers).await - } - external::PortCommandData::ExecuteDrst => self.process_execute_drst(command.port, controllers).await, - external::PortCommandData::SetTbtConfig(config) => { - self.process_set_tbt_config(command.port, config, controllers).await - } - external::PortCommandData::SetPdStateMachineConfig(config) => { - self.process_set_pd_state_machine_config(command.port, config, controllers) - .await - } - external::PortCommandData::SetTypeCStateMachineConfig(state) => { - self.process_set_type_c_state_machine_config(command.port, state, controllers) - .await - } - } - } - - /// Process external port status command - pub(super) async fn process_external_port_status( - &self, - port_id: GlobalPortId, - cached: Cached, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.get_port_status(controllers, port_id, cached).await; - if let Err(e) = status { - error!("Error getting port status: {:#?}", e); - } - external::Response::Port(status.map(external::PortResponseData::PortStatus)) - } - - /// Process get retimer fw update status commands - pub(super) async fn process_get_rt_fw_update_status( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.get_rt_fw_update_status(controllers, port_id).await; - if let Err(e) = status { - error!("Error getting retimer fw update status: {:#?}", e); - } - - external::Response::Port(status.map(external::PortResponseData::RetimerFwUpdateGetState)) - } - - /// Process set retimer fw update state commands - pub(super) async fn process_set_rt_fw_update_state( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_rt_fw_update_state(controllers, port_id).await; - if let Err(e) = status { - error!("Error setting retimer fw update state: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process clear retimer fw update state commands - pub(super) async fn process_clear_rt_fw_update_state( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.clear_rt_fw_update_state(controllers, port_id).await; - if let Err(e) = status { - error!("Error clear retimer fw update state: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set retimer compliance - pub(super) async fn process_set_rt_compliance( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_rt_compliance(controllers, port_id).await; - if let Err(e) = status { - error!("Error set retimer compliance: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - async fn process_reconfigure_retimer( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.reconfigure_retimer(controllers, port_id).await; - if let Err(e) = status { - error!("Error reconfiguring retimer: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - async fn process_set_max_sink_voltage( - &self, - port_id: GlobalPortId, - max_voltage_mv: Option, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self - .context - .set_max_sink_voltage(controllers, port_id, max_voltage_mv) - .await; - if let Err(e) = status { - error!("Error setting max voltage: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - async fn process_clear_dead_battery_flag( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.clear_dead_battery_flag(controllers, port_id).await; - if let Err(e) = status { - error!("Error clearing dead battery flag: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process send vdm commands - /// Process send vdm commands - async fn process_send_vdm( - &self, - port_id: GlobalPortId, - tx_vdm: SendVdm, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.send_vdm(controllers, port_id, tx_vdm).await; - if let Err(e) = status { - error!("Error sending VDM data: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set USB control commands - async fn process_set_usb_control( - &self, - port_id: GlobalPortId, - config: UsbControlConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_usb_control(controllers, port_id, config).await; - if let Err(e) = status { - error!("Error setting USB control: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process get DisplayPort status commands - async fn process_get_dp_status( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.get_dp_status(controllers, port_id).await; - if let Err(e) = status { - error!("Error getting DP status: {:#?}", e); - } - - external::Response::Port(status.map(external::PortResponseData::GetDpStatus)) - } - - /// Process set DisplayPort config commands - async fn process_set_dp_config( - &self, - port_id: GlobalPortId, - config: DpConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_dp_config(controllers, port_id, config).await; - if let Err(e) = status { - error!("Error setting DP config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process execute DisplayPort reset commands - async fn process_execute_drst( - &self, - port_id: GlobalPortId, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.execute_drst(controllers, port_id).await; - if let Err(e) = status { - error!("Error executing DP reset: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set Thunderbolt configuration command - async fn process_set_tbt_config( - &self, - port_id: GlobalPortId, - config: TbtConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self.context.set_tbt_config(controllers, port_id, config).await; - if let Err(e) = status { - error!("Error setting TBT config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set PD state-machine configuration command - async fn process_set_pd_state_machine_config( - &self, - port_id: GlobalPortId, - config: PdStateMachineConfig, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self - .context - .set_pd_state_machine_config(controllers, port_id, config) - .await; - if let Err(e) = status { - error!("Error setting PD state-machine config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } - - /// Process set Type-C state-machine configuration command - async fn process_set_type_c_state_machine_config( - &self, - port_id: GlobalPortId, - state: TypeCStateMachineState, - controllers: &intrusive_list::IntrusiveList, - ) -> external::Response<'static> { - let status = self - .context - .set_type_c_state_machine_config(controllers, port_id, state) - .await; - if let Err(e) = status { - error!("Error setting Type-C state-machine config: {:#?}", e); - } - - external::Response::Port(status.map(|_| external::PortResponseData::Complete)) - } } diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index ba16485e9..bc2839979 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -8,7 +8,7 @@ where PSU::Inner: psu::Psu, { /// Wait for a power policy event - pub(super) async fn wait_power_policy_event(&self) -> Event<'_> { + pub(super) async fn wait_power_policy_event(&self) -> Event { loop { match self.power_policy_event_subscriber.lock().await.next_message().await { WaitResult::Lagged(lagged) => { diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index c07c90ce3..800839c7f 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -10,6 +10,18 @@ use embedded_usb_pd::{PdError, PowerRole}; use super::*; +/// UCSI command response +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UcsiResponse { + /// Notify the OPM, the function call + pub notify_opm: bool, + /// Response CCI + pub cci: GlobalCci, + /// UCSI response data + pub data: Result, PdError>, +} + /// UCSI state #[derive(Default)] pub(super) struct State { @@ -47,10 +59,11 @@ where } /// PPM get capabilities implementation - fn process_get_capabilities(&self, controllers: &intrusive_list::IntrusiveList) -> ppm::ResponseData { + fn process_get_capabilities(&self, _controllers: &intrusive_list::IntrusiveList) -> ppm::ResponseData { debug!("Get PPM capabilities: {:?}", self.config.ucsi_capabilities); let mut capabilities = self.config.ucsi_capabilities; - capabilities.num_connectors = external::get_num_ports(controllers) as u8; + // TODO: implement this when the refactoring stabilizes + capabilities.num_connectors = 0; ppm::ResponseData::GetCapability(capabilities) } @@ -173,14 +186,14 @@ where } /// Process an external UCSI command - pub(super) async fn process_ucsi_command( + pub async fn process_ucsi_command( &self, controllers: &intrusive_list::IntrusiveList, command: &GlobalCommand, - ) -> external::UcsiResponse { + ) -> UcsiResponse { let state = &mut self.state.lock().await; let mut next_input = Some(PpmInput::Command(command)); - let mut response: external::UcsiResponse = external::UcsiResponse { + let mut response = UcsiResponse { notify_opm: false, cci: Cci::default(), data: Ok(None), @@ -194,7 +207,7 @@ where state.ucsi.ppm_state_machine.consume(next_input) } else { error!("Unexpected end of state machine processing"); - return external::UcsiResponse { + return UcsiResponse { notify_opm: true, cci: Cci::new_error(), data: Err(PdError::InvalidMode), @@ -205,7 +218,7 @@ where Ok(output) => output, Err(e @ InvalidTransition { .. }) => { error!("PPM state machine transition failed: {:#?}", e); - return external::UcsiResponse { + return UcsiResponse { notify_opm: true, cci: Cci::new_error(), data: Err(PdError::Failed), diff --git a/type-c-service/src/type_c/controller.rs b/type-c-service/src/type_c/controller.rs index 6f20fd8fc..b076f6371 100644 --- a/type-c-service/src/type_c/controller.rs +++ b/type-c-service/src/type_c/controller.rs @@ -11,7 +11,7 @@ use embedded_usb_pd::{ type_c::ConnectionState, }; -use super::{ATTN_VDM_LEN, ControllerId, OTHER_VDM_LEN, external}; +use super::{ATTN_VDM_LEN, ControllerId, OTHER_VDM_LEN}; use crate::type_c::Cached; use crate::type_c::comms::CommsMessage; use crate::type_c::event::{PortEvent, PortPending}; @@ -672,8 +672,6 @@ pub trait Controller { /// Internal context for managing PD controllers pub struct Context { port_events: Signal, - /// Channel for receiving commands to the type-C service - external_command: deferred::Channel>, /// Event broadcaster broadcaster: broadcaster::Immediate, } @@ -689,7 +687,6 @@ impl Context { pub const fn new() -> Self { Self { port_events: Signal::new(), - external_command: deferred::Channel::new(), broadcaster: broadcaster::Immediate::new(), } } @@ -1095,13 +1092,6 @@ impl Context { } } - /// Wait for an external command - pub async fn wait_external_command( - &self, - ) -> deferred::Request<'_, GlobalRawMutex, external::Command, external::Response<'static>> { - self.external_command.receive().await - } - /// Get the number of ports on the system pub fn get_num_ports(&self, controllers: &intrusive_list::IntrusiveList) -> usize { get_num_ports(controllers) @@ -1291,50 +1281,6 @@ impl Context { } } - /// Execute an external port command - pub(super) async fn execute_external_port_command( - &self, - command: external::Command, - ) -> Result { - match self.external_command.execute(command).await { - external::Response::Port(response) => response, - r => { - error!("Invalid response: expected external port, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Execute an external UCSI command - pub(super) async fn execute_external_ucsi_command(&self, command: ucsi::GlobalCommand) -> external::UcsiResponse { - match self.external_command.execute(external::Command::Ucsi(command)).await { - external::Response::Ucsi(response) => response, - r => { - error!("Invalid response: expected external UCSI, got {:?}", r); - external::UcsiResponse { - // Always notify OPM of an error - notify_opm: true, - cci: ucsi::cci::GlobalCci::new_error(), - data: Err(PdError::InvalidResponse), - } - } - } - } - - /// Execute an external controller command - pub(super) async fn execute_external_controller_command( - &self, - command: external::Command, - ) -> Result, PdError> { - match self.external_command.execute(command).await { - external::Response::Controller(response) => response, - r => { - error!("Invalid response: expected external controller, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - /// Register a message receiver for type-C messages pub async fn register_message_receiver( &self, @@ -1361,17 +1307,6 @@ pub fn register_controller( controllers.push(controller.get_pd_controller_device()) } -pub(super) fn lookup_controller( - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, -) -> Result<&'static Device<'static>, PdError> { - controllers - .into_iter() - .filter_map(|node| node.data::()) - .find(|controller| controller.id == controller_id) - .ok_or(PdError::InvalidController) -} - /// Get total number of ports on the system pub(super) fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { controllers diff --git a/type-c-service/src/type_c/external.rs b/type-c-service/src/type_c/external.rs deleted file mode 100644 index ef253db2a..000000000 --- a/type-c-service/src/type_c/external.rs +++ /dev/null @@ -1,495 +0,0 @@ -//! Message definitions for external type-C commands -use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError, ucsi}; - -use embedded_services::intrusive_list; - -use super::{ - ControllerId, - controller::{ControllerStatus, DpConfig, DpStatus, PortStatus, RetimerFwUpdateState, SendVdm, lookup_controller}, -}; - -use crate::type_c::{ - Cached, - controller::{Context, PdStateMachineConfig, TbtConfig, TypeCStateMachineState, UsbControlConfig}, -}; - -/// Data for controller-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ControllerCommandData { - /// Get controller status - ControllerStatus, - /// Sync controller state - SyncState, - /// Controller reset - Reset, -} - -/// Controller-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerCommand { - /// Controller ID - pub id: ControllerId, - /// Command data - pub data: ControllerCommandData, -} - -/// Response data for controller-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ControllerResponseData<'a> { - /// Command complete - Complete, - /// Get controller status - ControllerStatus(ControllerStatus<'a>), -} - -/// Controller-specific command response -pub type ControllerResponse<'a> = Result, PdError>; - -/// Data for port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortCommandData { - /// Get port status. The `bool` argument indicates whether to use cached data or force a fetch of register values. - PortStatus(Cached), - /// Get retimer fw update status - RetimerFwUpdateGetState, - /// Set retimer fw update status - RetimerFwUpdateSetState, - /// Clear retimer fw update status - RetimerFwUpdateClearState, - /// Set retimer compliance - SetRetimerCompliance, - /// Reconfigure retimer - ReconfigureRetimer, - /// Set max sink voltage to a specific value. - SetMaxSinkVoltage { - /// The maximum voltage to set, in millivolts. - /// If [`None`], the port will be set to its default maximum voltage. - max_voltage_mv: Option, - }, - /// Clear the dead battery flag for the given port. - ClearDeadBatteryFlag, - /// Set USB control - SetUsbControl(UsbControlConfig), - /// Send VDM - SendVdm(SendVdm), - /// Get DisplayPort status - GetDpStatus, - /// Set DisplayPort configuration - SetDpConfig(DpConfig), - /// Execute DisplayPort reset - ExecuteDrst, - /// Set Thunderbolt configuration - SetTbtConfig(TbtConfig), - /// Set PD state-machine configuration - SetPdStateMachineConfig(PdStateMachineConfig), - /// Set Type-C state-machine configuration - SetTypeCStateMachineConfig(TypeCStateMachineState), -} - -/// Port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortCommand { - /// Port ID - pub port: GlobalPortId, - /// Command data - pub data: PortCommandData, -} - -/// Response data for port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortResponseData { - /// Command completed with no error - Complete, - /// Get port status - PortStatus(PortStatus), - /// Get retimer fw update status - RetimerFwUpdateGetState(RetimerFwUpdateState), - /// Get DisplayPort status - GetDpStatus(DpStatus), -} - -/// Port-specific command response -pub type PortResponse = Result; - -/// External commands for type-C service -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Command { - /// Port command - Port(PortCommand), - /// Controller command - Controller(ControllerCommand), - /// UCSI command - Ucsi(ucsi::GlobalCommand), -} - -/// UCSI command response -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct UcsiResponse { - /// Notify the OPM, the function call - pub notify_opm: bool, - /// Response CCI - pub cci: ucsi::cci::GlobalCci, - /// UCSI response data - pub data: Result, PdError>, -} - -/// Alias to help simplify conversion into a result -pub type UcsiResponseResult = Result; - -impl From for UcsiResponseResult { - fn from(value: UcsiResponse) -> Self { - match value.data { - Ok(data) => Ok(ucsi::GlobalResponse { cci: value.cci, data }), - Err(err) => Err(err), - } - } -} - -/// External command response for type-C service -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Response<'a> { - /// Port command response - Port(PortResponse), - /// Controller command response - Controller(ControllerResponse<'a>), - /// UCSI command response - Ucsi(UcsiResponse), -} - -impl Context { - /// Get the status of the given port. - /// - /// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. - pub async fn get_port_status_external(&self, port: GlobalPortId, cached: Cached) -> Result { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::PortStatus(cached), - })) - .await? - { - PortResponseData::PortStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get the status of the given port by its controller and local port ID. - /// - /// Use the `cached` argument to specify whether to use cached data or force a fetch of register values. - pub async fn get_controller_port_status_external( - &self, - controllers: &intrusive_list::IntrusiveList, - controller: ControllerId, - port: LocalPortId, - cached: Cached, - ) -> Result { - let global_port = controller_port_to_global_id(controllers, controller, port)?; - self.get_port_status_external(global_port, cached).await - } - - /// Reset the given controller. - pub async fn reset_controller_external(&self, controller_id: ControllerId) -> Result<(), PdError> { - match self - .execute_external_controller_command(Command::Controller(ControllerCommand { - id: controller_id, - data: ControllerCommandData::Reset, - })) - .await? - { - ControllerResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get the status of the given controller - #[allow(unreachable_patterns)] - pub async fn get_controller_status_external(&self, id: ControllerId) -> Result, PdError> { - match self - .execute_external_controller_command(Command::Controller(ControllerCommand { - id, - data: ControllerCommandData::ControllerStatus, - })) - .await? - { - ControllerResponseData::ControllerStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get the retimer fw update status of the given port - pub async fn port_get_rt_fw_update_status_external( - &self, - port: GlobalPortId, - ) -> Result { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateGetState, - })) - .await? - { - PortResponseData::RetimerFwUpdateGetState(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer fw update state of the given port - pub async fn port_set_rt_fw_update_state_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateSetState, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the retimer fw update state of the given port - pub async fn port_clear_rt_fw_update_state_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::RetimerFwUpdateClearState, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer comliance state of the given port - pub async fn port_set_rt_compliance_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetRetimerCompliance, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Trigger a sync of the controller state - pub async fn sync_controller_state_external(&self, id: ControllerId) -> Result<(), PdError> { - match self - .execute_external_controller_command(Command::Controller(ControllerCommand { - id, - data: ControllerCommandData::SyncState, - })) - .await? - { - ControllerResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the maximum voltage for the given port to a specific value. - /// - /// See [`PortCommandData::SetMaxSinkVoltage::max_voltage_mv`] for details on the `max_voltage_mv` parameter. - pub async fn set_max_sink_voltage_external( - &self, - port: GlobalPortId, - max_voltage_mv: Option, - ) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetMaxSinkVoltage { max_voltage_mv }, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the dead battery flag for the given port. - pub async fn clear_dead_battery_flag_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ClearDeadBatteryFlag, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Reconfigure the retimer for the given port. - pub async fn reconfigure_retimer_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ReconfigureRetimer, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute a UCSI command - pub async fn execute_ucsi_command_external(&self, command: ucsi::GlobalCommand) -> UcsiResponse { - self.execute_external_ucsi_command(command).await - } - - /// Send vdm to the given port - pub async fn send_vdm_external(&self, port: GlobalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SendVdm(tx_vdm), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set USB control configuration - pub async fn set_usb_control_external(&self, port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetUsbControl(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get DisplayPort status for the given port - pub async fn get_dp_status_external(&self, port: GlobalPortId) -> Result { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::GetDpStatus, - })) - .await? - { - PortResponseData::GetDpStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set DisplayPort configuration for the given port - pub async fn set_dp_config_external(&self, port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetDpConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute DisplayPort reset for the given port - pub async fn execute_drst_external(&self, port: GlobalPortId) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::ExecuteDrst, - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Thunderbolt configuration for the given port - pub async fn set_tbt_config_external(&self, port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetTbtConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set PD state-machine configuration for the given port - pub async fn set_pd_state_machine_config_external( - &self, - port: GlobalPortId, - config: PdStateMachineConfig, - ) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetPdStateMachineConfig(config), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Type-C state-machine configuration for the given port - pub async fn set_type_c_state_machine_config_external( - &self, - port: GlobalPortId, - state: TypeCStateMachineState, - ) -> Result<(), PdError> { - match self - .execute_external_port_command(Command::Port(PortCommand { - port, - data: PortCommandData::SetTypeCStateMachineConfig(state), - })) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } -} - -/// Get the number of ports on the given controller -pub fn get_controller_num_ports( - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, -) -> Result { - Ok(lookup_controller(controllers, controller_id)?.num_ports()) -} - -/// Convert a (controller ID, local port ID) to a global port ID -pub fn controller_port_to_global_id( - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - port_id: LocalPortId, -) -> Result { - lookup_controller(controllers, controller_id)?.lookup_global_port(port_id) -} - -/// Get number of ports on the system -pub fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { - super::controller::get_num_ports(controllers) -} diff --git a/type-c-service/src/type_c/mod.rs b/type-c-service/src/type_c/mod.rs index a5100bf17..a4fa3f00c 100644 --- a/type-c-service/src/type_c/mod.rs +++ b/type-c-service/src/type_c/mod.rs @@ -5,7 +5,6 @@ use embedded_usb_pd::type_c; pub mod comms; pub mod controller; pub mod event; -pub mod external; /// Controller ID #[derive(Copy, Clone, Debug, PartialEq, Eq)] From f98844efb5475bfc7227664cd21f0451bd8f2b2f Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Mon, 23 Feb 2026 11:20:22 -0800 Subject: [PATCH 48/79] Break-out `type-c-interface` crate Also remove context function arguments. --- Cargo.lock | 17 +- Cargo.toml | 2 + examples/rt685s-evk/Cargo.lock | 15 +- examples/rt685s-evk/src/bin/type_c.rs | 10 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 13 +- examples/std/Cargo.lock | 15 +- examples/std/src/bin/type_c/basic.rs | 31 +- examples/std/src/bin/type_c/service.rs | 10 +- examples/std/src/bin/type_c/ucsi.rs | 10 +- examples/std/src/bin/type_c/unconstrained.rs | 10 +- type-c-interface/Cargo.toml | 38 + type-c-interface/src/lib.rs | 4 + .../src/port}/event.rs | 0 type-c-interface/src/port/mod.rs | 668 +++++++++ type-c-interface/src/service/context.rs | 534 +++++++ .../src/service/event.rs | 6 +- type-c-interface/src/service/mod.rs | 2 + type-c-service/Cargo.toml | 2 +- type-c-service/src/driver/tps6699x.rs | 37 +- type-c-service/src/lib.rs | 6 +- type-c-service/src/service/mod.rs | 37 +- type-c-service/src/service/pd.rs | 10 +- type-c-service/src/service/power.rs | 12 +- type-c-service/src/service/ucsi.rs | 27 +- type-c-service/src/service/vdm.rs | 20 +- type-c-service/src/task.rs | 6 +- type-c-service/src/type_c/controller.rs | 1315 ----------------- type-c-service/src/{type_c/mod.rs => util.rs} | 21 +- type-c-service/src/wrapper/backing.rs | 19 +- type-c-service/src/wrapper/cfu.rs | 2 +- type-c-service/src/wrapper/dp.rs | 2 +- type-c-service/src/wrapper/message.rs | 14 +- type-c-service/src/wrapper/mod.rs | 36 +- type-c-service/src/wrapper/pd.rs | 133 +- type-c-service/src/wrapper/vdm.rs | 6 +- 35 files changed, 1486 insertions(+), 1604 deletions(-) create mode 100644 type-c-interface/Cargo.toml create mode 100644 type-c-interface/src/lib.rs rename {type-c-service/src/type_c => type-c-interface/src/port}/event.rs (100%) create mode 100644 type-c-interface/src/port/mod.rs create mode 100644 type-c-interface/src/service/context.rs rename type-c-service/src/type_c/comms.rs => type-c-interface/src/service/event.rs (88%) create mode 100644 type-c-interface/src/service/mod.rs delete mode 100644 type-c-service/src/type_c/controller.rs rename type-c-service/src/{type_c/mod.rs => util.rs} (74%) diff --git a/Cargo.lock b/Cargo.lock index ce5ddac49..cb379bdfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2514,13 +2514,27 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "type-c-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "bitvec", + "defmt 0.3.100", + "embassy-sync", + "embassy-time", + "embedded-services", + "embedded-usb-pd", + "log", + "power-policy-interface", +] + [[package]] name = "type-c-service" version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", - "bitvec", "cfu-service", "critical-section", "defmt 0.3.100", @@ -2540,6 +2554,7 @@ dependencies = [ "static_cell", "tokio", "tps6699x", + "type-c-interface", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ffe8c00b1..ad4fe567f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "keyboard-service", "power-policy-interface", "odp-service-common", + "type-c-interface", ] exclude = ["examples/*"] @@ -105,6 +106,7 @@ static_cell = "2.1.0" toml = { version = "0.8", default-features = false } thermal-service-messages = { path = "./thermal-service-messages" } time-alarm-service-messages = { path = "./time-alarm-service-messages" } +type-c-interface = { path = "./type-c-interface" } syn = "2.0" tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" } tokio = { version = "1.42.0" } diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index e8ffda556..29651ba48 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1672,13 +1672,25 @@ dependencies = [ "itertools 0.14.0", ] +[[package]] +name = "type-c-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "bitvec", + "embassy-sync", + "embassy-time", + "embedded-services", + "embedded-usb-pd", + "power-policy-interface", +] + [[package]] name = "type-c-service" version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", - "bitvec", "cfu-service", "defmt 0.3.100", "embassy-futures", @@ -1693,6 +1705,7 @@ dependencies = [ "heapless", "power-policy-interface", "tps6699x", + "type-c-interface", ] [[package]] diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 91a3d9c29..83070d97b 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -26,9 +26,9 @@ use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; -use type_c_service::type_c::ControllerId; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::controller::ControllerId; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -143,11 +143,8 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( @@ -227,7 +224,6 @@ async fn main(spawner: Spawner) { let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 20f5b65d7..91294af5a 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -18,8 +18,7 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; -use embedded_services::event::NoopSender; -use embedded_services::{GlobalRawMutex, IntrusiveList}; +use embedded_services::GlobalRawMutex; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; @@ -29,9 +28,9 @@ use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; -use type_c_service::type_c::ControllerId; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::controller::ControllerId; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -227,11 +226,8 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( @@ -315,7 +311,6 @@ async fn main(spawner: Spawner) { let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index a032962ee..54cf2ca91 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1868,13 +1868,25 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "type-c-interface" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "bitvec", + "embassy-sync", + "embassy-time", + "embedded-services", + "embedded-usb-pd", + "power-policy-interface", +] + [[package]] name = "type-c-service" version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", - "bitvec", "cfu-service", "embassy-futures", "embassy-sync", @@ -1889,6 +1901,7 @@ dependencies = [ "log", "power-policy-interface", "tps6699x", + "type-c-interface", ] [[package]] diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 1bdbb604d..44321670d 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,11 +1,11 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; -use embedded_services::IntrusiveList; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; +use type_c_service::service::context::{Context, DeviceContainer}; use type_c_service::type_c::{Cached, ControllerId, controller}; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -22,7 +22,7 @@ mod test_controller { pub controller: controller::Device<'a>, } - impl controller::DeviceContainer for Controller<'_> { + impl DeviceContainer for Controller<'_> { fn get_pd_controller_device(&self) -> &controller::Device<'_> { &self.controller } @@ -115,13 +115,13 @@ mod test_controller { } #[embassy_executor::task] -async fn controller_task(controller_list: &'static IntrusiveList) { +async fn controller_task(controller_context: &'static Context) { static CONTROLLER: OnceLock = OnceLock::new(); static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, &PORTS)); - controller::register_controller(controller_list, controller).unwrap(); + controller_context.register_controller(controller).unwrap(); loop { controller.process().await; @@ -132,32 +132,27 @@ async fn controller_task(controller_list: &'static IntrusiveList) { async fn task(spawner: Spawner) { embedded_services::init().await; - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(Context::new()); info!("Starting controller task"); - spawner.must_spawn(controller_task(controller_list)); + spawner.must_spawn(controller_task(controller_context)); // Wait for controller to be registered Timer::after_secs(1).await; - let context = controller::Context::new(); + controller_context.reset_controller(CONTROLLER0_ID).await.unwrap(); - context.reset_controller(controller_list, CONTROLLER0_ID).await.unwrap(); - - let status = context - .get_controller_status(controller_list, CONTROLLER0_ID) - .await - .unwrap(); + let status = controller_context.get_controller_status(CONTROLLER0_ID).await.unwrap(); info!("Controller 0 status: {status:#?}"); - let status = context - .get_port_status(controller_list, PORT0_ID, Cached(true)) + let status = controller_context + .get_port_status(PORT0_ID, Cached(true)) .await .unwrap(); info!("Port 0 status: {status:#?}"); - let status = context - .get_port_status(controller_list, PORT1_ID, Cached(true)) + let status = controller_context + .get_port_status(PORT1_ID, Cached(true)) .await .unwrap(); info!("Port 1 status: {status:#?}"); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index c11d91a77..94202ad85 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -19,7 +19,7 @@ use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::type_c::controller::Context; +use type_c_service::service::context::Context; use type_c_service::type_c::{ControllerId, power_capability_from_current}; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::message::*; @@ -74,8 +74,8 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(type_c_service::service::context::Context::new()); let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); @@ -102,14 +102,10 @@ async fn task(spawner: Spawner) { // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Config::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index b39e84b38..8187012a9 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -24,8 +24,8 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; use type_c_service::service::config::Config; +use type_c_service::service::context::Context; use type_c_service::type_c::ControllerId; -use type_c_service::type_c::controller::Context; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -208,11 +208,8 @@ async fn task(spawner: Spawner) { embedded_services::init().await; - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(Context::new()); static STORAGE0: StaticCell> = StaticCell::new(); let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); @@ -359,7 +356,6 @@ async fn task(spawner: Spawner) { ..Default::default() }, controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index a11b38ee2..6344b1bd7 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -63,13 +63,8 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::type_c::controller::Context::new()); - - static CONTEXT: StaticCell = StaticCell::new(); - let context = CONTEXT.init(type_c_service::type_c::controller::Context::new()); - static CONTROLLER_LIST: StaticCell = StaticCell::new(); - let controller_list = CONTROLLER_LIST.init(IntrusiveList::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); @@ -210,7 +205,6 @@ async fn task(spawner: Spawner) { let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - controller_list, power_policy_publisher, power_policy_subscriber, )); diff --git a/type-c-interface/Cargo.toml b/type-c-interface/Cargo.toml new file mode 100644 index 000000000..b425a1fed --- /dev/null +++ b/type-c-interface/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "type-c-interface" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +bitfield.workspace = true +bitvec.workspace = true +embassy-sync.workspace = true +embassy-time.workspace = true +log = { workspace = true, optional = true } +defmt = { workspace = true, optional = true } +embedded-services.workspace = true +embedded-usb-pd.workspace = true +power-policy-interface.workspace = true + +[lints] +workspace = true + +[features] +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", +] diff --git a/type-c-interface/src/lib.rs b/type-c-interface/src/lib.rs new file mode 100644 index 000000000..8428ffcc4 --- /dev/null +++ b/type-c-interface/src/lib.rs @@ -0,0 +1,4 @@ +//! Interface for type-C service. +#![no_std] +pub mod port; +pub mod service; \ No newline at end of file diff --git a/type-c-service/src/type_c/event.rs b/type-c-interface/src/port/event.rs similarity index 100% rename from type-c-service/src/type_c/event.rs rename to type-c-interface/src/port/event.rs diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs new file mode 100644 index 000000000..4f2eeb336 --- /dev/null +++ b/type-c-interface/src/port/mod.rs @@ -0,0 +1,668 @@ +//! PD controller related code +use core::future::Future; + +use embedded_usb_pd::ucsi::{self, lpm}; +use embedded_usb_pd::{ + DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, + ado::Ado, + pdinfo::{AltMode, PowerPathStatus}, + type_c::ConnectionState, +}; + +use embedded_services::ipc::deferred; +use embedded_services::{GlobalRawMutex, intrusive_list}; + +pub mod event; + +use event::{PortEvent, PortPending}; + +/// Length of the Other VDM data +pub const OTHER_VDM_LEN: usize = 29; +/// Length of the Attention VDM data +pub const ATTN_VDM_LEN: usize = 9; +/// maximum number of data objects in a VDM +pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each + +/// Newtype to help clarify arguments to port status commands +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Cached(pub bool); + +/// Controller ID +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControllerId(pub u8); + +/// Port status +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PortStatus { + /// Current available source contract + pub available_source_contract: Option, + /// Current available sink contract + pub available_sink_contract: Option, + /// Current connection state + pub connection_state: Option, + /// Port partner supports dual-power roles + pub dual_power: bool, + /// plug orientation + pub plug_orientation: PlugOrientation, + /// power role + pub power_role: PowerRole, + /// data role + pub data_role: DataRole, + /// Active alt-modes + pub alt_mode: AltMode, + /// Power path status + pub power_path: PowerPathStatus, + /// EPR mode active + pub epr: bool, + /// Port partner is unconstrained + pub unconstrained_power: bool, +} + +impl PortStatus { + /// Create a new blank port status + /// Needed because default() is not const + pub const fn new() -> Self { + Self { + available_source_contract: None, + available_sink_contract: None, + connection_state: None, + dual_power: false, + plug_orientation: PlugOrientation::CC1, + power_role: PowerRole::Sink, + data_role: DataRole::Dfp, + alt_mode: AltMode::none(), + power_path: PowerPathStatus::none(), + epr: false, + unconstrained_power: false, + } + } + + /// Check if the port is connected + pub fn is_connected(&self) -> bool { + matches!( + self.connection_state, + Some(ConnectionState::Attached) + | Some(ConnectionState::DebugAccessory) + | Some(ConnectionState::AudioAccessory) + ) + } + + /// Check if a debug accessory is connected + pub fn is_debug_accessory(&self) -> bool { + matches!(self.connection_state, Some(ConnectionState::DebugAccessory)) + } +} + +impl Default for PortStatus { + fn default() -> Self { + Self::new() + } +} + +/// Other Vdm data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OtherVdm { + /// Other VDM data + pub data: [u8; OTHER_VDM_LEN], +} + +impl Default for OtherVdm { + fn default() -> Self { + Self { + data: [0; OTHER_VDM_LEN], + } + } +} + +impl From for [u8; OTHER_VDM_LEN] { + fn from(vdm: OtherVdm) -> Self { + vdm.data + } +} + +impl From<[u8; OTHER_VDM_LEN]> for OtherVdm { + fn from(data: [u8; OTHER_VDM_LEN]) -> Self { + Self { data } + } +} + +/// Attention Vdm data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AttnVdm { + /// Attention VDM data + pub data: [u8; ATTN_VDM_LEN], +} + +/// DisplayPort pin configuration +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpPinConfig { + /// 4L DP connection using USBC-USBC cable (Pin Assignment C) + pub pin_c: bool, + /// 2L USB + 2L DP connection using USBC-USBC cable (Pin Assignment D) + pub pin_d: bool, + /// 4L DP connection using USBC-DP cable (Pin Assignment E) + pub pin_e: bool, +} + +/// DisplayPort status data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpStatus { + /// DP alt-mode entered + pub alt_mode_entered: bool, + /// Get DP DFP pin config + pub dfp_d_pin_cfg: DpPinConfig, +} + +/// DisplayPort configuration data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpConfig { + /// DP alt-mode enabled + pub enable: bool, + /// Set DP DFP pin config + pub dfp_d_pin_cfg: DpPinConfig, +} + +impl Default for AttnVdm { + fn default() -> Self { + Self { + data: [0; ATTN_VDM_LEN], + } + } +} + +impl From for [u8; ATTN_VDM_LEN] { + fn from(vdm: AttnVdm) -> Self { + vdm.data + } +} + +impl From<[u8; ATTN_VDM_LEN]> for AttnVdm { + fn from(data: [u8; ATTN_VDM_LEN]) -> Self { + Self { data } + } +} + +/// Send VDM data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SendVdm { + /// initiating a VDM sequence + pub initiator: bool, + /// VDO count + pub vdo_count: u8, + /// VDO data + pub vdo_data: [u32; MAX_NUM_DATA_OBJECTS], +} + +impl SendVdm { + /// Create a new blank port status + pub const fn new() -> Self { + Self { + initiator: false, + vdo_count: 0, + vdo_data: [0; MAX_NUM_DATA_OBJECTS], + } + } +} + +impl Default for SendVdm { + fn default() -> Self { + Self::new() + } +} + +/// USB control configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct UsbControlConfig { + /// Enable USB2 data path + pub usb2_enabled: bool, + /// Enable USB3 data path + pub usb3_enabled: bool, + /// Enable USB4 data path + pub usb4_enabled: bool, +} + +impl Default for UsbControlConfig { + fn default() -> Self { + Self { + usb2_enabled: true, + usb3_enabled: true, + usb4_enabled: true, + } + } +} + +/// Thunderbolt control configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Default, Copy, PartialEq)] +pub struct TbtConfig { + /// Enable Thunderbolt + pub tbt_enabled: bool, +} + +/// PD state-machine configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Default, Copy, PartialEq)] +pub struct PdStateMachineConfig { + /// Enable or disable the PD state-machine + pub enabled: bool, +} + +/// TypeC State Machine +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TypeCStateMachineState { + /// Sink state machine only + Sink, + /// Source state machine only + Source, + /// DRP state machine + Drp, + /// Disabled + Disabled, +} + +/// Port-specific command data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PortCommandData { + /// Get port status + PortStatus(Cached), + /// Get and clear events + ClearEvents, + /// Get retimer fw update state + RetimerFwUpdateGetState, + /// Set retimer fw update state + RetimerFwUpdateSetState, + /// Clear retimer fw update state + RetimerFwUpdateClearState, + /// Set retimer compliance + SetRetimerCompliance, + /// Reconfigure retimer + ReconfigureRetimer, + /// Get oldest unhandled PD alert + GetPdAlert, + /// Set the maximum sink voltage in mV for the given port + SetMaxSinkVoltage(Option), + /// Set unconstrained power + SetUnconstrainedPower(bool), + /// Clear the dead battery flag for the given port + ClearDeadBatteryFlag, + /// Get other VDM + GetOtherVdm, + /// Get attention VDM + GetAttnVdm, + /// Send VDM + SendVdm(SendVdm), + /// Set USB control configuration + SetUsbControl(UsbControlConfig), + /// Get DisplayPort status + GetDpStatus, + /// Set DisplayPort configuration + SetDpConfig(DpConfig), + /// Execute DisplayPort reset + ExecuteDrst, + /// Set Thunderbolt configuration + SetTbtConfig(TbtConfig), + /// Set PD state-machine configuration + SetPdStateMachineConfig(PdStateMachineConfig), + /// Set Type-C state-machine configuration + SetTypeCStateMachineConfig(TypeCStateMachineState), + /// Execute the UCSI command + ExecuteUcsiCommand(lpm::CommandData), +} + +/// Port-specific commands +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PortCommand { + /// Port ID + pub port: GlobalPortId, + /// Command data + pub data: PortCommandData, +} + +/// PD controller command-specific data +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RetimerFwUpdateState { + /// Retimer FW Update Inactive + Inactive, + /// Revimer FW Update Active + Active, +} + +/// Port-specific response data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PortResponseData { + /// Command completed with no error + Complete, + /// Port status + PortStatus(PortStatus), + /// ClearEvents + ClearEvents(PortEvent), + /// Retimer Fw Update status + RtFwUpdateStatus(RetimerFwUpdateState), + /// PD alert + PdAlert(Option), + /// Get other VDM + OtherVdm(OtherVdm), + /// Get attention VDM + AttnVdm(AttnVdm), + /// Get DisplayPort status + DpStatus(DpStatus), + /// UCSI response + UcsiResponse(Result, PdError>), +} + +impl PortResponseData { + /// Helper function to convert to a result + pub fn complete_or_err(self) -> Result<(), PdError> { + match self { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } +} + +/// Port-specific command response +pub type PortResponse = Result; + +/// PD controller command-specific data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InternalCommandData { + /// Reset the PD controller + Reset, + /// Get controller status + Status, + /// Sync controller state + SyncState, +} + +/// PD controller command +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// Controller specific command + Controller(InternalCommandData), + /// Port command + Port(PortCommand), + /// UCSI command passthrough + Lpm(lpm::GlobalCommand), +} + +/// Controller-specific response data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InternalResponseData<'a> { + /// Command complete + Complete, + /// Controller status + Status(ControllerStatus<'a>), +} + +/// Response for controller-specific commands +pub type InternalResponse<'a> = Result, PdError>; + +/// PD controller command response +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Response<'a> { + /// Controller response + Controller(InternalResponse<'a>), + /// UCSI response passthrough + Ucsi(ucsi::GlobalResponse), + /// Port response + Port(PortResponse), +} + +/// Controller status +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControllerStatus<'a> { + /// Current controller mode + pub mode: &'a str, + /// True if we did not have to boot from a backup FW bank + pub valid_fw_bank: bool, + /// FW version 0 + pub fw_version0: u32, + /// FW version 1 + pub fw_version1: u32, +} + +/// PD controller +pub struct Device<'a> { + node: intrusive_list::Node, + pub(crate) id: ControllerId, + ports: &'a [GlobalPortId], + num_ports: usize, + command: deferred::Channel>, +} + +impl intrusive_list::NodeContainer for Device<'static> { + fn get_node(&self) -> &intrusive_list::Node { + &self.node + } +} + +impl<'a> Device<'a> { + /// Create a new PD controller struct + pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { + Self { + node: intrusive_list::Node::uninit(), + id, + ports, + num_ports: ports.len(), + command: deferred::Channel::new(), + } + } + + /// Get the controller ID + pub fn id(&self) -> ControllerId { + self.id + } + + /// Send a command to this controller + pub async fn execute_command(&self, command: Command) -> Response<'_> { + self.command.execute(command).await + } + + /// Check if this controller has the given port + pub fn has_port(&self, port: GlobalPortId) -> bool { + self.lookup_local_port(port).is_ok() + } + + /// Covert a local port ID to a global port ID + pub fn lookup_global_port(&self, port: LocalPortId) -> Result { + Ok(*self.ports.get(port.0 as usize).ok_or(PdError::InvalidParams)?) + } + + /// Convert a global port ID to a local port ID + pub fn lookup_local_port(&self, port: GlobalPortId) -> Result { + self.ports + .iter() + .position(|p| *p == port) + .map(|p| LocalPortId(p as u8)) + .ok_or(PdError::InvalidParams) + } + + /// Create a command handler for this controller + /// + /// DROP SAFETY: Direct call to deferred channel primitive + pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, Command, Response<'static>> { + self.command.receive().await + } + + /// Notify that there are pending events on one or more ports + pub fn notify_ports(&self, ctx: &crate::service::context::Context, pending: PortPending) { + ctx.notify_ports(pending); + } + + /// Number of ports on this controller + pub fn num_ports(&self) -> usize { + self.num_ports + } + + /// Slice of global ports on the Device + pub fn ports(&self) -> &'a [GlobalPortId] { + self.ports + } +} + +/// PD controller trait that device drivers may use to integrate with internal messaging system +pub trait Controller { + /// Type of error returned by the bus + type BusError; + + /// Wait for a port event to occur + /// # Implementation guide + /// This function should be drop safe. + /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. + fn wait_port_event(&mut self) -> impl Future>>; + /// Returns and clears current events for the given port + /// # Implementation guide + /// This function should be drop safe. + /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. + fn clear_port_events( + &mut self, + port: LocalPortId, + ) -> impl Future>>; + /// Returns the port status + fn get_port_status(&mut self, port: LocalPortId) + -> impl Future>>; + + /// Reset the controller + fn reset_controller(&mut self) -> impl Future>>; + + /// Returns the retimer fw update state + fn get_rt_fw_update_status( + &mut self, + port: LocalPortId, + ) -> impl Future>>; + /// Set retimer fw update state + fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>>; + /// Clear retimer fw update state + fn clear_rt_fw_update_state( + &mut self, + port: LocalPortId, + ) -> impl Future>>; + /// Set retimer compliance + fn set_rt_compliance(&mut self, port: LocalPortId) -> impl Future>>; + + /// Reconfigure the retimer for the given port. + fn reconfigure_retimer(&mut self, port: LocalPortId) -> impl Future>>; + + /// Clear the dead battery flag for the given port. + fn clear_dead_battery_flag(&mut self, port: LocalPortId) + -> impl Future>>; + + /// Enable or disable sink path + fn enable_sink_path( + &mut self, + port: LocalPortId, + enable: bool, + ) -> impl Future>>; + /// Get current controller status + fn get_controller_status( + &mut self, + ) -> impl Future, Error>>; + /// Get current PD alert + fn get_pd_alert(&mut self, port: LocalPortId) -> impl Future, Error>>; + /// Set the maximum sink voltage for the given port + /// + /// This may trigger a renegotiation + fn set_max_sink_voltage( + &mut self, + port: LocalPortId, + voltage_mv: Option, + ) -> impl Future>>; + /// Set port unconstrained status + fn set_unconstrained_power( + &mut self, + port: LocalPortId, + unconstrained: bool, + ) -> impl Future>>; + + // TODO: remove all these once we migrate to a generic FW update trait + // https://github.com/OpenDevicePartnership/embedded-services/issues/242 + /// Get current FW version + fn get_active_fw_version(&mut self) -> impl Future>>; + /// Start a firmware update + fn start_fw_update(&mut self) -> impl Future>>; + /// Abort a firmware update + fn abort_fw_update(&mut self) -> impl Future>>; + /// Finalize a firmware update + fn finalize_fw_update(&mut self) -> impl Future>>; + /// Write firmware update contents + fn write_fw_contents( + &mut self, + offset: usize, + data: &[u8], + ) -> impl Future>>; + /// Get the Rx Other VDM data for the given port + fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>>; + /// Get the Rx Attention VDM data for the given port + fn get_attn_vdm(&mut self, port: LocalPortId) -> impl Future>>; + /// Send a VDM to the given port + fn send_vdm( + &mut self, + port: LocalPortId, + tx_vdm: SendVdm, + ) -> impl Future>>; + + /// Set USB control configuration for the given port + fn set_usb_control( + &mut self, + port: LocalPortId, + config: UsbControlConfig, + ) -> impl Future>>; + + /// Get DisplayPort status for the given port + fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>>; + /// Set DisplayPort configuration for the given port + fn set_dp_config( + &mut self, + port: LocalPortId, + config: DpConfig, + ) -> impl Future>>; + /// Execute PD Data Reset for the given port + fn execute_drst(&mut self, port: LocalPortId) -> impl Future>>; + + /// Set Thunderbolt configuration for the given port + fn set_tbt_config( + &mut self, + port: LocalPortId, + config: TbtConfig, + ) -> impl Future>>; + + /// Set PD state-machine configuration for the given port + fn set_pd_state_machine_config( + &mut self, + port: LocalPortId, + config: PdStateMachineConfig, + ) -> impl Future>>; + + /// Set Type-C state-machine configuration for the given port + fn set_type_c_state_machine_config( + &mut self, + port: LocalPortId, + state: TypeCStateMachineState, + ) -> impl Future>>; + + /// Execute the given UCSI command + fn execute_ucsi_command( + &mut self, + command: lpm::LocalCommand, + ) -> impl Future, Error>>; +} diff --git a/type-c-interface/src/service/context.rs b/type-c-interface/src/service/context.rs new file mode 100644 index 000000000..be2b02877 --- /dev/null +++ b/type-c-interface/src/service/context.rs @@ -0,0 +1,534 @@ +use embassy_sync::signal::Signal; +use embassy_time::{Duration, with_timeout}; +use embedded_usb_pd::ucsi::{self, lpm}; +use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; + +use crate::port::event::{PortEvent, PortPending}; +use crate::port::{ + AttnVdm, Command, ControllerStatus, Device, DpConfig, DpStatus, InternalCommandData, InternalResponseData, + OtherVdm, PdStateMachineConfig, PortCommand, PortCommandData, PortResponseData, PortStatus, Response, + RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, +}; +use crate::port::{Cached, ControllerId}; +use crate::service; +use crate::service::event::Event; +use embedded_services::{ + GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace, +}; + +/// Default command timeout +/// set to high value since this is intended to prevent an unresponsive device from blocking the service implementation +const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); + +/// Trait for types that contain a controller struct +pub trait DeviceContainer { + /// Get the controller struct + fn get_pd_controller_device(&self) -> &Device<'_>; +} + +impl DeviceContainer for Device<'_> { + fn get_pd_controller_device(&self) -> &Device<'_> { + self + } +} + +pub struct Context { + port_events: Signal, + /// Event broadcaster + broadcaster: broadcaster::Immediate, + /// Controller list + controllers: intrusive_list::IntrusiveList, +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +impl Context { + /// Create new Context + pub const fn new() -> Self { + Self { + port_events: Signal::new(), + broadcaster: broadcaster::Immediate::new(), + controllers: intrusive_list::IntrusiveList::new(), + } + } + + /// Notify that there are pending events on one or more ports + /// Each bit corresponds to a global port ID + pub fn notify_ports(&self, pending: PortPending) { + let raw_pending: u32 = pending.into(); + trace!("Notify ports: {:#x}", raw_pending); + // Early exit if no events + if pending.is_none() { + return; + } + + self.port_events + .signal(if let Some(flags) = self.port_events.try_take() { + flags.union(pending) + } else { + pending + }); + } + + /// Send a command to the given controller with no timeout + pub async fn send_controller_command_no_timeout( + &self, + controller_id: ControllerId, + command: InternalCommandData, + ) -> Result, PdError> { + let node = self + .controllers + .into_iter() + .find(|node| { + if let Some(controller) = node.data::() { + controller.id == controller_id + } else { + false + } + }) + .ok_or(PdError::InvalidController)?; + + match node + .data::() + .ok_or(PdError::InvalidController)? + .execute_command(Command::Controller(command)) + .await + { + Response::Controller(response) => response, + r => { + error!("Invalid response: expected controller, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send a command to the given controller with a timeout + pub async fn send_controller_command( + &self, + controller_id: ControllerId, + command: InternalCommandData, + ) -> Result, PdError> { + match with_timeout( + DEFAULT_TIMEOUT, + self.send_controller_command_no_timeout(controller_id, command), + ) + .await + { + Ok(response) => response, + Err(_) => Err(PdError::Timeout), + } + } + + /// Reset the given controller + pub async fn reset_controller(&self, controller_id: ControllerId) -> Result<(), PdError> { + self.send_controller_command(controller_id, InternalCommandData::Reset) + .await + .map(|_| ()) + } + + fn find_node_by_port(&self, port_id: GlobalPortId) -> Result<&IntrusiveNode, PdError> { + self.controllers + .into_iter() + .find(|node| { + if let Some(controller) = node.data::() { + controller.has_port(port_id) + } else { + false + } + }) + .ok_or(PdError::InvalidPort) + } + + /// Send a command to the given port + pub async fn send_port_command_ucsi_no_timeout( + &self, + port_id: GlobalPortId, + command: lpm::CommandData, + ) -> Result { + let node = self.find_node_by_port(port_id)?; + + match node + .data::() + .ok_or(PdError::InvalidController)? + .execute_command(Command::Lpm(lpm::Command::new(port_id, command))) + .await + { + Response::Ucsi(response) => Ok(response), + r => { + error!("Invalid response: expected LPM, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send a command to the given port with a timeout + pub async fn send_port_command_ucsi( + &self, + port_id: GlobalPortId, + command: lpm::CommandData, + ) -> Result { + match with_timeout( + DEFAULT_TIMEOUT, + self.send_port_command_ucsi_no_timeout(port_id, command), + ) + .await + { + Ok(response) => response, + Err(_) => Err(PdError::Timeout), + } + } + + /// Send a command to the given port with no timeout + pub async fn send_port_command_no_timeout( + &self, + port_id: GlobalPortId, + command: PortCommandData, + ) -> Result { + let node = self.find_node_by_port(port_id)?; + + match node + .data::() + .ok_or(PdError::InvalidController)? + .execute_command(Command::Port(PortCommand { + port: port_id, + data: command, + })) + .await + { + Response::Port(response) => response, + r => { + error!("Invalid response: expected port, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send a command to the given port with a timeout + pub async fn send_port_command( + &self, + port_id: GlobalPortId, + command: PortCommandData, + ) -> Result { + match with_timeout(DEFAULT_TIMEOUT, self.send_port_command_no_timeout(port_id, command)).await { + Ok(response) => response, + Err(_) => Err(PdError::Timeout), + } + } + + /// Get the current port events + pub async fn get_unhandled_events(&self) -> PortPending { + self.port_events.wait().await + } + + /// Get the unhandled events for the given port + pub async fn get_port_event(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::ClearEvents).await? { + PortResponseData::ClearEvents(event) => Ok(event), + r => { + error!("Invalid response: expected clear events, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the current port status + pub async fn get_port_status(&self, port: GlobalPortId, cached: Cached) -> Result { + match self + .send_port_command(port, PortCommandData::PortStatus(cached)) + .await? + { + PortResponseData::PortStatus(status) => Ok(status), + r => { + error!("Invalid response: expected port status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the oldest unhandled PD alert for the given port + pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { + match self.send_port_command(port, PortCommandData::GetPdAlert).await? { + PortResponseData::PdAlert(alert) => Ok(alert), + r => { + error!("Invalid response: expected PD alert, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the retimer fw update status + pub async fn get_rt_fw_update_status(&self, port: GlobalPortId) -> Result { + match self + .send_port_command(port, PortCommandData::RetimerFwUpdateGetState) + .await? + { + PortResponseData::RtFwUpdateStatus(status) => Ok(status), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set the retimer fw update state + pub async fn set_rt_fw_update_state(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::RetimerFwUpdateSetState) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Clear the retimer fw update state + pub async fn clear_rt_fw_update_state(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::RetimerFwUpdateClearState) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set the retimer compliance + pub async fn set_rt_compliance(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetRetimerCompliance) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Reconfigure the retimer for the given port. + pub async fn reconfigure_retimer(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::ReconfigureRetimer) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set the maximum sink voltage for the given port. + /// + /// See [`PortCommandData::SetMaxSinkVoltage`] for details on the `max_voltage_mv` parameter. + pub async fn set_max_sink_voltage(&self, port: GlobalPortId, max_voltage_mv: Option) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetMaxSinkVoltage(max_voltage_mv)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Clear the dead battery flag for the given port. + pub async fn clear_dead_battery_flag(&self, port: GlobalPortId) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::ClearDeadBatteryFlag) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Get current controller status + pub async fn get_controller_status( + &self, + controller_id: ControllerId, + ) -> Result, PdError> { + match self + .send_controller_command(controller_id, InternalCommandData::Status) + .await? + { + InternalResponseData::Status(status) => Ok(status), + r => { + error!("Invalid response: expected controller status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Set unconstrained power for the given port + pub async fn set_unconstrained_power(&self, port: GlobalPortId, unconstrained: bool) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetUnconstrainedPower(unconstrained)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Sync controller state + pub async fn sync_controller_state(&self, controller_id: ControllerId) -> Result<(), PdError> { + match self + .send_controller_command(controller_id, InternalCommandData::SyncState) + .await? + { + InternalResponseData::Complete => Ok(()), + r => { + error!("Invalid response: expected controller status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the other vdm for the given port + pub async fn get_other_vdm(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::GetOtherVdm).await? { + PortResponseData::OtherVdm(vdm) => Ok(vdm), + r => { + error!("Invalid response: expected other VDM, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Get the attention vdm for the given port + pub async fn get_attn_vdm(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::GetAttnVdm).await? { + PortResponseData::AttnVdm(vdm) => Ok(vdm), + r => { + error!("Invalid response: expected attention VDM, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Send VDM to the given port + pub async fn send_vdm(&self, port: GlobalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { + match self.send_port_command(port, PortCommandData::SendVdm(tx_vdm)).await? { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set USB control configuration for the given port + pub async fn set_usb_control(&self, port: GlobalPortId, config: UsbControlConfig) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetUsbControl(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Get DisplayPort status for the given port + pub async fn get_dp_status(&self, port: GlobalPortId) -> Result { + match self.send_port_command(port, PortCommandData::GetDpStatus).await? { + PortResponseData::DpStatus(status) => Ok(status), + r => { + error!("Invalid response: expected DP status, got {:?}", r); + Err(PdError::InvalidResponse) + } + } + } + + /// Set DisplayPort configuration for the given port + pub async fn set_dp_config(&self, port: GlobalPortId, config: DpConfig) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetDpConfig(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Execute PD Data Reset for the given port + pub async fn execute_drst(&self, port: GlobalPortId) -> Result<(), PdError> { + match self.send_port_command(port, PortCommandData::ExecuteDrst).await? { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set Thunderbolt configuration for the given port + pub async fn set_tbt_config(&self, port: GlobalPortId, config: TbtConfig) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetTbtConfig(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set PD state-machine configuration for the given port + pub async fn set_pd_state_machine_config( + &self, + port: GlobalPortId, + config: PdStateMachineConfig, + ) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetPdStateMachineConfig(config)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Set Type-C state-machine configuration for the given port + pub async fn set_type_c_state_machine_config( + &self, + port: GlobalPortId, + state: TypeCStateMachineState, + ) -> Result<(), PdError> { + match self + .send_port_command(port, PortCommandData::SetTypeCStateMachineConfig(state)) + .await? + { + PortResponseData::Complete => Ok(()), + _ => Err(PdError::InvalidResponse), + } + } + + /// Execute the given UCSI command + pub async fn execute_ucsi_command( + &self, + command: lpm::GlobalCommand, + ) -> Result, PdError> { + match self + .send_port_command(command.port(), PortCommandData::ExecuteUcsiCommand(command.operation())) + .await? + { + PortResponseData::UcsiResponse(response) => response, + _ => Err(PdError::InvalidResponse), + } + } + + /// Register a message receiver for type-C messages + pub async fn register_message_receiver( + &self, + receiver: &'static broadcaster::Receiver<'_, service::event::Event>, + ) -> intrusive_list::Result<()> { + self.broadcaster.register_receiver(receiver) + } + + /// Broadcast a type-C message to all subscribers + pub async fn broadcast_message(&self, message: service::event::Event) { + self.broadcaster.broadcast(message).await; + } + + /// Register a PD controller + pub fn register_controller(&self, controller: &'static impl DeviceContainer) -> Result<(), intrusive_list::Error> { + self.controllers.push(controller.get_pd_controller_device()) + } + + /// Get total number of ports on the system + pub fn get_num_ports(&self) -> usize { + self.controllers + .iter_only::() + .fold(0, |acc, controller| acc + controller.num_ports()) + } +} diff --git a/type-c-service/src/type_c/comms.rs b/type-c-interface/src/service/event.rs similarity index 88% rename from type-c-service/src/type_c/comms.rs rename to type-c-interface/src/service/event.rs index 9a998cd31..800d15558 100644 --- a/type-c-service/src/type_c/comms.rs +++ b/type-c-interface/src/service/event.rs @@ -5,7 +5,7 @@ use embedded_usb_pd::GlobalPortId; /// Message generated when a debug acessory is connected or disconnected #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DebugAccessoryMessage { +pub struct DebugAccessory { /// Port pub port: GlobalPortId, /// Connected @@ -25,9 +25,9 @@ pub struct UsciChangeIndicator { /// Top-level comms message #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CommsMessage { +pub enum Event { /// Debug accessory message - DebugAccessory(DebugAccessoryMessage), + DebugAccessory(DebugAccessory), /// UCSI CCI message UcsiCci(UsciChangeIndicator), } diff --git a/type-c-interface/src/service/mod.rs b/type-c-interface/src/service/mod.rs new file mode 100644 index 000000000..23749388f --- /dev/null +++ b/type-c-interface/src/service/mod.rs @@ -0,0 +1,2 @@ +pub mod context; +pub mod event; diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 812ed8972..59d5a1bcb 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -28,7 +28,7 @@ log = { workspace = true, optional = true } tps6699x = { workspace = true, features = ["embassy"] } cfu-service.workspace = true power-policy-interface.workspace = true -bitvec.workspace = true +type-c-interface.workspace = true [dev-dependencies] embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index 8470af87f..f801a6e85 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -1,10 +1,3 @@ -use crate::type_c; -use crate::type_c::ATTN_VDM_LEN; -use crate::type_c::controller::{ - self, AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, - TypeCStateMachineState, UsbControlConfig, -}; -use crate::type_c::event::PortEvent; use ::tps6699x::registers::field_sets::IntEventBus1; use ::tps6699x::registers::{PdCcPullUp, PpExtVbusSw, PpIntVbusSw}; use ::tps6699x::{PORT0, PORT1, TPS66993_NUM_PORTS, TPS66994_NUM_PORTS}; @@ -35,9 +28,15 @@ use tps6699x::command::{ }; use tps6699x::fw_update::UpdateConfig as FwUpdateConfig; use tps6699x::registers::port_config::TypeCStateMachine; +use type_c_interface::port::event::PortEvent; +use type_c_interface::port::{ATTN_VDM_LEN, DpConfig, DpStatus, PdStateMachineConfig, RetimerFwUpdateState}; +use type_c_interface::port::{ + AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, + TypeCStateMachineState, UsbControlConfig, +}; -use crate::type_c::power_capability_from_current; -use crate::type_c::power_capability_try_from_contract; +use crate::util::power_capability_from_current; +use crate::util::power_capability_try_from_contract; type Updater<'a, M, B> = BorrowedUpdaterInProgress>; @@ -409,10 +408,10 @@ impl Controller for Tps6699x<'_, M, B> { async fn get_rt_fw_update_status( &mut self, port: LocalPortId, - ) -> Result> { + ) -> Result> { match self.tps6699x.get_rt_fw_update_status(port).await { - Ok(true) => Ok(type_c::controller::RetimerFwUpdateState::Active), - Ok(false) => Ok(type_c::controller::RetimerFwUpdateState::Inactive), + Ok(true) => Ok(RetimerFwUpdateState::Active), + Ok(false) => Ok(RetimerFwUpdateState::Inactive), Err(e) => Err(e), } } @@ -658,7 +657,7 @@ impl Controller for Tps6699x<'_, M, B> { Ok(()) } - async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { + async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { let dp_status = self.tps6699x.get_dp_status(port).await?; debug!("Port{} DP status: {:#?}", port.0, dp_status); @@ -668,17 +667,13 @@ impl Controller for Tps6699x<'_, M, B> { let cfg_raw: PdDpPinConfig = dp_config.config_pin().into(); let pin_config: DpPinConfig = cfg_raw.into(); - Ok(controller::DpStatus { + Ok(DpStatus { alt_mode_entered, dfp_d_pin_cfg: pin_config, }) } - async fn set_dp_config( - &mut self, - port: LocalPortId, - config: controller::DpConfig, - ) -> Result<(), Error> { + async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), Error> { debug!("Port{} setting DP config: {:#?}", port.0, config); let mut dp_config_reg = self.tps6699x.get_dp_config(port).await?; @@ -717,7 +712,7 @@ impl Controller for Tps6699x<'_, M, B> { async fn set_pd_state_machine_config( &mut self, port: LocalPortId, - config: controller::PdStateMachineConfig, + config: PdStateMachineConfig, ) -> Result<(), Error> { debug!("Port{} setting PD state machine config: {:#?}", port.0, config); @@ -731,7 +726,7 @@ impl Controller for Tps6699x<'_, M, B> { async fn set_type_c_state_machine_config( &mut self, port: LocalPortId, - state: controller::TypeCStateMachineState, + state: TypeCStateMachineState, ) -> Result<(), Error> { debug!("Port{} setting Type-C state machine state: {:#?}", port.0, state); diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index c2bb53404..f3bddffda 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -2,12 +2,12 @@ pub mod driver; pub mod service; pub mod task; -pub mod type_c; +pub mod util; pub mod wrapper; use core::future::Future; -use type_c::event::{PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged}; +use type_c_interface::port::event::{PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged}; /// Enum to contain all port event variants #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -122,7 +122,7 @@ impl PortEventStreamer { mod tests { use core::sync::atomic::AtomicBool; - use crate::type_c::event::PortPending; + use type_c_interface::port::event::PortPending; use super::*; diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index b466e1cb7..4e209f883 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -3,18 +3,15 @@ use embassy_sync::{ mutex::Mutex, pubsub::{DynImmediatePublisher, DynSubscriber}, }; -use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, sync::Lockable, trace}; +use embedded_services::{GlobalRawMutex, debug, error, info, sync::Lockable, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; use power_policy_interface::psu; -use crate::type_c::{ - self, Cached, comms, - controller::PortStatus, - event::{PortNotificationSingle, PortStatusChanged}, -}; - use crate::{PortEventStreamer, PortEventVariant}; +use type_c_interface::port::event::{PortNotificationSingle, PortStatusChanged}; +use type_c_interface::port::{Cached, PortStatus}; +use type_c_interface::service::event; pub mod config; pub mod pd; @@ -45,9 +42,7 @@ where PSU::Inner: psu::Psu, { /// Type-C context - context: &'a type_c::controller::Context, - /// Controller intrusive list - controllers: &'a intrusive_list::IntrusiveList, + pub(crate) context: &'a type_c_interface::service::context::Context, /// Current state state: Mutex, /// Config @@ -70,6 +65,7 @@ where // This is present instead of just using [`power_policy::CommsMessage`] to allow for // supporting variants like `ConsumerConnected(GlobalPortId, ConsumerPowerCapability)` // But there's currently not a way to do look-ups between power policy device IDs and GlobalPortIds +#[derive(Copy, Clone)] pub enum PowerPolicyEvent { /// Unconstrained state changed Unconstrained(power_policy_interface::service::UnconstrainedState), @@ -80,6 +76,7 @@ pub enum PowerPolicyEvent { } /// Type-C service events +#[derive(Copy, Clone)] pub enum Event { /// Port event PortStatusChanged(GlobalPortId, PortStatusChanged, PortStatus), @@ -96,8 +93,7 @@ where /// Create a new service the given configuration pub fn create( config: config::Config, - context: &'a crate::type_c::controller::Context, - controller_list: &'a intrusive_list::IntrusiveList, + context: &'a type_c_interface::service::context::Context, power_policy_publisher: DynImmediatePublisher<'a, power_policy_interface::service::event::Event<'a, PSU>>, power_policy_subscriber: DynSubscriber<'a, power_policy_interface::service::event::Event<'a, PSU>>, ) -> Self { @@ -107,7 +103,6 @@ where config, _power_policy_event_publisher: power_policy_publisher.into(), power_policy_event_subscriber: Mutex::new(power_policy_subscriber), - controllers: controller_list, } } @@ -150,7 +145,7 @@ where } self.context - .broadcast_message(comms::CommsMessage::DebugAccessory(comms::DebugAccessoryMessage { + .broadcast_message(event::Event::DebugAccessory(event::DebugAccessory { port: port_id, connected: status.is_connected(), })) @@ -169,10 +164,7 @@ where match select(self.wait_port_flags(), self.wait_power_policy_event()).await { Either::First(mut stream) => { if let Some((port_id, event)) = stream - .next(|port_id| { - self.context - .get_port_event(self.controllers, GlobalPortId(port_id as u8)) - }) + .next(|port_id| self.context.get_port_event(GlobalPortId(port_id as u8))) .await? { let port_id = GlobalPortId(port_id as u8); @@ -180,10 +172,7 @@ where match event { PortEventVariant::StatusChanged(status_event) => { // Return a port status changed event - let status = self - .context - .get_port_status(self.controllers, port_id, Cached(true)) - .await?; + let status = self.context.get_port_status(port_id, Cached(true)).await?; return Ok(Event::PortStatusChanged(port_id, status_event, status)); } PortEventVariant::Notification(notification) => { @@ -225,8 +214,4 @@ where let event = self.wait_next().await?; self.process_event(event).await } - - pub(crate) fn controllers(&self) -> &'a intrusive_list::IntrusiveList { - self.controllers - } } diff --git a/type-c-service/src/service/pd.rs b/type-c-service/src/service/pd.rs index 2070dbbc7..b0d940ee4 100644 --- a/type-c-service/src/service/pd.rs +++ b/type-c-service/src/service/pd.rs @@ -1,6 +1,6 @@ //! Power Delivery (PD) related functionality. -use embedded_services::{intrusive_list, sync::Lockable}; +use embedded_services::sync::Lockable; use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; use power_policy_interface::psu; @@ -13,11 +13,7 @@ where /// Get the oldest unhandled PD alert for the given port. /// /// Returns [`None`] if no alerts are pending. - pub async fn get_pd_alert( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result, PdError> { - self.context.get_pd_alert(controllers, port).await + pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { + self.context.get_pd_alert(port).await } } diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index bc2839979..68e0ec5c3 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -35,9 +35,9 @@ where /// Set the unconstrained state for all ports pub(super) async fn set_unconstrained_all(&self, unconstrained: bool) -> Result<(), Error> { - for port_index in 0..self.context.get_num_ports(self.controllers) { + for port_index in 0..self.context.get_num_ports() { self.context - .set_unconstrained_power(self.controllers, GlobalPortId(port_index as u8), unconstrained) + .set_unconstrained_power(GlobalPortId(port_index as u8), unconstrained) .await?; } Ok(()) @@ -60,7 +60,7 @@ where self.set_unconstrained_all(true).await?; } else { // Only one unconstrained device is present, see if that's one of our ports - let num_ports = self.context.get_num_ports(self.controllers); + let num_ports = self.context.get_num_ports(); let unconstrained_port = state .port_status .iter() @@ -77,11 +77,7 @@ where ); for port_index in 0..num_ports { self.context - .set_unconstrained_power( - self.controllers, - GlobalPortId(port_index as u8), - port_index != unconstrained_index, - ) + .set_unconstrained_power(GlobalPortId(port_index as u8), port_index != unconstrained_index) .await?; } } else { diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index 800839c7f..8b34e2de4 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -7,6 +7,7 @@ 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 super::*; @@ -59,7 +60,7 @@ where } /// PPM get capabilities implementation - fn process_get_capabilities(&self, _controllers: &intrusive_list::IntrusiveList) -> ppm::ResponseData { + fn process_get_capabilities(&self) -> ppm::ResponseData { debug!("Get PPM capabilities: {:?}", self.config.ucsi_capabilities); let mut capabilities = self.config.ucsi_capabilities; // TODO: implement this when the refactoring stabilizes @@ -71,14 +72,13 @@ where &self, state: &mut State, command: &ucsi::ppm::Command, - controllers: &intrusive_list::IntrusiveList, ) -> Result, PdError> { match command { ppm::Command::SetNotificationEnable(enable) => { self.process_set_notification_enable(state, enable.notification_enable); Ok(None) } - ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities(controllers))), + ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities())), _ => Ok(None), // Other commands are currently no-ops } } @@ -114,7 +114,6 @@ where &self, state: &mut super::State, command: &ucsi::lpm::GlobalCommand, - controllers: &intrusive_list::IntrusiveList, ) -> Result, PdError> { debug!("Processing LPM command: {:?}", command); match command.operation() { @@ -123,11 +122,11 @@ where if let Some(capabilities) = &self.config.ucsi_port_capabilities { Ok(Some(lpm::ResponseData::GetConnectorCapability(*capabilities))) } else { - self.context.execute_ucsi_command(controllers, *command).await + self.context.execute_ucsi_command(*command).await } } lpm::CommandData::GetConnectorStatus => { - let mut response = self.context.execute_ucsi_command(controllers, *command).await; + let mut response = self.context.execute_ucsi_command(*command).await; if let Ok(Some(lpm::ResponseData::GetConnectorStatus(lpm::get_connector_status::ResponseData { status_change: ref mut states_change, status: @@ -147,7 +146,7 @@ where response } - _ => self.context.execute_ucsi_command(controllers, *command).await, + _ => self.context.execute_ucsi_command(*command).await, } } @@ -169,7 +168,7 @@ where if let Some(next_port) = state.pending_ports.front() { debug!("ACK_CCI processed, next pending port: {:?}", next_port); self.context - .broadcast_message(comms::CommsMessage::UcsiCci(comms::UsciChangeIndicator { + .broadcast_message(Event::UcsiCci(UsciChangeIndicator { port: *next_port, // False here because the OPM gets notified by the CCI, don't need a separate notification notify_opm: false, @@ -186,11 +185,7 @@ where } /// Process an external UCSI command - pub async fn process_ucsi_command( - &self, - controllers: &intrusive_list::IntrusiveList, - command: &GlobalCommand, - ) -> UcsiResponse { + pub async fn process_ucsi_command(&self, command: &GlobalCommand) -> UcsiResponse { let state = &mut self.state.lock().await; let mut next_input = Some(PpmInput::Command(command)); let mut response = UcsiResponse { @@ -234,12 +229,12 @@ where match command { ucsi::GlobalCommand::PpmCommand(ppm_command) => { response.data = self - .process_ppm_command(&mut state.ucsi, ppm_command, controllers) + .process_ppm_command(&mut state.ucsi, ppm_command) .map(|inner| inner.map(ResponseData::Ppm)); } ucsi::GlobalCommand::LpmCommand(lpm_command) => { response.data = self - .process_lpm_command(state, lpm_command, controllers) + .process_lpm_command(state, lpm_command) .await .map(|inner| inner.map(ResponseData::Lpm)); } @@ -365,7 +360,7 @@ where let notify_opm = state.pending_ports.is_empty(); if state.pending_ports.push_back(port_id).is_ok() { self.context - .broadcast_message(comms::CommsMessage::UcsiCci(comms::UsciChangeIndicator { + .broadcast_message(Event::UcsiCci(UsciChangeIndicator { port: port_id, notify_opm, })) diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index ca4770e52..b4234aaff 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,7 +1,7 @@ //! VDM (Vendor Defined Messages) related functionality. -use crate::type_c::controller::{AttnVdm, OtherVdm}; -use embedded_services::{intrusive_list, sync::Lockable}; +use type_c_interface::port::{AttnVdm, OtherVdm}; +use embedded_services::sync::Lockable; use embedded_usb_pd::{GlobalPortId, PdError}; use power_policy_interface::psu; @@ -12,20 +12,12 @@ where PSU::Inner: psu::Psu, { /// Get the other vdm for the given port - pub async fn get_other_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - ) -> Result { - self.context.get_other_vdm(controllers, port_id).await + pub async fn get_other_vdm(&self, port_id: GlobalPortId) -> Result { + self.context.get_other_vdm(port_id).await } /// Get the attention vdm for the given port - pub async fn get_attn_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - ) -> Result { - self.context.get_attn_vdm(controllers, port_id).await + pub async fn get_attn_vdm(&self, port_id: GlobalPortId) -> Result { + self.context.get_attn_vdm(port_id).await } } diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index afd52d886..cdc999e26 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -27,7 +27,7 @@ pub async fn task_closure< PSU::Inner: psu::Psu, S: event::Sender, V: crate::wrapper::FwOfferValidator, - D::Inner: crate::type_c::controller::Controller, + D::Inner: type_c_interface::port::Controller, { info!("Starting type-c task"); @@ -35,7 +35,7 @@ pub async fn task_closure< // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 for controller_wrapper in wrappers { - if controller_wrapper.register(service.controllers(), cfu_client).is_err() { + if controller_wrapper.register(cfu_client).is_err() { error!("Failed to register a controller"); return; } @@ -57,7 +57,7 @@ pub async fn task<'a, M, D, PSU: Lockable, S, V, const N: usize>( PSU::Inner: psu::Psu, S: event::Sender, V: crate::wrapper::FwOfferValidator, - ::Inner: crate::type_c::controller::Controller, + ::Inner: type_c_interface::port::Controller, { task_closure(service, wrappers, cfu_client, |service: &Service<'_, PSU>| async { if let Err(e) = service.process_next_event().await { diff --git a/type-c-service/src/type_c/controller.rs b/type-c-service/src/type_c/controller.rs deleted file mode 100644 index b076f6371..000000000 --- a/type-c-service/src/type_c/controller.rs +++ /dev/null @@ -1,1315 +0,0 @@ -//! PD controller related code -use core::future::Future; - -use embassy_sync::signal::Signal; -use embassy_time::{Duration, with_timeout}; -use embedded_usb_pd::ucsi::{self, lpm}; -use embedded_usb_pd::{ - DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, - ado::Ado, - pdinfo::{AltMode, PowerPathStatus}, - type_c::ConnectionState, -}; - -use super::{ATTN_VDM_LEN, ControllerId, OTHER_VDM_LEN}; -use crate::type_c::Cached; -use crate::type_c::comms::CommsMessage; -use crate::type_c::event::{PortEvent, PortPending}; -use embedded_services::ipc::deferred; -use embedded_services::{ - GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace, -}; - -/// maximum number of data objects in a VDM -pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each - -/// Port status -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortStatus { - /// Current available source contract - pub available_source_contract: Option, - /// Current available sink contract - pub available_sink_contract: Option, - /// Current connection state - pub connection_state: Option, - /// Port partner supports dual-power roles - pub dual_power: bool, - /// plug orientation - pub plug_orientation: PlugOrientation, - /// power role - pub power_role: PowerRole, - /// data role - pub data_role: DataRole, - /// Active alt-modes - pub alt_mode: AltMode, - /// Power path status - pub power_path: PowerPathStatus, - /// EPR mode active - pub epr: bool, - /// Port partner is unconstrained - pub unconstrained_power: bool, -} - -impl PortStatus { - /// Create a new blank port status - /// Needed because default() is not const - pub const fn new() -> Self { - Self { - available_source_contract: None, - available_sink_contract: None, - connection_state: None, - dual_power: false, - plug_orientation: PlugOrientation::CC1, - power_role: PowerRole::Sink, - data_role: DataRole::Dfp, - alt_mode: AltMode::none(), - power_path: PowerPathStatus::none(), - epr: false, - unconstrained_power: false, - } - } - - /// Check if the port is connected - pub fn is_connected(&self) -> bool { - matches!( - self.connection_state, - Some(ConnectionState::Attached) - | Some(ConnectionState::DebugAccessory) - | Some(ConnectionState::AudioAccessory) - ) - } - - /// Check if a debug accessory is connected - pub fn is_debug_accessory(&self) -> bool { - matches!(self.connection_state, Some(ConnectionState::DebugAccessory)) - } -} - -impl Default for PortStatus { - fn default() -> Self { - Self::new() - } -} - -/// Other Vdm data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OtherVdm { - /// Other VDM data - pub data: [u8; OTHER_VDM_LEN], -} - -impl Default for OtherVdm { - fn default() -> Self { - Self { - data: [0; OTHER_VDM_LEN], - } - } -} - -impl From for [u8; OTHER_VDM_LEN] { - fn from(vdm: OtherVdm) -> Self { - vdm.data - } -} - -impl From<[u8; OTHER_VDM_LEN]> for OtherVdm { - fn from(data: [u8; OTHER_VDM_LEN]) -> Self { - Self { data } - } -} - -/// Attention Vdm data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AttnVdm { - /// Attention VDM data - pub data: [u8; ATTN_VDM_LEN], -} - -/// DisplayPort pin configuration -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpPinConfig { - /// 4L DP connection using USBC-USBC cable (Pin Assignment C) - pub pin_c: bool, - /// 2L USB + 2L DP connection using USBC-USBC cable (Pin Assignment D) - pub pin_d: bool, - /// 4L DP connection using USBC-DP cable (Pin Assignment E) - pub pin_e: bool, -} - -/// DisplayPort status data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpStatus { - /// DP alt-mode entered - pub alt_mode_entered: bool, - /// Get DP DFP pin config - pub dfp_d_pin_cfg: DpPinConfig, -} - -/// DisplayPort configuration data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpConfig { - /// DP alt-mode enabled - pub enable: bool, - /// Set DP DFP pin config - pub dfp_d_pin_cfg: DpPinConfig, -} - -impl Default for AttnVdm { - fn default() -> Self { - Self { - data: [0; ATTN_VDM_LEN], - } - } -} - -impl From for [u8; ATTN_VDM_LEN] { - fn from(vdm: AttnVdm) -> Self { - vdm.data - } -} - -impl From<[u8; ATTN_VDM_LEN]> for AttnVdm { - fn from(data: [u8; ATTN_VDM_LEN]) -> Self { - Self { data } - } -} - -/// Send VDM data -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SendVdm { - /// initiating a VDM sequence - pub initiator: bool, - /// VDO count - pub vdo_count: u8, - /// VDO data - pub vdo_data: [u32; MAX_NUM_DATA_OBJECTS], -} - -impl SendVdm { - /// Create a new blank port status - pub const fn new() -> Self { - Self { - initiator: false, - vdo_count: 0, - vdo_data: [0; MAX_NUM_DATA_OBJECTS], - } - } -} - -impl Default for SendVdm { - fn default() -> Self { - Self::new() - } -} - -/// USB control configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct UsbControlConfig { - /// Enable USB2 data path - pub usb2_enabled: bool, - /// Enable USB3 data path - pub usb3_enabled: bool, - /// Enable USB4 data path - pub usb4_enabled: bool, -} - -impl Default for UsbControlConfig { - fn default() -> Self { - Self { - usb2_enabled: true, - usb3_enabled: true, - usb4_enabled: true, - } - } -} - -/// Thunderbolt control configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] -pub struct TbtConfig { - /// Enable Thunderbolt - pub tbt_enabled: bool, -} - -/// PD state-machine configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] -pub struct PdStateMachineConfig { - /// Enable or disable the PD state-machine - pub enabled: bool, -} - -/// TypeC State Machine -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum TypeCStateMachineState { - /// Sink state machine only - Sink, - /// Source state machine only - Source, - /// DRP state machine - Drp, - /// Disabled - Disabled, -} - -/// Port-specific command data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortCommandData { - /// Get port status - PortStatus(Cached), - /// Get and clear events - ClearEvents, - /// Get retimer fw update state - RetimerFwUpdateGetState, - /// Set retimer fw update state - RetimerFwUpdateSetState, - /// Clear retimer fw update state - RetimerFwUpdateClearState, - /// Set retimer compliance - SetRetimerCompliance, - /// Reconfigure retimer - ReconfigureRetimer, - /// Get oldest unhandled PD alert - GetPdAlert, - /// Set the maximum sink voltage in mV for the given port - SetMaxSinkVoltage(Option), - /// Set unconstrained power - SetUnconstrainedPower(bool), - /// Clear the dead battery flag for the given port - ClearDeadBatteryFlag, - /// Get other VDM - GetOtherVdm, - /// Get attention VDM - GetAttnVdm, - /// Send VDM - SendVdm(SendVdm), - /// Set USB control configuration - SetUsbControl(UsbControlConfig), - /// Get DisplayPort status - GetDpStatus, - /// Set DisplayPort configuration - SetDpConfig(DpConfig), - /// Execute DisplayPort reset - ExecuteDrst, - /// Set Thunderbolt configuration - SetTbtConfig(TbtConfig), - /// Set PD state-machine configuration - SetPdStateMachineConfig(PdStateMachineConfig), - /// Set Type-C state-machine configuration - SetTypeCStateMachineConfig(TypeCStateMachineState), - /// Execute the UCSI command - ExecuteUcsiCommand(lpm::CommandData), -} - -/// Port-specific commands -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortCommand { - /// Port ID - pub port: GlobalPortId, - /// Command data - pub data: PortCommandData, -} - -/// PD controller command-specific data -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RetimerFwUpdateState { - /// Retimer FW Update Inactive - Inactive, - /// Revimer FW Update Active - Active, -} - -/// Port-specific response data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortResponseData { - /// Command completed with no error - Complete, - /// Port status - PortStatus(PortStatus), - /// ClearEvents - ClearEvents(PortEvent), - /// Retimer Fw Update status - RtFwUpdateStatus(RetimerFwUpdateState), - /// PD alert - PdAlert(Option), - /// Get other VDM - OtherVdm(OtherVdm), - /// Get attention VDM - AttnVdm(AttnVdm), - /// Get DisplayPort status - DpStatus(DpStatus), - /// UCSI response - UcsiResponse(Result, PdError>), -} - -impl PortResponseData { - /// Helper function to convert to a result - pub fn complete_or_err(self) -> Result<(), PdError> { - match self { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } -} - -/// Port-specific command response -pub type PortResponse = Result; - -/// PD controller command-specific data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InternalCommandData { - /// Reset the PD controller - Reset, - /// Get controller status - Status, - /// Sync controller state - SyncState, -} - -/// PD controller command -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Command { - /// Controller specific command - Controller(InternalCommandData), - /// Port command - Port(PortCommand), - /// UCSI command passthrough - Lpm(lpm::GlobalCommand), -} - -/// Controller-specific response data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InternalResponseData<'a> { - /// Command complete - Complete, - /// Controller status - Status(ControllerStatus<'a>), -} - -/// Response for controller-specific commands -pub type InternalResponse<'a> = Result, PdError>; - -/// PD controller command response -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Response<'a> { - /// Controller response - Controller(InternalResponse<'a>), - /// UCSI response passthrough - Ucsi(ucsi::GlobalResponse), - /// Port response - Port(PortResponse), -} - -/// Controller status -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerStatus<'a> { - /// Current controller mode - pub mode: &'a str, - /// True if we did not have to boot from a backup FW bank - pub valid_fw_bank: bool, - /// FW version 0 - pub fw_version0: u32, - /// FW version 1 - pub fw_version1: u32, -} - -/// PD controller -pub struct Device<'a> { - node: intrusive_list::Node, - id: ControllerId, - ports: &'a [GlobalPortId], - num_ports: usize, - command: deferred::Channel>, -} - -impl intrusive_list::NodeContainer for Device<'static> { - fn get_node(&self) -> &intrusive_list::Node { - &self.node - } -} - -impl<'a> Device<'a> { - /// Create a new PD controller struct - pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { - Self { - node: intrusive_list::Node::uninit(), - id, - ports, - num_ports: ports.len(), - command: deferred::Channel::new(), - } - } - - /// Get the controller ID - pub fn id(&self) -> ControllerId { - self.id - } - - /// Send a command to this controller - pub async fn execute_command(&self, command: Command) -> Response<'_> { - self.command.execute(command).await - } - - /// Check if this controller has the given port - pub fn has_port(&self, port: GlobalPortId) -> bool { - self.lookup_local_port(port).is_ok() - } - - /// Covert a local port ID to a global port ID - pub fn lookup_global_port(&self, port: LocalPortId) -> Result { - Ok(*self.ports.get(port.0 as usize).ok_or(PdError::InvalidParams)?) - } - - /// Convert a global port ID to a local port ID - pub fn lookup_local_port(&self, port: GlobalPortId) -> Result { - self.ports - .iter() - .position(|p| *p == port) - .map(|p| LocalPortId(p as u8)) - .ok_or(PdError::InvalidParams) - } - - /// Create a command handler for this controller - /// - /// DROP SAFETY: Direct call to deferred channel primitive - pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, Command, Response<'static>> { - self.command.receive().await - } - - /// Notify that there are pending events on one or more ports - pub fn notify_ports(&self, ctx: &Context, pending: PortPending) { - ctx.notify_ports(pending); - } - - /// Number of ports on this controller - pub fn num_ports(&self) -> usize { - self.num_ports - } - - /// Slice of global ports on the Device - pub fn ports(&self) -> &'a [GlobalPortId] { - self.ports - } -} - -/// Trait for types that contain a controller struct -pub trait DeviceContainer { - /// Get the controller struct - fn get_pd_controller_device(&self) -> &Device<'_>; -} - -impl DeviceContainer for Device<'_> { - fn get_pd_controller_device(&self) -> &Device<'_> { - self - } -} - -/// PD controller trait that device drivers may use to integrate with internal messaging system -pub trait Controller { - /// Type of error returned by the bus - type BusError; - - /// Wait for a port event to occur - /// # Implementation guide - /// This function should be drop safe. - /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. - fn wait_port_event(&mut self) -> impl Future>>; - /// Returns and clears current events for the given port - /// # Implementation guide - /// This function should be drop safe. - /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. - fn clear_port_events( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Returns the port status - fn get_port_status(&mut self, port: LocalPortId) - -> impl Future>>; - - /// Reset the controller - fn reset_controller(&mut self) -> impl Future>>; - - /// Returns the retimer fw update state - fn get_rt_fw_update_status( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Set retimer fw update state - fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>>; - /// Clear retimer fw update state - fn clear_rt_fw_update_state( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Set retimer compliance - fn set_rt_compliance(&mut self, port: LocalPortId) -> impl Future>>; - - /// Reconfigure the retimer for the given port. - fn reconfigure_retimer(&mut self, port: LocalPortId) -> impl Future>>; - - /// Clear the dead battery flag for the given port. - fn clear_dead_battery_flag(&mut self, port: LocalPortId) - -> impl Future>>; - - /// Enable or disable sink path - fn enable_sink_path( - &mut self, - port: LocalPortId, - enable: bool, - ) -> impl Future>>; - /// Get current controller status - fn get_controller_status( - &mut self, - ) -> impl Future, Error>>; - /// Get current PD alert - fn get_pd_alert(&mut self, port: LocalPortId) -> impl Future, Error>>; - /// Set the maximum sink voltage for the given port - /// - /// This may trigger a renegotiation - fn set_max_sink_voltage( - &mut self, - port: LocalPortId, - voltage_mv: Option, - ) -> impl Future>>; - /// Set port unconstrained status - fn set_unconstrained_power( - &mut self, - port: LocalPortId, - unconstrained: bool, - ) -> impl Future>>; - - // TODO: remove all these once we migrate to a generic FW update trait - // https://github.com/OpenDevicePartnership/embedded-services/issues/242 - /// Get current FW version - fn get_active_fw_version(&mut self) -> impl Future>>; - /// Start a firmware update - fn start_fw_update(&mut self) -> impl Future>>; - /// Abort a firmware update - fn abort_fw_update(&mut self) -> impl Future>>; - /// Finalize a firmware update - fn finalize_fw_update(&mut self) -> impl Future>>; - /// Write firmware update contents - fn write_fw_contents( - &mut self, - offset: usize, - data: &[u8], - ) -> impl Future>>; - /// Get the Rx Other VDM data for the given port - fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>>; - /// Get the Rx Attention VDM data for the given port - fn get_attn_vdm(&mut self, port: LocalPortId) -> impl Future>>; - /// Send a VDM to the given port - fn send_vdm( - &mut self, - port: LocalPortId, - tx_vdm: SendVdm, - ) -> impl Future>>; - - /// Set USB control configuration for the given port - fn set_usb_control( - &mut self, - port: LocalPortId, - config: UsbControlConfig, - ) -> impl Future>>; - - /// Get DisplayPort status for the given port - fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>>; - /// Set DisplayPort configuration for the given port - fn set_dp_config( - &mut self, - port: LocalPortId, - config: DpConfig, - ) -> impl Future>>; - /// Execute PD Data Reset for the given port - fn execute_drst(&mut self, port: LocalPortId) -> impl Future>>; - - /// Set Thunderbolt configuration for the given port - fn set_tbt_config( - &mut self, - port: LocalPortId, - config: TbtConfig, - ) -> impl Future>>; - - /// Set PD state-machine configuration for the given port - fn set_pd_state_machine_config( - &mut self, - port: LocalPortId, - config: PdStateMachineConfig, - ) -> impl Future>>; - - /// Set Type-C state-machine configuration for the given port - fn set_type_c_state_machine_config( - &mut self, - port: LocalPortId, - state: TypeCStateMachineState, - ) -> impl Future>>; - - /// Execute the given UCSI command - fn execute_ucsi_command( - &mut self, - command: lpm::LocalCommand, - ) -> impl Future, Error>>; -} - -/// Internal context for managing PD controllers -pub struct Context { - port_events: Signal, - /// Event broadcaster - broadcaster: broadcaster::Immediate, -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} - -impl Context { - /// Create new Context - pub const fn new() -> Self { - Self { - port_events: Signal::new(), - broadcaster: broadcaster::Immediate::new(), - } - } - - /// Notify that there are pending events on one or more ports - /// Each bit corresponds to a global port ID - pub fn notify_ports(&self, pending: PortPending) { - let raw_pending: u32 = pending.into(); - trace!("Notify ports: {:#x}", raw_pending); - // Early exit if no events - if pending.is_none() { - return; - } - - self.port_events - .signal(if let Some(flags) = self.port_events.try_take() { - flags.union(pending) - } else { - pending - }); - } - - /// Send a command to the given controller with no timeout - pub async fn send_controller_command_no_timeout( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - command: InternalCommandData, - ) -> Result, PdError> { - let node = controllers - .into_iter() - .find(|node| { - if let Some(controller) = node.data::() { - controller.id == controller_id - } else { - false - } - }) - .ok_or(PdError::InvalidController)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Controller(command)) - .await - { - Response::Controller(response) => response, - r => { - error!("Invalid response: expected controller, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given controller with a timeout - pub async fn send_controller_command( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - command: InternalCommandData, - ) -> Result, PdError> { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_controller_command_no_timeout(controllers, controller_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - - /// Reset the given controller - pub async fn reset_controller( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - ) -> Result<(), PdError> { - self.send_controller_command(controllers, controller_id, InternalCommandData::Reset) - .await - .map(|_| ()) - } - - fn find_node_by_port( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - ) -> Result<&IntrusiveNode, PdError> { - controllers - .into_iter() - .find(|node| { - if let Some(controller) = node.data::() { - controller.has_port(port_id) - } else { - false - } - }) - .ok_or(PdError::InvalidPort) - } - - /// Send a command to the given port - pub async fn send_port_command_ucsi_no_timeout( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: lpm::CommandData, - ) -> Result { - let node = self.find_node_by_port(controllers, port_id)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Lpm(lpm::Command::new(port_id, command))) - .await - { - Response::Ucsi(response) => Ok(response), - r => { - error!("Invalid response: expected LPM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given port with a timeout - pub async fn send_port_command_ucsi( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: lpm::CommandData, - ) -> Result { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_port_command_ucsi_no_timeout(controllers, port_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - - /// Send a command to the given port with no timeout - pub async fn send_port_command_no_timeout( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: PortCommandData, - ) -> Result { - let node = self.find_node_by_port(controllers, port_id)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Port(PortCommand { - port: port_id, - data: command, - })) - .await - { - Response::Port(response) => response, - r => { - error!("Invalid response: expected port, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given port with a timeout - pub async fn send_port_command( - &self, - controllers: &intrusive_list::IntrusiveList, - port_id: GlobalPortId, - command: PortCommandData, - ) -> Result { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_port_command_no_timeout(controllers, port_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - - /// Get the current port events - pub async fn get_unhandled_events(&self) -> PortPending { - self.port_events.wait().await - } - - /// Get the unhandled events for the given port - pub async fn get_port_event( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::ClearEvents) - .await? - { - PortResponseData::ClearEvents(event) => Ok(event), - r => { - error!("Invalid response: expected clear events, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the current port status - pub async fn get_port_status( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - cached: Cached, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::PortStatus(cached)) - .await? - { - PortResponseData::PortStatus(status) => Ok(status), - r => { - error!("Invalid response: expected port status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the oldest unhandled PD alert for the given port - pub async fn get_pd_alert( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result, PdError> { - match self - .send_port_command(controllers, port, PortCommandData::GetPdAlert) - .await? - { - PortResponseData::PdAlert(alert) => Ok(alert), - r => { - error!("Invalid response: expected PD alert, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the retimer fw update status - pub async fn get_rt_fw_update_status( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateGetState) - .await? - { - PortResponseData::RtFwUpdateStatus(status) => Ok(status), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer fw update state - pub async fn set_rt_fw_update_state( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateSetState) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the retimer fw update state - pub async fn clear_rt_fw_update_state( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::RetimerFwUpdateClearState) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the retimer compliance - pub async fn set_rt_compliance( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetRetimerCompliance) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Reconfigure the retimer for the given port. - pub async fn reconfigure_retimer( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::ReconfigureRetimer) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set the maximum sink voltage for the given port. - /// - /// See [`PortCommandData::SetMaxSinkVoltage`] for details on the `max_voltage_mv` parameter. - pub async fn set_max_sink_voltage( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - max_voltage_mv: Option, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetMaxSinkVoltage(max_voltage_mv)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Clear the dead battery flag for the given port. - pub async fn clear_dead_battery_flag( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::ClearDeadBatteryFlag) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get current controller status - pub async fn get_controller_status( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - ) -> Result, PdError> { - match self - .send_controller_command(controllers, controller_id, InternalCommandData::Status) - .await? - { - InternalResponseData::Status(status) => Ok(status), - r => { - error!("Invalid response: expected controller status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Set unconstrained power for the given port - pub async fn set_unconstrained_power( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - unconstrained: bool, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetUnconstrainedPower(unconstrained)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Sync controller state - pub async fn sync_controller_state( - &self, - controllers: &intrusive_list::IntrusiveList, - controller_id: ControllerId, - ) -> Result<(), PdError> { - match self - .send_controller_command(controllers, controller_id, InternalCommandData::SyncState) - .await? - { - InternalResponseData::Complete => Ok(()), - r => { - error!("Invalid response: expected controller status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the number of ports on the system - pub fn get_num_ports(&self, controllers: &intrusive_list::IntrusiveList) -> usize { - get_num_ports(controllers) - } - - /// Get the other vdm for the given port - pub async fn get_other_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::GetOtherVdm) - .await? - { - PortResponseData::OtherVdm(vdm) => Ok(vdm), - r => { - error!("Invalid response: expected other VDM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the attention vdm for the given port - pub async fn get_attn_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::GetAttnVdm) - .await? - { - PortResponseData::AttnVdm(vdm) => Ok(vdm), - r => { - error!("Invalid response: expected attention VDM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send VDM to the given port - pub async fn send_vdm( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - tx_vdm: SendVdm, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SendVdm(tx_vdm)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set USB control configuration for the given port - pub async fn set_usb_control( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: UsbControlConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetUsbControl(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Get DisplayPort status for the given port - pub async fn get_dp_status( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result { - match self - .send_port_command(controllers, port, PortCommandData::GetDpStatus) - .await? - { - PortResponseData::DpStatus(status) => Ok(status), - r => { - error!("Invalid response: expected DP status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Set DisplayPort configuration for the given port - pub async fn set_dp_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: DpConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetDpConfig(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute PD Data Reset for the given port - pub async fn execute_drst( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::ExecuteDrst) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Thunderbolt configuration for the given port - pub async fn set_tbt_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: TbtConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetTbtConfig(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set PD state-machine configuration for the given port - pub async fn set_pd_state_machine_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - config: PdStateMachineConfig, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetPdStateMachineConfig(config)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Set Type-C state-machine configuration for the given port - pub async fn set_type_c_state_machine_config( - &self, - controllers: &intrusive_list::IntrusiveList, - port: GlobalPortId, - state: TypeCStateMachineState, - ) -> Result<(), PdError> { - match self - .send_port_command(controllers, port, PortCommandData::SetTypeCStateMachineConfig(state)) - .await? - { - PortResponseData::Complete => Ok(()), - _ => Err(PdError::InvalidResponse), - } - } - - /// Execute the given UCSI command - pub async fn execute_ucsi_command( - &self, - controllers: &intrusive_list::IntrusiveList, - command: lpm::GlobalCommand, - ) -> Result, PdError> { - match self - .send_port_command( - controllers, - command.port(), - PortCommandData::ExecuteUcsiCommand(command.operation()), - ) - .await? - { - PortResponseData::UcsiResponse(response) => response, - _ => Err(PdError::InvalidResponse), - } - } - - /// Register a message receiver for type-C messages - pub async fn register_message_receiver( - &self, - receiver: &'static broadcaster::Receiver<'_, CommsMessage>, - ) -> intrusive_list::Result<()> { - self.broadcaster.register_receiver(receiver) - } - - /// Broadcast a type-C message to all subscribers - pub async fn broadcast_message(&self, message: CommsMessage) { - self.broadcaster.broadcast(message).await; - } -} - -/// Default command timeout -/// set to high value since this is intended to prevent an unresponsive device from blocking the service implementation -const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); - -/// Register a PD controller -pub fn register_controller( - controllers: &intrusive_list::IntrusiveList, - controller: &'static impl DeviceContainer, -) -> Result<(), intrusive_list::Error> { - controllers.push(controller.get_pd_controller_device()) -} - -/// Get total number of ports on the system -pub(super) fn get_num_ports(controllers: &intrusive_list::IntrusiveList) -> usize { - controllers - .iter_only::() - .fold(0, |acc, controller| acc + controller.num_ports()) -} diff --git a/type-c-service/src/type_c/mod.rs b/type-c-service/src/util.rs similarity index 74% rename from type-c-service/src/type_c/mod.rs rename to type-c-service/src/util.rs index a4fa3f00c..9eaadeaf7 100644 --- a/type-c-service/src/type_c/mod.rs +++ b/type-c-service/src/util.rs @@ -1,21 +1,7 @@ -//! Type-C service +//! Type-C service utility functions and constants. use embedded_usb_pd::pdo::{Common, Contract}; use embedded_usb_pd::type_c; -pub mod comms; -pub mod controller; -pub mod event; - -/// Controller ID -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerId(pub u8); - -/// Length of the Other VDM data -pub const OTHER_VDM_LEN: usize = 29; -/// Length of the Attention VDM data -pub const ATTN_VDM_LEN: usize = 9; - pub fn power_capability_try_from_contract( contract: Contract, ) -> Option { @@ -60,8 +46,3 @@ pub const POWER_CAPABILITY_5V_3A0: power_policy_interface::capability::PowerCapa voltage_mv: 5000, current_ma: 3000, }; - -/// Newtype to help clarify arguments to port status commands -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Cached(pub bool); diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 68c54916d..92e182d34 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -16,11 +16,8 @@ use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::event; use embedded_usb_pd::{GlobalPortId, ado::Ado}; -use crate::type_c::{ - ControllerId, - controller::PortStatus, - event::{PortEvent, PortStatusChanged}, -}; +use type_c_interface::port::event::{PortEvent, PortStatusChanged}; +use type_c_interface::port::{ControllerId, PortStatus}; use crate::{ PortEventStreamer, @@ -50,8 +47,8 @@ impl Default for ControllerState { /// Service registration objects pub struct Registration<'a, M: RawMutex> { - pub context: &'a crate::type_c::controller::Context, - pub pd_controller: &'a crate::type_c::controller::Device<'a>, + pub context: &'a type_c_interface::service::context::Context, + pub pd_controller: &'a type_c_interface::port::Device<'a>, pub cfu_device: &'a CfuDevice, pub power_devices: &'a [&'a Mutex>], } @@ -68,7 +65,7 @@ const MAX_BUFFERED_PD_ALERTS: usize = 4; /// Base storage pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related - context: &'a crate::type_c::controller::Context, + context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, pd_ports: [GlobalPortId; N], cfu_device: CfuDevice, @@ -80,7 +77,7 @@ pub struct Storage<'a, const N: usize, M: RawMutex> { impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( - context: &'a crate::type_c::controller::Context, + context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, cfu_id: ComponentId, pd_ports: [GlobalPortId; N], @@ -207,7 +204,7 @@ pub struct ReferencedStorage< S: event::Sender, > { intermediate: &'a IntermediateStorage<'a, N, M, S>, - pd_controller: crate::type_c::controller::Device<'a>, + pd_controller: type_c_interface::port::Device<'a>, power_devices: [&'a Mutex>; N], } @@ -218,7 +215,7 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender) -> Option { Some(Self { intermediate, - pd_controller: crate::type_c::controller::Device::new( + pd_controller: type_c_interface::port::Device::new( intermediate.storage.controller_id, intermediate.storage.pd_ports.as_slice(), ), diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index beb786e90..0a83689ba 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,7 +1,7 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation -use crate::type_c::controller::Controller; use crate::wrapper::backing::ControllerState; +use type_c_interface::port::Controller; use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 0bf3165a0..39da1c89e 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,5 +1,5 @@ use super::{ControllerWrapper, FwOfferValidator}; -use crate::type_c::controller::Controller; +use type_c_interface::port::Controller; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, sync::Lockable, trace}; diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index c49d31475..405c54d9f 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -2,9 +2,9 @@ use embedded_services::{GlobalRawMutex, ipc::deferred}; use embedded_usb_pd::{LocalPortId, ado::Ado}; -use crate::type_c::{ - controller::{self, DpStatus, PortStatus}, - event::{PortNotificationSingle, PortStatusChanged}, +use type_c_interface::{ + port::{self, DpStatus, PortStatus}, + port::event::{PortNotificationSingle, PortStatusChanged}, }; /// Port status changed event data @@ -56,7 +56,7 @@ pub enum Event<'a> { /// Power policy command received PowerPolicyCommand(EventPowerPolicyCommand), /// Command from TCPM - ControllerCommand(deferred::Request<'a, GlobalRawMutex, controller::Command, controller::Response<'static>>), + ControllerCommand(deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>), /// Cfu event CfuEvent(EventCfu), } @@ -94,15 +94,15 @@ pub struct OutputPowerPolicyCommand { /// Controller command output data pub struct OutputControllerCommand<'a> { /// Controller request - pub request: deferred::Request<'a, GlobalRawMutex, controller::Command, controller::Response<'static>>, + pub request: deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>, /// Response - pub response: controller::Response<'static>, + pub response: port::Response<'static>, } pub mod vdm { //! Events and output for vendor-defined messaging. use super::LocalPortId; - use crate::type_c::controller::{AttnVdm, OtherVdm}; + use type_c_interface::port::{AttnVdm, OtherVdm}; /// The kind of output from processing a vendor-defined message. #[derive(Copy, Clone, Debug)] diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index a724e92fd..343870891 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -1,8 +1,8 @@ //! This module contains the [`ControllerWrapper`] struct. This struct serves as a bridge between various service messages -//! and the actual controller functions provided by [`crate::type_c::controller::Controller`]. +//! and the actual controller functions provided by [`type_c_interface::port::Controller`]. //! # Supported service messaging //! This struct current currently supports messages from the following services: -//! * Type-C: [`crate::type_c::controller::Command`] +//! * Type-C: [`type_c_interface::port::Command`] //! * CFU: [`cfu_service::Request`] //! # Event loop //! This struct follows a standard wait/process/finalize event loop. @@ -19,8 +19,6 @@ use core::array::from_fn; use core::future::pending; use core::ops::DerefMut; -use crate::type_c::controller::{self, Controller, PortStatus}; -use crate::type_c::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; use crate::wrapper::backing::{ControllerState, PortState}; use cfu_service::CfuClient; use embassy_futures::select::{Either, Either5, select, select_array, select5}; @@ -31,7 +29,7 @@ use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::sync::Lockable; use embedded_services::{debug, error, info, trace, warn}; -use embedded_services::{event, intrusive_list}; +use embedded_services::{event}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; @@ -49,6 +47,9 @@ mod power; pub mod proxy; mod vdm; +use type_c_interface::port::{Controller, PortStatus}; +use type_c_interface::port::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; + /// Base interval for checking for FW update timeouts and recovery attempts pub const DEFAULT_FW_UPDATE_TICK_INTERVAL_MS: u64 = 5000; /// Default number of ticks before we consider a firmware update to have timed out @@ -66,7 +67,7 @@ pub trait FwOfferValidator { /// Maximum number of supported ports pub const MAX_SUPPORTED_PORTS: usize = 2; -/// Common functionality implemented on top of [`crate::type_c::controller::Controller`] +/// Common functionality implemented on top of [`type_c_interface::port::Controller`] pub struct ControllerWrapper< 'device, M: RawMutex, @@ -591,18 +592,17 @@ where } /// Register all devices with their respective services - pub fn register( - &'static self, - controllers: &intrusive_list::IntrusiveList, - cfu_client: &CfuClient, - ) -> Result<(), Error<::BusError>> { - controller::register_controller(controllers, self.registration.pd_controller).map_err(|_| { - error!( - "Controller{}: Failed to register PD controller", - self.registration.pd_controller.id().0 - ); - Error::Pd(PdError::Failed) - })?; + pub fn register(&'static self, cfu_client: &CfuClient) -> Result<(), Error<::BusError>> { + self.registration + .context + .register_controller(self.registration.pd_controller) + .map_err(|_| { + error!( + "Controller{}: Failed to register PD controller", + self.registration.pd_controller.id().0 + ); + Error::Pd(PdError::Failed) + })?; //TODO: Remove when we have a more general framework in place cfu_client.register_device(self.registration.cfu_device).map_err(|_| { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index a842bb2b4..a345c07fe 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,6 +1,6 @@ -use crate::type_c::Cached; -use crate::type_c::controller::{InternalResponseData, Response}; use crate::wrapper::backing::ControllerState; +use type_c_interface::port::Cached; +use type_c_interface::port::{InternalResponseData, Response}; use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; @@ -8,6 +8,7 @@ use embedded_services::debug; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; use embedded_usb_pd::ucsi::{self, lpm}; use power_policy_interface::psu::{self, PsuState}; +use type_c_interface::port; use super::*; @@ -110,7 +111,7 @@ where state: &psu::State, local_port: LocalPortId, voltage_mv: Option, - ) -> Result { + ) -> Result { let psu_state = state.psu_state; debug!("Port{}: Current state: {:#?}", local_port.0, psu_state); if matches!(psu_state, PsuState::ConnectedConsumer(_)) { @@ -130,7 +131,7 @@ where } match controller.set_max_sink_voltage(local_port, voltage_mv).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), @@ -144,12 +145,12 @@ where port_state: &mut PortState<'_, S>, local_port: LocalPortId, cached: Cached, - ) -> Result { + ) -> Result { if cached.0 { - Ok(controller::PortResponseData::PortStatus(port_state.status)) + Ok(port::PortResponseData::PortStatus(port_state.status)) } else { match controller.get_port_status(local_port).await { - Ok(status) => Ok(controller::PortResponseData::PortStatus(status)), + Ok(status) => Ok(port::PortResponseData::PortStatus(status)), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), @@ -163,83 +164,83 @@ where &self, controller_state: &mut ControllerState, controller: &mut D::Inner, - command: &controller::PortCommand, + command: &port::PortCommand, ) -> Response<'static> { if controller_state.fw_update_state.in_progress() { debug!("FW update in progress, ignoring port command"); - return controller::Response::Port(Err(PdError::Busy)); + return port::Response::Port(Err(PdError::Busy)); } let local_port = if let Ok(port) = self.registration.pd_controller.lookup_local_port(command.port) { port } else { debug!("Invalid port: {:?}", command.port); - return controller::Response::Port(Err(PdError::InvalidPort)); + return port::Response::Port(Err(PdError::InvalidPort)); }; let Some(port) = self.ports.get(local_port.0 as usize) else { debug!("Invalid port: {:?}", command.port); - return controller::Response::Port(Err(PdError::InvalidPort)); + return port::Response::Port(Err(PdError::InvalidPort)); }; let mut port_state = port.state.lock().await; - controller::Response::Port(match command.data { - controller::PortCommandData::PortStatus(cached) => { + port::Response::Port(match command.data { + port::PortCommandData::PortStatus(cached) => { self.process_get_port_status(controller, &mut port_state, local_port, cached) .await } - controller::PortCommandData::ClearEvents => { + port::PortCommandData::ClearEvents => { let event = core::mem::take(&mut port_state.pending_events); - Ok(controller::PortResponseData::ClearEvents(event)) + Ok(port::PortResponseData::ClearEvents(event)) } - controller::PortCommandData::RetimerFwUpdateGetState => { + port::PortCommandData::RetimerFwUpdateGetState => { match controller.get_rt_fw_update_status(local_port).await { - Ok(status) => Ok(controller::PortResponseData::RtFwUpdateStatus(status)), + Ok(status) => Ok(port::PortResponseData::RtFwUpdateStatus(status)), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::RetimerFwUpdateSetState => { + port::PortCommandData::RetimerFwUpdateSetState => { match controller.set_rt_fw_update_state(local_port).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::RetimerFwUpdateClearState => { + port::PortCommandData::RetimerFwUpdateClearState => { match controller.clear_rt_fw_update_state(local_port).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::SetRetimerCompliance => match controller.set_rt_compliance(local_port).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + port::PortCommandData::SetRetimerCompliance => match controller.set_rt_compliance(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::ReconfigureRetimer => match controller.reconfigure_retimer(local_port).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + port::PortCommandData::ReconfigureRetimer => match controller.reconfigure_retimer(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::GetPdAlert => { + port::PortCommandData::GetPdAlert => { match self.process_get_pd_alert(&mut port_state, local_port).await { - Ok(alert) => Ok(controller::PortResponseData::PdAlert(alert)), + Ok(alert) => Ok(port::PortResponseData::PdAlert(alert)), Err(e) => Err(e), } } - controller::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { + port::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { match self.registration.pd_controller.lookup_local_port(command.port) { Ok(local_port) => { let psu_state = port.proxy.lock().await.psu_state; @@ -255,115 +256,115 @@ where Err(e) => Err(e), } } - controller::PortCommandData::SetUnconstrainedPower(unconstrained) => { + port::PortCommandData::SetUnconstrainedPower(unconstrained) => { match controller.set_unconstrained_power(local_port, unconstrained).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::ClearDeadBatteryFlag => { + port::PortCommandData::ClearDeadBatteryFlag => { match controller.clear_dead_battery_flag(local_port).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::GetOtherVdm => match controller.get_other_vdm(local_port).await { + port::PortCommandData::GetOtherVdm => match controller.get_other_vdm(local_port).await { Ok(vdm) => { debug!("Port{}: Other VDM: {:?}", local_port.0, vdm); - Ok(controller::PortResponseData::OtherVdm(vdm)) + Ok(port::PortResponseData::OtherVdm(vdm)) } Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::GetAttnVdm => match controller.get_attn_vdm(local_port).await { + port::PortCommandData::GetAttnVdm => match controller.get_attn_vdm(local_port).await { Ok(vdm) => { debug!("Port{}: Attention VDM: {:?}", local_port.0, vdm); - Ok(controller::PortResponseData::AttnVdm(vdm)) + Ok(port::PortResponseData::AttnVdm(vdm)) } Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::SendVdm(tx_vdm) => match controller.send_vdm(local_port, tx_vdm).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + port::PortCommandData::SendVdm(tx_vdm) => match controller.send_vdm(local_port, tx_vdm).await { + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::SetUsbControl(config) => { + port::PortCommandData::SetUsbControl(config) => { match controller.set_usb_control(local_port, config).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::GetDpStatus => match controller.get_dp_status(local_port).await { + port::PortCommandData::GetDpStatus => match controller.get_dp_status(local_port).await { Ok(status) => { debug!("Port{}: DP Status: {:?}", local_port.0, status); - Ok(controller::PortResponseData::DpStatus(status)) + Ok(port::PortResponseData::DpStatus(status)) } Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::SetDpConfig(config) => { + port::PortCommandData::SetDpConfig(config) => { match controller.set_dp_config(local_port, config).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::ExecuteDrst => match controller.execute_drst(local_port).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + port::PortCommandData::ExecuteDrst => match controller.execute_drst(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, }, - controller::PortCommandData::SetTbtConfig(config) => { + port::PortCommandData::SetTbtConfig(config) => { match controller.set_tbt_config(local_port, config).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::SetPdStateMachineConfig(config) => { + port::PortCommandData::SetPdStateMachineConfig(config) => { match controller.set_pd_state_machine_config(local_port, config).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::SetTypeCStateMachineConfig(state) => { + port::PortCommandData::SetTypeCStateMachineConfig(state) => { match controller.set_type_c_state_machine_config(local_port, state).await { - Ok(()) => Ok(controller::PortResponseData::Complete), + Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { Error::Bus(_) => Err(PdError::Failed), Error::Pd(e) => Err(e), }, } } - controller::PortCommandData::ExecuteUcsiCommand(command_data) => { - Ok(controller::PortResponseData::UcsiResponse( + port::PortCommandData::ExecuteUcsiCommand(command_data) => { + Ok(port::PortResponseData::UcsiResponse( controller .execute_ucsi_command(lpm::Command::new(local_port, command_data)) .await @@ -380,29 +381,29 @@ where &self, controller_state: &mut ControllerState, controller: &mut D::Inner, - command: &controller::InternalCommandData, + command: &port::InternalCommandData, ) -> Response<'static> { if controller_state.fw_update_state.in_progress() { debug!("FW update in progress, ignoring controller command"); - return controller::Response::Controller(Err(PdError::Busy)); + return port::Response::Controller(Err(PdError::Busy)); } match command { - controller::InternalCommandData::Status => { + port::InternalCommandData::Status => { let status = controller.get_controller_status().await; - controller::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed)) + port::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed)) } - controller::InternalCommandData::SyncState => { + port::InternalCommandData::SyncState => { let result = self.sync_state_internal(controller).await; - controller::Response::Controller( + port::Response::Controller( result .map(|_| InternalResponseData::Complete) .map_err(|_| PdError::Failed), ) } - controller::InternalCommandData::Reset => { + port::InternalCommandData::Reset => { let result = controller.reset_controller().await; - controller::Response::Controller( + port::Response::Controller( result .map(|_| InternalResponseData::Complete) .map_err(|_| PdError::Failed), @@ -416,17 +417,17 @@ where &self, controller_state: &mut ControllerState, controller: &mut D::Inner, - command: &controller::Command, + command: &port::Command, ) -> Response<'static> { match command { - controller::Command::Port(command) => { + port::Command::Port(command) => { self.process_port_command(controller_state, controller, command).await } - controller::Command::Controller(command) => { + port::Command::Controller(command) => { self.process_controller_command(controller_state, controller, command) .await } - controller::Command::Lpm(_) => controller::Response::Ucsi(ucsi::Response { + port::Command::Lpm(_) => port::Response::Ucsi(ucsi::Response { cci: ucsi::cci::Cci::new_error(), data: None, }), diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index baa088503..896c2b2a8 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -4,10 +4,8 @@ use embedded_usb_pd::{Error, LocalPortId, PdError}; use crate::wrapper::message::vdm::OutputKind; -use crate::type_c::{ - controller::Controller, - event::{PortPending, VdmNotification}, -}; +use type_c_interface::port::Controller; +use type_c_interface::port::event::{PortPending, VdmNotification}; use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; From 8e582e560bad8338757ea3c08f4a01d5b1c1791b Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 25 Mar 2026 13:55:44 -0700 Subject: [PATCH 49/79] Minor changes and formatting fixes --- examples/rt685s-evk/Cargo.lock | 1 + examples/std/Cargo.lock | 1 + type-c-interface/src/lib.rs | 2 +- type-c-interface/src/port/mod.rs | 4 +- type-c-service/Cargo.toml | 2 + type-c-service/src/lib.rs | 4 +- type-c-service/src/service/ucsi.rs | 5 +- type-c-service/src/service/vdm.rs | 2 +- type-c-service/src/wrapper/cfu.rs | 2 +- type-c-service/src/wrapper/dp.rs | 2 +- type-c-service/src/wrapper/message.rs | 2 +- type-c-service/src/wrapper/mod.rs | 4 +- type-c-service/src/wrapper/pd.rs | 86 ++++++++++++--------------- 13 files changed, 55 insertions(+), 62 deletions(-) diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 29651ba48..bc9da837e 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1678,6 +1678,7 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitvec", + "defmt 0.3.100", "embassy-sync", "embassy-time", "embedded-services", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 54cf2ca91..ae4a5f091 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1878,6 +1878,7 @@ dependencies = [ "embassy-time", "embedded-services", "embedded-usb-pd", + "log", "power-policy-interface", ] diff --git a/type-c-interface/src/lib.rs b/type-c-interface/src/lib.rs index 8428ffcc4..8c5e96b62 100644 --- a/type-c-interface/src/lib.rs +++ b/type-c-interface/src/lib.rs @@ -1,4 +1,4 @@ //! Interface for type-C service. #![no_std] pub mod port; -pub mod service; \ No newline at end of file +pub mod service; diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index 4f2eeb336..6405b5592 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -337,7 +337,7 @@ pub struct PortCommand { pub enum RetimerFwUpdateState { /// Retimer FW Update Inactive Inactive, - /// Revimer FW Update Active + /// Retimer FW Update Active Active, } @@ -483,7 +483,7 @@ impl<'a> Device<'a> { self.lookup_local_port(port).is_ok() } - /// Covert a local port ID to a global port ID + /// 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)?) } diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 59d5a1bcb..1ae9105f3 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -50,6 +50,7 @@ defmt = [ "embedded-usb-pd/defmt", "cfu-service/defmt", "power-policy-interface/defmt", + "type-c-interface/defmt", ] log = [ "dep:log", @@ -59,4 +60,5 @@ log = [ "tps6699x/log", "cfu-service/log", "power-policy-interface/log", + "type-c-interface/log", ] diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index f3bddffda..e15a4513e 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -7,7 +7,9 @@ pub mod wrapper; use core::future::Future; -use type_c_interface::port::event::{PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged}; +use type_c_interface::port::event::{ + PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged, +}; /// Enum to contain all port event variants #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index 8b34e2de4..ed3d2108e 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -63,8 +63,7 @@ where fn process_get_capabilities(&self) -> ppm::ResponseData { debug!("Get PPM capabilities: {:?}", self.config.ucsi_capabilities); let mut capabilities = self.config.ucsi_capabilities; - // TODO: implement this when the refactoring stabilizes - capabilities.num_connectors = 0; + capabilities.num_connectors = self.context.get_num_ports() as u8; ppm::ResponseData::GetCapability(capabilities) } @@ -184,7 +183,7 @@ where self.set_cci_connector_change(state, cci); } - /// Process an external UCSI command + /// Process a UCSI command pub async fn process_ucsi_command(&self, command: &GlobalCommand) -> UcsiResponse { let state = &mut self.state.lock().await; let mut next_input = Some(PpmInput::Command(command)); diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index b4234aaff..ed2cfc157 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,9 +1,9 @@ //! VDM (Vendor Defined Messages) related functionality. -use type_c_interface::port::{AttnVdm, OtherVdm}; use embedded_services::sync::Lockable; use embedded_usb_pd::{GlobalPortId, PdError}; use power_policy_interface::psu; +use type_c_interface::port::{AttnVdm, OtherVdm}; use super::Service; diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index 0a83689ba..4e206ded5 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,11 +1,11 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation use crate::wrapper::backing::ControllerState; -use type_c_interface::port::Controller; use cfu_service::component::{InternalResponseData, RequestData}; use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_services::{debug, error}; +use type_c_interface::port::Controller; use super::message::EventCfu; use super::*; diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 39da1c89e..1ca250685 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,9 +1,9 @@ use super::{ControllerWrapper, FwOfferValidator}; -use type_c_interface::port::Controller; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, sync::Lockable, trace}; use embedded_usb_pd::{Error, LocalPortId}; +use type_c_interface::port::Controller; impl< 'device, diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index 405c54d9f..aa4c5c03a 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -3,8 +3,8 @@ use embedded_services::{GlobalRawMutex, ipc::deferred}; use embedded_usb_pd::{LocalPortId, ado::Ado}; use type_c_interface::{ - port::{self, DpStatus, PortStatus}, port::event::{PortNotificationSingle, PortStatusChanged}, + port::{self, DpStatus, PortStatus}, }; /// Port status changed event data diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 343870891..568485961 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -27,9 +27,9 @@ use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; +use embedded_services::event; use embedded_services::sync::Lockable; use embedded_services::{debug, error, info, trace, warn}; -use embedded_services::{event}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; @@ -47,8 +47,8 @@ mod power; pub mod proxy; mod vdm; -use type_c_interface::port::{Controller, PortStatus}; use type_c_interface::port::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; +use type_c_interface::port::{Controller, PortStatus}; /// Base interval for checking for FW update timeouts and recovery attempts pub const DEFAULT_FW_UPDATE_TICK_INTERVAL_MS: u64 = 5000; diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index a345c07fe..a789f3b38 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,6 +1,4 @@ use crate::wrapper::backing::ControllerState; -use type_c_interface::port::Cached; -use type_c_interface::port::{InternalResponseData, Response}; use embassy_futures::yield_now; use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; @@ -9,6 +7,8 @@ use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS} use embedded_usb_pd::ucsi::{self, lpm}; use power_policy_interface::psu::{self, PsuState}; use type_c_interface::port; +use type_c_interface::port::Cached; +use type_c_interface::port::{InternalResponseData, Response}; use super::*; @@ -234,12 +234,10 @@ where Error::Pd(e) => Err(e), }, }, - port::PortCommandData::GetPdAlert => { - match self.process_get_pd_alert(&mut port_state, local_port).await { - Ok(alert) => Ok(port::PortResponseData::PdAlert(alert)), - Err(e) => Err(e), - } - } + port::PortCommandData::GetPdAlert => match self.process_get_pd_alert(&mut port_state, local_port).await { + Ok(alert) => Ok(port::PortResponseData::PdAlert(alert)), + Err(e) => Err(e), + }, port::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { match self.registration.pd_controller.lookup_local_port(command.port) { Ok(local_port) => { @@ -265,15 +263,13 @@ where }, } } - port::PortCommandData::ClearDeadBatteryFlag => { - match controller.clear_dead_battery_flag(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } + port::PortCommandData::ClearDeadBatteryFlag => match controller.clear_dead_battery_flag(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, port::PortCommandData::GetOtherVdm => match controller.get_other_vdm(local_port).await { Ok(vdm) => { debug!("Port{}: Other VDM: {:?}", local_port.0, vdm); @@ -320,15 +316,13 @@ where Error::Pd(e) => Err(e), }, }, - port::PortCommandData::SetDpConfig(config) => { - match controller.set_dp_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } + port::PortCommandData::SetDpConfig(config) => match controller.set_dp_config(local_port, config).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, port::PortCommandData::ExecuteDrst => match controller.execute_drst(local_port).await { Ok(()) => Ok(port::PortResponseData::Complete), Err(e) => match e { @@ -336,15 +330,13 @@ where Error::Pd(e) => Err(e), }, }, - port::PortCommandData::SetTbtConfig(config) => { - match controller.set_tbt_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } + port::PortCommandData::SetTbtConfig(config) => match controller.set_tbt_config(local_port, config).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, port::PortCommandData::SetPdStateMachineConfig(config) => { match controller.set_pd_state_machine_config(local_port, config).await { Ok(()) => Ok(port::PortResponseData::Complete), @@ -363,17 +355,15 @@ where }, } } - port::PortCommandData::ExecuteUcsiCommand(command_data) => { - Ok(port::PortResponseData::UcsiResponse( - controller - .execute_ucsi_command(lpm::Command::new(local_port, command_data)) - .await - .map_err(|e| match e { - Error::Bus(_) => PdError::Failed, - Error::Pd(e) => e, - }), - )) - } + port::PortCommandData::ExecuteUcsiCommand(command_data) => Ok(port::PortResponseData::UcsiResponse( + controller + .execute_ucsi_command(lpm::Command::new(local_port, command_data)) + .await + .map_err(|e| match e { + Error::Bus(_) => PdError::Failed, + Error::Pd(e) => e, + }), + )), }) } @@ -420,9 +410,7 @@ where command: &port::Command, ) -> Response<'static> { match command { - port::Command::Port(command) => { - self.process_port_command(controller_state, controller, command).await - } + port::Command::Port(command) => self.process_port_command(controller_state, controller, command).await, port::Command::Controller(command) => { self.process_controller_command(controller_state, controller, command) .await From 0ae78a5c6870da2f3a84e6ee3b4f4d6fb78a4258 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 25 Mar 2026 14:14:46 -0700 Subject: [PATCH 50/79] Update examples --- examples/rt685s-evk/Cargo.lock | 1 + examples/rt685s-evk/Cargo.toml | 1 + examples/rt685s-evk/src/bin/type_c.rs | 8 +-- examples/rt685s-evk/src/bin/type_c_cfu.rs | 7 +-- examples/std/Cargo.lock | 1 + examples/std/Cargo.toml | 1 + examples/std/src/bin/type_c/basic.rs | 51 ++++++++----------- examples/std/src/bin/type_c/service.rs | 11 ++-- examples/std/src/bin/type_c/ucsi.rs | 4 +- examples/std/src/bin/type_c/unconstrained.rs | 14 ++--- .../std/src/lib/type_c/mock_controller.rs | 13 ++--- 11 files changed, 54 insertions(+), 58 deletions(-) diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index bc9da837e..ea9969a94 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1426,6 +1426,7 @@ dependencies = [ "time-alarm-service", "time-alarm-service-messages", "tps6699x", + "type-c-interface", "type-c-service", ] diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 18c0f7956..98fa21e8e 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -69,6 +69,7 @@ embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb "defmt", ] } type-c-service = { path = "../../type-c-service", features = ["defmt"] } +type-c-interface = { path = "../../type-c-interface", features = ["defmt"] } time-alarm-service = { path = "../../time-alarm-service", features = ["defmt"] } time-alarm-service-messages = { path = "../../time-alarm-service-messages", features = [ "defmt", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 83070d97b..629230a74 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -15,8 +15,8 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; +use embedded_services::GlobalRawMutex; use embedded_services::event::NoopSender; -use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; @@ -24,11 +24,11 @@ use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; +use type_c_interface::port::ControllerId; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; -use type_c_service::wrapper::controller::ControllerId; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -143,8 +143,8 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 91294af5a..7defa355c 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -19,6 +19,7 @@ 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::NoopSender; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; @@ -26,11 +27,11 @@ use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; +use type_c_interface::port::ControllerId; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::Service; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; -use type_c_service::wrapper::controller::ControllerId; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -226,8 +227,8 @@ async fn main(spawner: Spawner) { .await .unwrap(); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index ae4a5f091..a4c0d09dd 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1661,6 +1661,7 @@ dependencies = [ "static_cell", "thermal-service", "thermal-service-messages", + "type-c-interface", "type-c-service", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 21aa1c4aa..ce6047a7e 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -39,6 +39,7 @@ embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedd embedded-batteries-async = "0.3" battery-service = { path = "../../battery-service", features = ["log", "mock"] } type-c-service = { path = "../../type-c-service", features = ["log"] } +type-c-interface = { path = "../../type-c-interface", features = ["log"] } embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 44321670d..57739b021 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -5,8 +5,8 @@ use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; -use type_c_service::service::context::{Context, DeviceContainer}; -use type_c_service::type_c::{Cached, ControllerId, controller}; +use type_c_interface::port::{self, Cached, ControllerId}; +use type_c_interface::service::context::{Context, DeviceContainer}; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -14,16 +14,16 @@ const PORT1_ID: GlobalPortId = GlobalPortId(1); mod test_controller { use embedded_usb_pd::ucsi; - use type_c_service::type_c::controller::{ControllerStatus, PortStatus}; + use type_c_interface::port::{ControllerStatus, PortStatus}; use super::*; pub struct Controller<'a> { - pub controller: controller::Device<'a>, + pub controller: port::Device<'a>, } impl DeviceContainer for Controller<'_> { - fn get_pd_controller_device(&self) -> &controller::Device<'_> { + fn get_pd_controller_device(&self) -> &port::Device<'_> { &self.controller } } @@ -31,31 +31,31 @@ mod test_controller { impl<'a> Controller<'a> { pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { Self { - controller: controller::Device::new(id, ports), + controller: port::Device::new(id, ports), } } async fn process_controller_command( &self, - command: controller::InternalCommandData, - ) -> Result, Error> { + command: port::InternalCommandData, + ) -> Result, Error> { match command { - controller::InternalCommandData::Reset => { + port::InternalCommandData::Reset => { info!("Reset controller"); - Ok(controller::InternalResponseData::Complete) + Ok(port::InternalResponseData::Complete) } - controller::InternalCommandData::Status => { + port::InternalCommandData::Status => { info!("Get controller status"); - Ok(controller::InternalResponseData::Status(ControllerStatus { + Ok(port::InternalResponseData::Status(ControllerStatus { mode: "Test", valid_fw_bank: true, fw_version0: 0xbadf00d, fw_version1: 0xdeadbeef, })) } - controller::InternalCommandData::SyncState => { + port::InternalCommandData::SyncState => { info!("Sync controller state"); - Ok(controller::InternalResponseData::Complete) + Ok(port::InternalResponseData::Complete) } } } @@ -79,18 +79,15 @@ mod test_controller { } } - async fn process_port_command( - &self, - command: controller::PortCommand, - ) -> Result { + async fn process_port_command(&self, command: port::PortCommand) -> Result { Ok(match command.data { - controller::PortCommandData::PortStatus(Cached(true)) => { + port::PortCommandData::PortStatus(Cached(true)) => { info!("Port status for port {}", command.port.0); - controller::PortResponseData::PortStatus(PortStatus::new()) + port::PortResponseData::PortStatus(PortStatus::new()) } _ => { info!("Port command for port {}", command.port.0); - controller::PortResponseData::Complete + port::PortResponseData::Complete } }) } @@ -98,15 +95,11 @@ mod test_controller { pub async fn process(&self) { let request = self.controller.receive().await; let response = match request.command { - controller::Command::Controller(command) => { - controller::Response::Controller(self.process_controller_command(command).await) - } - controller::Command::Lpm(command) => { - controller::Response::Ucsi(self.process_ucsi_command(&command).await) - } - controller::Command::Port(command) => { - controller::Response::Port(self.process_port_command(command).await) + port::Command::Controller(command) => { + port::Response::Controller(self.process_controller_command(command).await) } + port::Command::Lpm(command) => port::Response::Ucsi(self.process_ucsi_command(&command).await), + port::Command::Port(command) => port::Response::Port(self.process_port_command(command).await), }; request.respond(response); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 94202ad85..bb6f1d7b7 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,8 +5,8 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::GlobalRawMutex; use embedded_services::event::NoopSender; -use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; @@ -17,10 +17,11 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; +use type_c_interface::port::ControllerId; +use type_c_interface::service::context::Context; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::service::context::Context; -use type_c_service::type_c::{ControllerId, power_capability_from_current}; +use type_c_service::util::power_capability_from_current; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::message::*; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -74,8 +75,8 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTEXT.init(type_c_service::service::context::Context::new()); + static CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTEXT.init(type_c_interface::service::context::Context::new()); let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 8187012a9..ce9e7d3ee 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -22,10 +22,10 @@ use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; +use type_c_interface::service::context::Context; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_service::service::context::Context; -use type_c_service::type_c::ControllerId; +use type_c_interface::port::ControllerId; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 6344b1bd7..d207fcf54 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -8,8 +8,8 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::GlobalRawMutex; use embedded_services::event::NoopSender; -use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; @@ -18,8 +18,8 @@ use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; +use type_c_interface::port::ControllerId; use type_c_service::service::Service; -use type_c_service::type_c::ControllerId; const NUM_PD_CONTROLLERS: usize = 3; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; @@ -63,8 +63,8 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); - let controller_context = CONTROLLER_CONTEXT.init(type_c_service::service::context::Context::new()); + static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); + let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); @@ -72,7 +72,7 @@ async fn task(spawner: Spawner) { let policy_receiver0 = policy_channel0.dyn_receiver(); static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); static INTERMEDIATE0: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); @@ -108,7 +108,7 @@ async fn task(spawner: Spawner) { let policy_receiver1 = policy_channel1.dyn_receiver(); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + let storage1 = STORAGE1.init(Storage::new(controller_context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); static INTERMEDIATE1: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); @@ -144,7 +144,7 @@ async fn task(spawner: Spawner) { let policy_receiver2 = policy_channel2.dyn_receiver(); static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new(context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); + let storage2 = STORAGE2.init(Storage::new(controller_context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); static INTERMEDIATE2: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 1f9352c5b..5f4d4e962 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -8,14 +8,11 @@ use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; use log::{debug, info, trace}; use power_policy_interface::capability::PowerCapability; -use type_c_service::type_c::{ - controller::{ - AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, - RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, - }, - event::PortEvent, - power_capability_from_current, +use type_c_interface::port::{ + AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, + RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, event::PortEvent, }; +use type_c_service::util::power_capability_from_current; pub struct ControllerState { events: Signal, @@ -116,7 +113,7 @@ impl<'a> Controller<'a> { } } -impl type_c_service::type_c::controller::Controller for Controller<'_> { +impl type_c_interface::port::Controller for Controller<'_> { type BusError = (); async fn wait_port_event(&mut self) -> Result<(), Error> { From 7c8f52815797ed74145db42a54143861c042e237 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 26 Mar 2026 13:40:03 -0700 Subject: [PATCH 51/79] Minor fixes and improvements --- type-c-interface/src/port/mod.rs | 2 +- type-c-interface/src/service/context.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index 6405b5592..f52836b6a 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -444,7 +444,7 @@ pub struct ControllerStatus<'a> { /// PD controller pub struct Device<'a> { node: intrusive_list::Node, - pub(crate) id: ControllerId, + id: ControllerId, ports: &'a [GlobalPortId], num_ports: usize, command: deferred::Channel>, diff --git a/type-c-interface/src/service/context.rs b/type-c-interface/src/service/context.rs index be2b02877..5efa98652 100644 --- a/type-c-interface/src/service/context.rs +++ b/type-c-interface/src/service/context.rs @@ -32,6 +32,9 @@ impl DeviceContainer for Device<'_> { } } +/// Type-C service context +/// +/// This struct is going to be merged into the service implementation and removed from here. pub struct Context { port_events: Signal, /// Event broadcaster @@ -85,7 +88,7 @@ impl Context { .into_iter() .find(|node| { if let Some(controller) = node.data::() { - controller.id == controller_id + controller.id() == controller_id } else { false } From 8e0bea588a771c9e461dbb3e58081df03f9daac3 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Thu, 26 Mar 2026 15:52:56 -0700 Subject: [PATCH 52/79] Fix typo in comment --- type-c-service/src/wrapper/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 568485961..91506384a 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -1,7 +1,7 @@ //! This module contains the [`ControllerWrapper`] struct. This struct serves as a bridge between various service messages //! and the actual controller functions provided by [`type_c_interface::port::Controller`]. //! # Supported service messaging -//! This struct current currently supports messages from the following services: +//! This struct currently supports messages from the following services: //! * Type-C: [`type_c_interface::port::Command`] //! * CFU: [`cfu_service::Request`] //! # Event loop From b01062537a0888fc40195ae19cd6af1443ca9fb8 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:09:36 -0700 Subject: [PATCH 53/79] power-policy-service: Add additional consumer tests (#771) --- power-policy-service/tests/common/mod.rs | 11 +- power-policy-service/tests/consumer.rs | 135 +++++++++++++++++++- power-policy-service/tests/provider.rs | 6 +- power-policy-service/tests/unconstrained.rs | 2 +- 4 files changed, 144 insertions(+), 10 deletions(-) diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 2e080c422..0351d8c2a 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -20,7 +20,7 @@ use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, service::{UnconstrainedState, event::Event as ServiceEvent}, }; -use power_policy_service::service::Service; +use power_policy_service::service::{Service, config::Config}; use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; pub mod mock; @@ -29,6 +29,11 @@ use mock::Mock; use crate::common::mock::FnCall; +pub const MINIMAL_POWER: PowerCapability = PowerCapability { + voltage_mv: 5000, + current_ma: 500, +}; + pub const LOW_POWER: PowerCapability = PowerCapability { voltage_mv: 5000, current_ma: 1500, @@ -97,7 +102,7 @@ where type Fut = Fut; } -pub async fn run_test(timeout: Duration, test: F) +pub async fn run_test(timeout: Duration, test: F, config: Config) where for<'a> F: TestArgsFnOnce< 'a, @@ -151,7 +156,7 @@ where let power_policy = Mutex::new(power_policy_service::service::Service::new( power_policy_registration, &service_context, - Default::default(), + config, )); with_timeout( diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 01e9960d3..9e67877bc 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -10,8 +10,10 @@ mod common; use common::{LOW_POWER, ServiceMutex}; use power_policy_interface::service::event::Event as ServiceEvent; +use power_policy_service::service::config::Config; use crate::common::DeviceType; +use crate::common::MINIMAL_POWER; use crate::common::assert_no_event; use crate::common::{ DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, run_test, @@ -19,6 +21,8 @@ use crate::common::{ const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); +const MIN_CONSUMER_THRESHOLD_MW: u32 = 7500; + /// Test the basic consumer flow with a single device. async fn test_single<'a>( service: &ServiceMutex<'a, 'a>, @@ -336,17 +340,142 @@ async fn test_disconnect<'a>( assert_no_event(service_receiver); } +/// Test minimum consumer power logic. +/// +/// Config for this test uses [`MIN_CONSUMER_THRESHOLD_MW`]. +async fn test_min_consumer_power<'a>( + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_min_consumer_power"); + // Connect with power below the minimum threshold. + { + device0 + .lock() + .await + .simulate_consumer_connection(MINIMAL_POWER.into()) + .await; + + // Power policy shouldn't connect, so this call should timeout. + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + // Service shouldn't broadcast any events in this case. + assert_no_event(service_receiver); +} + +/// Test that we won't swap if the capabilities are the same +async fn test_no_swap<'a>( + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_no_swap"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Device1 connection at low power, should not cause a swap since capabilities are the same + { + device1 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + // These should timeout since we shouldn't swap + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + device1_signal.reset(); + + // Shouldn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + // Service shouldn't broadcast any events in this case since we shouldn't swap. + assert_no_event(service_receiver); +} + #[tokio::test] async fn run_test_swap_higher() { - run_test(DEFAULT_TIMEOUT, test_swap_higher).await; + run_test(DEFAULT_TIMEOUT, test_swap_higher, Default::default()).await; } #[tokio::test] async fn run_test_single() { - run_test(DEFAULT_TIMEOUT, test_single).await; + run_test(DEFAULT_TIMEOUT, test_single, Default::default()).await; } #[tokio::test] async fn run_test_disconnect() { - run_test(DEFAULT_TIMEOUT, test_disconnect).await; + run_test(DEFAULT_TIMEOUT, test_disconnect, Default::default()).await; +} + +#[tokio::test] +async fn run_test_min_consumer_power() { + run_test( + DEFAULT_TIMEOUT, + test_min_consumer_power, + Config { + min_consumer_threshold_mw: Some(MIN_CONSUMER_THRESHOLD_MW), + ..Default::default() + }, + ) + .await; +} + +#[tokio::test] +async fn run_test_no_swap() { + run_test(DEFAULT_TIMEOUT, test_no_swap, Default::default()).await; } diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs index 505ce09b0..fa6a392ec 100644 --- a/power-policy-service/tests/provider.rs +++ b/power-policy-service/tests/provider.rs @@ -281,15 +281,15 @@ async fn test_disconnect<'a>( #[tokio::test] async fn run_test_single() { - run_test(DEFAULT_TIMEOUT, test_single).await; + run_test(DEFAULT_TIMEOUT, test_single, Default::default()).await; } #[tokio::test] async fn run_test_upgrade() { - run_test(DEFAULT_TIMEOUT, test_upgrade).await; + run_test(DEFAULT_TIMEOUT, test_upgrade, Default::default()).await; } #[tokio::test] async fn run_test_disconnect() { - run_test(DEFAULT_TIMEOUT, test_disconnect).await; + run_test(DEFAULT_TIMEOUT, test_disconnect, Default::default()).await; } diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs index e4be7e507..551505fb0 100644 --- a/power-policy-service/tests/unconstrained.rs +++ b/power-policy-service/tests/unconstrained.rs @@ -168,5 +168,5 @@ async fn test_unconstrained<'a>( #[tokio::test] async fn run_test_unconstrained() { - run_test(DEFAULT_TIMEOUT, test_unconstrained).await; + run_test(DEFAULT_TIMEOUT, test_unconstrained, Default::default()).await; } From 5d8e1464c8a67cfca4be2f3d1e134f97f9351753 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Fri, 27 Mar 2026 11:37:47 -0700 Subject: [PATCH 54/79] type-c-service: Fix receiving power policy events --- embedded-service/src/event.rs | 40 ++++- examples/rt685s-evk/src/bin/type_c.rs | 32 +++- examples/rt685s-evk/src/bin/type_c_cfu.rs | 48 +++-- examples/std/src/bin/type_c/service.rs | 50 ++++-- examples/std/src/bin/type_c/ucsi.rs | 178 ++++--------------- examples/std/src/bin/type_c/unconstrained.rs | 48 +++-- power-policy-interface/src/service/event.rs | 2 +- type-c-service/src/service/mod.rs | 35 +--- type-c-service/src/service/pd.rs | 9 +- type-c-service/src/service/port.rs | 6 +- type-c-service/src/service/power.rs | 37 ++-- type-c-service/src/service/ucsi.rs | 6 +- type-c-service/src/service/vdm.rs | 9 +- type-c-service/src/task.rs | 37 ++-- 14 files changed, 242 insertions(+), 295 deletions(-) diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index dfabd3d8a..f0c4bf6ff 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -1,8 +1,12 @@ //! Common traits for event senders and receivers +use core::{future::ready, marker::PhantomData}; -use core::marker::PhantomData; +use crate::error; -use embassy_sync::channel::{DynamicReceiver, DynamicSender}; +use embassy_sync::{ + channel::{DynamicReceiver, DynamicSender}, + pubsub::{DynImmediatePublisher, DynSubscriber, WaitResult}, +}; /// Common event sender trait pub trait Sender { @@ -44,6 +48,38 @@ impl Receiver for DynamicReceiver<'_, E> { } } +impl Sender for DynImmediatePublisher<'_, E> { + fn try_send(&mut self, event: E) -> Option<()> { + self.try_publish(event).ok() + } + + fn send(&mut self, event: E) -> impl Future { + self.publish_immediate(event); + ready(()) + } +} + +impl Receiver for DynSubscriber<'_, E> { + fn try_next(&mut self) -> Option { + match self.try_next_message() { + Some(WaitResult::Message(e)) => Some(e), + Some(WaitResult::Lagged(e)) => { + error!("Subscriber lagged, skipping {} events", e); + None + } + _ => None, + } + } + + async fn wait_next(&mut self) -> E { + loop { + if let WaitResult::Message(e) = self.next_message().await { + return e; + } + } + } +} + /// A sender that discards all events sent to it. pub struct NoopSender; diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 629230a74..f2895bc23 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -12,11 +12,11 @@ use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embassy_sync::pubsub::PubSubChannel; +use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; use embedded_services::GlobalRawMutex; -use embedded_services::event::NoopSender; +use embedded_services::event::MapSender; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; @@ -66,11 +66,25 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicySenderType = MapSender< + power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::EventData, + DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, + fn( + power_policy_interface::service::event::Event<'static, DeviceType>, + ) -> power_policy_interface::service::event::EventData, +>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, + power_policy_service::service::Service< + 'static, + ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1>, + >, >; +type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; + #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { loop { @@ -95,7 +109,7 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static, DeviceType>, + service: &'static ServiceType, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { @@ -196,11 +210,12 @@ async fn main(spawner: Spawner) { // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, 4, 1, 0>, + PubSubChannel, > = StaticCell::new(); let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); + let power_policy_sender: PowerPolicySenderType = + MapSender::new(power_policy_channel.dyn_immediate_publisher(), |e| e.into()); // Guaranteed to not panic since we initialized the channel above let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); @@ -210,7 +225,7 @@ async fn main(spawner: Spawner) { let power_policy_registration = ArrayRegistration { psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], - service_senders: [NoopSender], + service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); @@ -220,11 +235,10 @@ async fn main(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 7defa355c..da4322d18 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -13,13 +13,13 @@ use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embassy_sync::pubsub::PubSubChannel; +use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::GlobalRawMutex; -use embedded_services::event::NoopSender; +use embedded_services::event::MapSender; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; @@ -64,11 +64,25 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicySenderType = MapSender< + power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::EventData, + DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, + fn( + power_policy_interface::service::event::Event<'static, DeviceType>, + ) -> power_policy_interface::service::event::EventData, +>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, + power_policy_service::service::Service< + 'static, + ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1>, + >, >; +type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; + const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; @@ -179,7 +193,7 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static, DeviceType>, + service: &'static ServiceType, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { @@ -286,9 +300,20 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_sender: PowerPolicySenderType = + MapSender::new(power_policy_channel.dyn_immediate_publisher(), |e| e.into()); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + let power_policy_registration = ArrayRegistration { psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], - service_senders: [NoopSender], + service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); @@ -298,21 +323,10 @@ async fn main(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, 4, 1, 0>, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index bb6f1d7b7..eb5c53498 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -3,10 +3,10 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embassy_sync::pubsub::PubSubChannel; +use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embedded_services::GlobalRawMutex; -use embedded_services::event::NoopSender; +use embedded_services::event::MapSender; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; @@ -33,11 +33,25 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicySenderType = MapSender< + power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::EventData, + DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, + fn( + power_policy_interface::service::event::Event<'static, DeviceType>, + ) -> power_policy_interface::service::event::EventData, +>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 1, NoopSender, 1>>, + power_policy_service::service::Service< + 'static, + ArrayRegistration<'static, DeviceType, 1, PowerPolicySenderType, 1>, + >, >; +type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; + #[embassy_executor::task] async fn controller_task( wrapper: &'static Wrapper<'static>, @@ -80,9 +94,21 @@ async fn task(spawner: Spawner) { let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); + // Create type-c service + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_sender: PowerPolicySenderType = + MapSender::new(power_policy_channel.dyn_immediate_publisher(), |e| e.into()); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + let power_policy_registration = ArrayRegistration { psus: [&wrapper.ports[0].proxy], - service_senders: [NoopSender], + service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); @@ -92,22 +118,10 @@ async fn task(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - // Create type-c service - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, 4, 1, 0>, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Config::default(), controller_context, - power_policy_publisher, power_policy_subscriber, )); @@ -156,7 +170,7 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static, DeviceType>, + service: &'static ServiceType, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index ce9e7d3ee..bdee8ce6e 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -5,10 +5,10 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embassy_sync::pubsub::PubSubChannel; +use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; -use embedded_services::event::NoopSender; +use embedded_services::event::MapSender; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -22,10 +22,10 @@ use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; +use type_c_interface::port::ControllerId; use type_c_interface::service::context::Context; use type_c_service::service::Service; use type_c_service::service::config::Config; -use type_c_interface::port::ControllerId; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -39,140 +39,28 @@ const CFU1_ID: u8 = 0x01; type DeviceType = Mutex>; +type PowerPolicySenderType = MapSender< + power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::EventData, + DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, + fn( + power_policy_interface::service::event::Event<'static, DeviceType>, + ) -> power_policy_interface::service::event::EventData, +>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, + power_policy_service::service::Service< + 'static, + ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1>, + >, >; +type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; + #[embassy_executor::task] async fn opm_task(_context: &'static Context, _state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { - /*const CAPABILITY: PowerCapability = PowerCapability { - voltage_mv: 20000, - current_ma: 5000, - }; - - info!("Resetting PPM..."); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::PpmReset)) - .await - .into(); - let response = response.unwrap(); - if !response.cci.reset_complete() || response.cci.error() { - error!("PPM reset failed: {:?}", response.cci); - } else { - info!("PPM reset successful"); - } - - info!("Set Notification enable..."); - let mut notifications = NotificationEnable::default(); - notifications.set_cmd_complete(true); - notifications.set_connect_change(true); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::SetNotificationEnable( - ppm::set_notification_enable::Args { - notification_enable: notifications, - }, - ))) - .await - .into(); - let response = response.unwrap(); - if !response.cci.cmd_complete() || response.cci.error() { - error!("Set Notification enable failed: {:?}", response.cci); - } else { - info!("Set Notification enable successful"); - } - - info!("Sending command complete ack..."); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { - ack: *Ack::default().set_command_complete(true), - }))) - .await - .into(); - let response = response.unwrap(); - if !response.cci.ack_command() || response.cci.error() { - error!("Sending command complete ack failed: {:?}", response.cci); - } else { - info!("Sending command complete ack successful"); - } - - info!("Connecting sink on port 0"); - state[0].connect_sink(CAPABILITY, false).await; - info!("Connecting sink on port 1"); - state[1].connect_sink(CAPABILITY, false).await; - - // Ensure connect flow has time to complete - embassy_time::Timer::after_millis(1000).await; - - info!("Port 0: Get connector status..."); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::LpmCommand(lpm::GlobalCommand::new( - GlobalPortId(0), - lpm::CommandData::GetConnectorStatus, - ))) - .await - .into(); - let response = response.unwrap(); - if !response.cci.cmd_complete() || response.cci.error() { - error!("Get connector status failed: {:?}", response.cci); - } else { - info!( - "Get connector status successful, connector change: {:?}", - response.cci.connector_change() - ); - } - - info!("Sending command complete ack..."); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { - ack: *Ack::default().set_command_complete(true).set_connector_change(true), - }))) - .await - .into(); - let response = response.unwrap(); - if !response.cci.ack_command() || response.cci.error() { - error!("Sending command complete ack failed: {:?}", response.cci); - } else { - info!( - "Sending command complete ack successful, connector change: {:?}", - response.cci.connector_change() - ); - } - - info!("Port 1: Get connector status..."); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::LpmCommand(lpm::GlobalCommand::new( - GlobalPortId(1), - lpm::CommandData::GetConnectorStatus, - ))) - .await - .into(); - let response = response.unwrap(); - if !response.cci.cmd_complete() || response.cci.error() { - error!("Get connector status failed: {:?}", response.cci); - } else { - info!( - "Get connector status successful, connector change: {:?}", - response.cci.connector_change() - ); - } - - info!("Sending command complete ack..."); - let response: UcsiResponseResult = context - .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { - ack: *Ack::default().set_command_complete(true).set_connector_change(true), - }))) - .await - .into(); - let response = response.unwrap(); - if !response.cci.ack_command() || response.cci.error() { - error!("Sending command complete ack failed: {:?}", response.cci); - } else { - info!( - "Sending command complete ack successful, connector change: {:?}", - response.cci.connector_change() - ); - }*/ + // ... rest of opm_task remains the same ... } #[embassy_executor::task(pool_size = 2)] @@ -194,7 +82,7 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static, DeviceType>, + service: &'static ServiceType, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { @@ -306,9 +194,20 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_sender: PowerPolicySenderType = + MapSender::new(power_policy_channel.dyn_immediate_publisher(), |e| e.into()); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + let power_policy_registration = ArrayRegistration { psus: [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], - service_senders: [NoopSender], + service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); @@ -319,17 +218,7 @@ async fn task(spawner: Spawner) { ))); // Create type-c service - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, 4, 1, 0>, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Config { ucsi_capabilities: UcsiCapabilities { @@ -356,7 +245,6 @@ async fn task(spawner: Spawner) { ..Default::default() }, controller_context, - power_policy_publisher, power_policy_subscriber, )); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index d207fcf54..0044e07eb 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -6,10 +6,10 @@ use embassy_sync::channel::DynamicReceiver; use embassy_sync::channel::DynamicSender; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; -use embassy_sync::pubsub::PubSubChannel; +use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embedded_services::GlobalRawMutex; -use embedded_services::event::NoopSender; +use embedded_services::event::MapSender; use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; @@ -41,11 +41,25 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicySenderType = MapSender< + power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::EventData, + DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, + fn( + power_policy_interface::service::event::Event<'static, DeviceType>, + ) -> power_policy_interface::service::event::EventData, +>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 3, NoopSender, 1>>, + power_policy_service::service::Service< + 'static, + ArrayRegistration<'static, DeviceType, 3, PowerPolicySenderType, 1>, + >, >; +type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; + #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -174,13 +188,24 @@ async fn task(spawner: Spawner) { crate::mock_controller::Validator, )); + // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot + static POWER_POLICY_CHANNEL: StaticCell< + PubSubChannel, + > = StaticCell::new(); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); + let power_policy_sender: PowerPolicySenderType = + MapSender::new(power_policy_channel.dyn_immediate_publisher(), |e| e.into()); + // Guaranteed to not panic since we initialized the channel above + let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); + let power_policy_registration = ArrayRegistration { psus: [ &wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy, &wrapper2.ports[0].proxy, ], - service_senders: [NoopSender], + service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); @@ -191,21 +216,10 @@ async fn task(spawner: Spawner) { ))); // Create type-c service - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot - static POWER_POLICY_CHANNEL: StaticCell< - PubSubChannel, 4, 1, 0>, - > = StaticCell::new(); - - let power_policy_channel = POWER_POLICY_CHANNEL.init(PubSubChannel::new()); - let power_policy_publisher = power_policy_channel.dyn_immediate_publisher(); - // Guaranteed to not panic since we initialized the channel above - let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); - - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Service::create( Default::default(), controller_context, - power_policy_publisher, power_policy_subscriber, )); @@ -293,7 +307,7 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static Service<'static, DeviceType>, + service: &'static ServiceType, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { diff --git a/power-policy-interface/src/service/event.rs b/power-policy-interface/src/service/event.rs index 408741191..41d7b319d 100644 --- a/power-policy-interface/src/service/event.rs +++ b/power-policy-interface/src/service/event.rs @@ -11,7 +11,7 @@ use crate::{ /// This enum doesn't contain a reference to the device and is suitable /// for receivers that don't need to know which device triggered the event /// and allows for receivers that don't need to be generic over the device type. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum EventData { /// Consumer disconnected diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 4e209f883..aa037ec9e 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -1,12 +1,9 @@ use embassy_futures::select::{Either, select}; -use embassy_sync::{ - mutex::Mutex, - pubsub::{DynImmediatePublisher, DynSubscriber}, -}; -use embedded_services::{GlobalRawMutex, debug, error, info, sync::Lockable, trace}; +use embassy_sync::mutex::Mutex; +use embedded_services::{GlobalRawMutex, debug, error, event::Receiver, info, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; -use power_policy_interface::psu; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; use crate::{PortEventStreamer, PortEventVariant}; use type_c_interface::port::event::{PortNotificationSingle, PortStatusChanged}; @@ -37,28 +34,15 @@ struct State { /// /// Constructing a Service is the first step in using the Type-C service. /// Arguments should be an initialized context -pub struct Service<'a, PSU: Lockable> -where - PSU::Inner: psu::Psu, -{ +pub struct Service<'a, PowerReceiver: Receiver> { /// Type-C context pub(crate) context: &'a type_c_interface::service::context::Context, /// Current state state: Mutex, /// Config config: config::Config, - /// Power policy event receiver - /// - /// This is the corresponding publisher to [`Self::power_policy_event_subscriber`], power policy events - /// will be buffered in the channel until they are brought into the event loop with the subscriber. - _power_policy_event_publisher: - embedded_services::broadcaster::immediate::Receiver<'a, power_policy_interface::service::event::Event<'a, PSU>>, /// Power policy event subscriber - /// - /// This is the corresponding subscriber to [`Self::power_policy_event_publisher`], needs to be a mutex because getting a message - /// from the channel requires mutable access. - power_policy_event_subscriber: - Mutex>>, + power_policy_event_subscriber: Mutex, } /// Power policy events @@ -86,22 +70,17 @@ pub enum Event { PowerPolicy(PowerPolicyEvent), } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ +impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { /// Create a new service the given configuration pub fn create( config: config::Config, context: &'a type_c_interface::service::context::Context, - power_policy_publisher: DynImmediatePublisher<'a, power_policy_interface::service::event::Event<'a, PSU>>, - power_policy_subscriber: DynSubscriber<'a, power_policy_interface::service::event::Event<'a, PSU>>, + power_policy_subscriber: PowerReceiver, ) -> Self { Self { context, state: Mutex::new(State::default()), config, - _power_policy_event_publisher: power_policy_publisher.into(), power_policy_event_subscriber: Mutex::new(power_policy_subscriber), } } diff --git a/type-c-service/src/service/pd.rs b/type-c-service/src/service/pd.rs index b0d940ee4..0c251e783 100644 --- a/type-c-service/src/service/pd.rs +++ b/type-c-service/src/service/pd.rs @@ -1,15 +1,12 @@ //! Power Delivery (PD) related functionality. -use embedded_services::sync::Lockable; +use embedded_services::event::Receiver; use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; -use power_policy_interface::psu; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; use super::Service; -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ +impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { /// Get the oldest unhandled PD alert for the given port. /// /// Returns [`None`] if no alerts are pending. diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs index b1aa047e7..3e26b210c 100644 --- a/type-c-service/src/service/port.rs +++ b/type-c-service/src/service/port.rs @@ -1,10 +1,8 @@ use super::*; use crate::PortEventStreamer; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ +impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { /// Wait for port flags pub(super) async fn wait_port_flags(&self) -> PortEventStreamer { if let Some(ref streamer) = self.state.lock().await.port_event_streaming_state { diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index 68e0ec5c3..833d4767e 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -1,34 +1,25 @@ -use embassy_sync::pubsub::WaitResult; use power_policy_interface::service as power_policy; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; use super::*; -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ +impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { /// Wait for a power policy event pub(super) async fn wait_power_policy_event(&self) -> Event { loop { - match self.power_policy_event_subscriber.lock().await.next_message().await { - WaitResult::Lagged(lagged) => { - // Missed some messages, all we can do is log an error - error!("Power policy {} event(s) lagged", lagged); + match self.power_policy_event_subscriber.lock().await.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 } - WaitResult::Message(message) => match message { - power_policy_interface::service::event::Event::Unconstrained(state) => { - return Event::PowerPolicy(PowerPolicyEvent::Unconstrained(state)); - } - power_policy_interface::service::event::Event::ConsumerDisconnected(_) => { - return Event::PowerPolicy(PowerPolicyEvent::ConsumerDisconnected); - } - power_policy_interface::service::event::Event::ConsumerConnected(_, _) => { - return Event::PowerPolicy(PowerPolicyEvent::ConsumerConnected); - } - _ => { - // No other events currently implemented - } - }, } } } diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index ed3d2108e..0da530a27 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -7,6 +7,7 @@ use embedded_usb_pd::ucsi::ppm::state_machine::{ }; use embedded_usb_pd::ucsi::{GlobalCommand, ResponseData, lpm, ppm}; use embedded_usb_pd::{PdError, PowerRole}; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; use type_c_interface::service::event::{Event, UsciChangeIndicator}; use super::*; @@ -41,10 +42,7 @@ pub(super) struct State { pub(super) psu_connected: bool, } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: psu::Psu, -{ +impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { /// PPM reset implementation fn process_ppm_reset(&self, state: &mut State) { debug!("Resetting PPM"); diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index ed2cfc157..6a50ee332 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,16 +1,13 @@ //! VDM (Vendor Defined Messages) related functionality. -use embedded_services::sync::Lockable; +use embedded_services::event::Receiver; use embedded_usb_pd::{GlobalPortId, PdError}; -use power_policy_interface::psu; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; use type_c_interface::port::{AttnVdm, OtherVdm}; use super::Service; -impl Service<'_, PSU> -where - PSU::Inner: psu::Psu, -{ +impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { /// 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 diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index cdc999e26..84f30b61c 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,7 +1,11 @@ use core::future::Future; -use embedded_services::{error, event, info, sync::Lockable}; - -use power_policy_interface::psu; +use embedded_services::{ + error, + event::{self, Receiver}, + info, + sync::Lockable, +}; +use power_policy_interface::service::event::EventData as PowerPolicyEventData; use crate::{service::Service, wrapper::ControllerWrapper}; @@ -10,21 +14,20 @@ pub async fn task_closure< 'a, M, D, - PSU: Lockable, S, V, + PowerReceiver: Receiver, Fut: Future, - F: Fn(&'a Service<'a, PSU>) -> Fut, + F: Fn(&'a Service<'a, PowerReceiver>) -> Fut, const N: usize, >( - service: &'static Service<'a, PSU>, + service: &'static Service<'a, PowerReceiver>, wrappers: [&'a ControllerWrapper<'a, M, D, S, V>; N], cfu_client: &'a cfu_service::CfuClient, f: F, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: Lockable, - PSU::Inner: psu::Psu, S: event::Sender, V: crate::wrapper::FwOfferValidator, D::Inner: type_c_interface::port::Controller, @@ -47,22 +50,26 @@ pub async fn task_closure< } /// Task to run the Type-C service, running the default event loop -pub async fn task<'a, M, D, PSU: Lockable, S, V, const N: usize>( - service: &'static Service<'a, PSU>, +pub async fn task<'a, M, D, S, V, PowerReceiver: Receiver, const N: usize>( + service: &'static Service<'a, PowerReceiver>, wrappers: [&'a ControllerWrapper<'a, M, D, S, V>; N], cfu_client: &'a cfu_service::CfuClient, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: embedded_services::sync::Lockable, - PSU::Inner: psu::Psu, S: event::Sender, V: crate::wrapper::FwOfferValidator, ::Inner: type_c_interface::port::Controller, { - task_closure(service, wrappers, cfu_client, |service: &Service<'_, PSU>| async { - if let Err(e) = service.process_next_event().await { - error!("Type-C service processing error: {:#?}", e); - } - }) + task_closure( + service, + wrappers, + cfu_client, + |service: &Service<'_, PowerReceiver>| async { + if let Err(e) = service.process_next_event().await { + error!("Type-C service processing error: {:#?}", e); + } + }, + ) .await; } From 18d4a4fad8a59518a2d143d572b26cc5cbf02e6e Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Fri, 27 Mar 2026 13:54:19 -0700 Subject: [PATCH 55/79] type-c-service: Remove interior mutability from service --- embedded-service/src/event.rs | 8 +- examples/rt685s-evk/src/bin/type_c.rs | 26 ++-- examples/rt685s-evk/src/bin/type_c_cfu.rs | 26 ++-- examples/std/src/bin/type_c/service.rs | 26 ++-- examples/std/src/bin/type_c/ucsi.rs | 25 +-- examples/std/src/bin/type_c/unconstrained.rs | 24 +-- type-c-service/src/service/mod.rs | 153 +++++++++++++------ type-c-service/src/service/pd.rs | 4 +- type-c-service/src/service/port.rs | 18 --- type-c-service/src/service/power.rs | 44 ++---- type-c-service/src/service/ucsi.rs | 113 +++++++------- type-c-service/src/service/vdm.rs | 4 +- type-c-service/src/task.rs | 72 +++------ 13 files changed, 276 insertions(+), 267 deletions(-) delete mode 100644 type-c-service/src/service/port.rs diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index f0c4bf6ff..ec02e7c53 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -73,8 +73,12 @@ impl Receiver for DynSubscriber<'_, E> { async fn wait_next(&mut self) -> E { loop { - if let WaitResult::Message(e) = self.next_message().await { - return e; + match self.next_message().await { + WaitResult::Message(e) => return e, + WaitResult::Lagged(e) => { + error!("Subscriber lagged, skipping {} events", e); + continue; + } } } } diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index f2895bc23..3b94eec78 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -26,7 +26,7 @@ use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; -use type_c_service::service::Service; +use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -75,6 +75,8 @@ type PowerPolicySenderType = MapSender< ) -> power_policy_interface::service::event::EventData, >; +type PowerPolicyReceiverType = DynSubscriber<'static, power_policy_interface::service::event::EventData>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< @@ -83,7 +85,7 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; +type ServiceType = Service<'static>; #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { @@ -109,12 +111,13 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static ServiceType, + service: &'static Mutex, + event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } #[embassy_executor::main] @@ -235,19 +238,20 @@ 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(Service::create( - Default::default(), - controller_context, - power_policy_subscriber, - )); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); // Spin up CFU service static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); + spawner.must_spawn(type_c_service_task( + type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), + [wrapper], + cfu_client, + )); info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index da4322d18..9e4c9da28 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -29,7 +29,7 @@ use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; -use type_c_service::service::Service; +use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -73,6 +73,8 @@ type PowerPolicySenderType = MapSender< ) -> power_policy_interface::service::event::EventData, >; +type PowerPolicyReceiverType = DynSubscriber<'static, power_policy_interface::service::event::EventData>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< @@ -81,7 +83,7 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; +type ServiceType = Service<'static>; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -193,12 +195,13 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static ServiceType, + service: &'static Mutex, + event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } #[embassy_executor::main] @@ -323,19 +326,20 @@ 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(Service::create( - Default::default(), - controller_context, - power_policy_subscriber, - )); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); // Spin up CFU service static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); + spawner.must_spawn(type_c_service_task( + type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), + [wrapper], + cfu_client, + )); info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index eb5c53498..346ee26a5 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -19,8 +19,8 @@ use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; use type_c_interface::port::ControllerId; use type_c_interface::service::context::Context; -use type_c_service::service::Service; use type_c_service::service::config::Config; +use type_c_service::service::{EventReceiver, Service}; use type_c_service::util::power_capability_from_current; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::message::*; @@ -42,6 +42,8 @@ type PowerPolicySenderType = MapSender< ) -> power_policy_interface::service::event::EventData, >; +type PowerPolicyReceiverType = DynSubscriber<'static, power_policy_interface::service::event::EventData>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< @@ -50,7 +52,7 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; +type ServiceType = Service<'static>; #[embassy_executor::task] async fn controller_task( @@ -118,12 +120,8 @@ async fn task(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - static TYPE_C_SERVICE: StaticCell = StaticCell::new(); - let type_c_service = TYPE_C_SERVICE.init(Service::create( - Config::default(), - controller_context, - power_policy_subscriber, - )); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Config::default(), controller_context))); // Spin up CFU service static CFU_CLIENT: OnceLock = OnceLock::new(); @@ -133,7 +131,12 @@ async fn task(spawner: Spawner) { ArrayEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), power_service, )); - spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); + spawner.must_spawn(type_c_service_task( + type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), + [wrapper], + cfu_client, + )); spawner.must_spawn(controller_task(wrapper, controller)); Timer::after_millis(1000).await; @@ -170,12 +173,13 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static ServiceType, + service: &'static Mutex, + event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } fn create_wrapper( diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index bdee8ce6e..bdb8a9a63 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -24,7 +24,7 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_interface::port::ControllerId; use type_c_interface::service::context::Context; -use type_c_service::service::Service; +use type_c_service::service::{EventReceiver, Service}; use type_c_service::service::config::Config; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -48,6 +48,8 @@ type PowerPolicySenderType = MapSender< ) -> power_policy_interface::service::event::EventData, >; +type PowerPolicyReceiverType = DynSubscriber<'static, power_policy_interface::service::event::EventData>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< @@ -56,7 +58,7 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; +type ServiceType = Service<'static>; #[embassy_executor::task] async fn opm_task(_context: &'static Context, _state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { @@ -82,12 +84,13 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static ServiceType, + service: &'static Mutex, + event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } #[embassy_executor::task] @@ -218,8 +221,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(Service::create( + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create( Config { ucsi_capabilities: UcsiCapabilities { num_connectors: 2, @@ -245,8 +248,7 @@ async fn task(spawner: Spawner) { ..Default::default() }, controller_context, - power_policy_subscriber, - )); + ))); // Spin up CFU service static CFU_CLIENT: OnceLock = OnceLock::new(); @@ -260,7 +262,12 @@ async fn task(spawner: Spawner) { power_service, )); - spawner.must_spawn(type_c_service_task(type_c_service, [wrapper0, wrapper1], cfu_client)); + spawner.must_spawn(type_c_service_task( + type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), + [wrapper0, wrapper1], + cfu_client, + )); spawner.must_spawn(wrapper_task(wrapper0)); spawner.must_spawn(wrapper_task(wrapper1)); spawner.must_spawn(opm_task(controller_context, [state0, state1])); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 0044e07eb..4ce1ddb69 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -19,12 +19,12 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_interface::port::ControllerId; -use type_c_service::service::Service; - -const NUM_PD_CONTROLLERS: usize = 3; +use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; +const NUM_PD_CONTROLLERS: usize = 3; + const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const CFU0_ID: u8 = 0x00; @@ -50,6 +50,8 @@ type PowerPolicySenderType = MapSender< ) -> power_policy_interface::service::event::EventData, >; +type PowerPolicyReceiverType = DynSubscriber<'static, power_policy_interface::service::event::EventData>; + type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< @@ -58,7 +60,7 @@ type PowerPolicyServiceType = Mutex< >, >; -type ServiceType = Service<'static, DynSubscriber<'static, power_policy_interface::service::event::EventData>>; +type ServiceType = Service<'static>; #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { @@ -216,12 +218,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(Service::create( - Default::default(), - controller_context, - power_policy_subscriber, - )); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); // Spin up CFU service static CFU_CLIENT: OnceLock = OnceLock::new(); @@ -240,6 +238,7 @@ async fn task(spawner: Spawner) { )); spawner.must_spawn(type_c_service_task( type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), [wrapper0, wrapper1, wrapper2], cfu_client, )); @@ -307,12 +306,13 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( - service: &'static ServiceType, + service: &'static Mutex, + event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } fn main() { diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index aa037ec9e..d2e4b5f61 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -1,6 +1,8 @@ +use core::cell::RefCell; +use core::future::pending; + use embassy_futures::select::{Either, select}; -use embassy_sync::mutex::Mutex; -use embedded_services::{GlobalRawMutex, debug, error, event::Receiver, info, trace}; +use embedded_services::{debug, error, event::Receiver, info, trace}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::PdError as Error; use power_policy_interface::service::event::EventData as PowerPolicyEventData; @@ -12,7 +14,6 @@ use type_c_interface::service::event; pub mod config; pub mod pd; -mod port; mod power; mod ucsi; pub mod vdm; @@ -24,8 +25,6 @@ const MAX_SUPPORTED_PORTS: usize = 4; struct State { /// Current port status port_status: [PortStatus; MAX_SUPPORTED_PORTS], - /// Next port to check, this is used to round-robin through ports - port_event_streaming_state: Option, /// UCSI state ucsi: ucsi::State, } @@ -34,15 +33,13 @@ struct State { /// /// Constructing a Service is the first step in using the Type-C service. /// Arguments should be an initialized context -pub struct Service<'a, PowerReceiver: Receiver> { +pub struct Service<'a> { /// Type-C context pub(crate) context: &'a type_c_interface::service::context::Context, /// Current state - state: Mutex, + state: State, /// Config config: config::Config, - /// Power policy event subscriber - power_policy_event_subscriber: Mutex, } /// Power policy events @@ -70,31 +67,29 @@ pub enum Event { PowerPolicy(PowerPolicyEvent), } -impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { +impl<'a> Service<'a> { /// Create a new service the given configuration - pub fn create( - config: config::Config, - context: &'a type_c_interface::service::context::Context, - power_policy_subscriber: PowerReceiver, - ) -> Self { + pub fn create(config: config::Config, context: &'a type_c_interface::service::context::Context) -> Self { Self { context, - state: Mutex::new(State::default()), + state: State::default(), config, - power_policy_event_subscriber: Mutex::new(power_policy_subscriber), } } /// Get the cached port status - pub async fn get_cached_port_status(&self, port_id: GlobalPortId) -> Result { - let state = self.state.lock().await; - Ok(*state.port_status.get(port_id.0 as usize).ok_or(Error::InvalidPort)?) + pub fn get_cached_port_status(&self, port_id: GlobalPortId) -> Result { + Ok(*self + .state + .port_status + .get(port_id.0 as usize) + .ok_or(Error::InvalidPort)?) } /// Set the cached port status - async fn set_cached_port_status(&self, port_id: GlobalPortId, status: PortStatus) -> Result<(), Error> { - let mut state = self.state.lock().await; - *state + 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; @@ -103,12 +98,12 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive /// Process events for a specific port async fn process_port_event( - &self, + &mut self, port_id: GlobalPortId, event: PortStatusChanged, status: PortStatus, ) -> Result<(), Error> { - let old_status = self.get_cached_port_status(port_id).await?; + let old_status = self.get_cached_port_status(port_id)?; debug!("Port{}: Event: {:#?}", port_id.0, event); debug!("Port{} Previous status: {:#?}", port_id.0, old_status); @@ -131,14 +126,59 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive .await; } - self.set_cached_port_status(port_id, status).await?; + self.set_cached_port_status(port_id, status)?; self.handle_ucsi_port_event(port_id, event, &status).await; Ok(()) } + /// Process the given event + pub async fn process_event(&mut self, event: Event) -> Result<(), Error> { + match event { + Event::PortStatusChanged(port, event_kind, status) => { + trace!("Port{}: Processing port status changed", port.0); + self.process_port_event(port, event_kind, status).await + } + Event::PortNotification(port, notification) => { + // Other port notifications + info!("Port{}: Got port notification: {:?}", port.0, notification); + Ok(()) + } + Event::PowerPolicy(event) => { + trace!("Processing power policy event"); + self.process_power_policy_event(&event).await + } + } + } +} + +/// 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, + /// Next port to check, this is used to round-robin through ports + port_event_streaming_state: Option, + /// 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, + port_event_streaming_state: None, + power_policy_event_subscriber: RefCell::new(power_policy_event_subscriber), + } + } + /// Wait for the next event - pub async fn wait_next(&self) -> Result { + pub async fn wait_next(&mut self) -> Result { loop { match select(self.wait_port_flags(), self.wait_power_policy_event()).await { Either::First(mut stream) => { @@ -147,7 +187,7 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive .await? { let port_id = GlobalPortId(port_id as u8); - self.state.lock().await.port_event_streaming_state = Some(stream); + self.port_event_streaming_state = Some(stream); match event { PortEventVariant::StatusChanged(status_event) => { // Return a port status changed event @@ -161,7 +201,7 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } } } else { - self.state.lock().await.port_event_streaming_state = None; + self.port_event_streaming_state = None; } } Either::Second(event) => return Ok(event), @@ -169,28 +209,43 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } } - /// Process the given event - pub async fn process_event(&self, event: Event) -> Result<(), Error> { - match event { - Event::PortStatusChanged(port, event_kind, status) => { - trace!("Port{}: Processing port status changed", port.0); - self.process_port_event(port, event_kind, status).await - } - Event::PortNotification(port, notification) => { - // Other port notifications - info!("Port{}: Got port notification: {:?}", port.0, notification); - Ok(()) - } - Event::PowerPolicy(event) => { - trace!("Processing power policy event"); - self.process_power_policy_event(&event).await - } + /// Wait for port flags + async fn wait_port_flags(&self) -> PortEventStreamer { + if let Some(ref streamer) = self.port_event_streaming_state { + // If we have an existing iterator, return it + // Yield first to prevent starving other tasks + embassy_futures::yield_now().await; + *streamer + } else { + // Wait for the next port event and create a streamer + PortEventStreamer::new(self.context.get_unhandled_events().await.into_iter()) } } - /// Combined processing function - pub async fn process_next_event(&self) -> Result<(), Error> { - let event = self.wait_next().await?; - self.process_event(event).await + /// 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/pd.rs b/type-c-service/src/service/pd.rs index 0c251e783..21934fa76 100644 --- a/type-c-service/src/service/pd.rs +++ b/type-c-service/src/service/pd.rs @@ -1,12 +1,10 @@ //! Power Delivery (PD) related functionality. -use embedded_services::event::Receiver; use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; -use power_policy_interface::service::event::EventData as PowerPolicyEventData; use super::Service; -impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { +impl Service<'_> { /// Get the oldest unhandled PD alert for the given port. /// /// Returns [`None`] if no alerts are pending. diff --git a/type-c-service/src/service/port.rs b/type-c-service/src/service/port.rs deleted file mode 100644 index 3e26b210c..000000000 --- a/type-c-service/src/service/port.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; -use crate::PortEventStreamer; -use power_policy_interface::service::event::EventData as PowerPolicyEventData; - -impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { - /// Wait for port flags - pub(super) async fn wait_port_flags(&self) -> PortEventStreamer { - if let Some(ref streamer) = self.state.lock().await.port_event_streaming_state { - // If we have an existing iterator, return it - // Yield first to prevent starving other tasks - embassy_futures::yield_now().await; - *streamer - } else { - // Wait for the next port event and create a streamer - PortEventStreamer::new(self.context.get_unhandled_events().await.into_iter()) - } - } -} diff --git a/type-c-service/src/service/power.rs b/type-c-service/src/service/power.rs index 833d4767e..4ec9e3807 100644 --- a/type-c-service/src/service/power.rs +++ b/type-c-service/src/service/power.rs @@ -1,31 +1,10 @@ use power_policy_interface::service as power_policy; -use power_policy_interface::service::event::EventData as PowerPolicyEventData; use super::*; -impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { - /// Wait for a power policy event - pub(super) async fn wait_power_policy_event(&self) -> Event { - loop { - match self.power_policy_event_subscriber.lock().await.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 - } - } - } - } - +impl Service<'_> { /// Set the unconstrained state for all ports - pub(super) async fn set_unconstrained_all(&self, unconstrained: bool) -> Result<(), Error> { + 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) @@ -36,12 +15,10 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive /// Processed unconstrained state change pub(super) async fn process_unconstrained_state_change( - &self, + &mut self, unconstrained_state: &power_policy::UnconstrainedState, ) -> Result<(), Error> { if unconstrained_state.unconstrained { - let state = self.state.lock().await; - if unconstrained_state.available > 1 { // There are multiple available unconstrained consumers, set all ports to unconstrained // TODO: determine if we need to consider if we need to consider @@ -52,7 +29,8 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } 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 = state + let unconstrained_port = self + .state .port_status .iter() .take(num_ports) @@ -88,21 +66,19 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } /// Process power policy events - pub(super) async fn process_power_policy_event(&self, message: &PowerPolicyEvent) -> Result<(), Error> { + pub(super) async fn process_power_policy_event(&mut self, message: &PowerPolicyEvent) -> Result<(), Error> { match message { PowerPolicyEvent::Unconstrained(state) => self.process_unconstrained_state_change(state).await, PowerPolicyEvent::ConsumerDisconnected => { - let mut state = self.state.lock().await; - state.ucsi.psu_connected = false; + self.state.ucsi.psu_connected = false; // Notify OPM because this can affect battery charging capability status - self.pend_ucsi_connected_ports(&mut state).await; + self.pend_ucsi_connected_ports().await; Ok(()) } PowerPolicyEvent::ConsumerConnected => { - let mut state = self.state.lock().await; - state.ucsi.psu_connected = true; + self.state.ucsi.psu_connected = true; // Notify OPM because this can affect battery charging capability status - self.pend_ucsi_connected_ports(&mut state).await; + self.pend_ucsi_connected_ports().await; Ok(()) } } diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index 0da530a27..62749cc89 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -7,7 +7,6 @@ use embedded_usb_pd::ucsi::ppm::state_machine::{ }; use embedded_usb_pd::ucsi::{GlobalCommand, ResponseData, lpm, ppm}; use embedded_usb_pd::{PdError, PowerRole}; -use power_policy_interface::service::event::EventData as PowerPolicyEventData; use type_c_interface::service::event::{Event, UsciChangeIndicator}; use super::*; @@ -42,19 +41,19 @@ pub(super) struct State { pub(super) psu_connected: bool, } -impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { +impl Service<'_> { /// PPM reset implementation - fn process_ppm_reset(&self, state: &mut State) { + fn process_ppm_reset(&mut self) { debug!("Resetting PPM"); - state.notifications_enabled = NotificationEnable::default(); - state.pending_ports.clear(); - state.valid_battery_charging_capability.clear(); + self.state.ucsi.notifications_enabled = NotificationEnable::default(); + self.state.ucsi.pending_ports.clear(); + self.state.ucsi.valid_battery_charging_capability.clear(); } /// Set notification enable implementation - fn process_set_notification_enable(&self, state: &mut State, enable: NotificationEnable) { + fn process_set_notification_enable(&mut self, enable: NotificationEnable) { debug!("Set Notification Enable: {:?}", enable); - state.notifications_enabled = enable; + self.state.ucsi.notifications_enabled = enable; } /// PPM get capabilities implementation @@ -65,14 +64,10 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive ppm::ResponseData::GetCapability(capabilities) } - fn process_ppm_command( - &self, - state: &mut State, - command: &ucsi::ppm::Command, - ) -> Result, PdError> { + fn process_ppm_command(&mut self, command: &ucsi::ppm::Command) -> Result, PdError> { match command { ppm::Command::SetNotificationEnable(enable) => { - self.process_set_notification_enable(state, enable.notification_enable); + self.process_set_notification_enable(enable.notification_enable); Ok(None) } ppm::Command::GetCapability => Ok(Some(self.process_get_capabilities())), @@ -83,12 +78,11 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive /// Determine the battery charging capability status for the given port fn determine_battery_charging_capability_status( &self, - state: &mut State, port_id: GlobalPortId, port_status: &PortStatus, ) -> Option { if port_status.power_role == PowerRole::Sink { - if state.valid_battery_charging_capability.contains(&port_id) && !state.psu_connected { + if self.state.ucsi.valid_battery_charging_capability.contains(&port_id) && !self.state.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 @@ -108,8 +102,7 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } async fn process_lpm_command( - &self, - state: &mut super::State, + &mut self, command: &ucsi::lpm::GlobalCommand, ) -> Result, PdError> { debug!("Processing LPM command: {:?}", command); @@ -135,9 +128,9 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive }))) = response { let raw_port = command.port().0 as usize; - let port_status = state.port_status.get(raw_port).ok_or(PdError::InvalidPort)?; + let port_status = self.state.port_status.get(raw_port).ok_or(PdError::InvalidPort)?; *battery_charging_status = - self.determine_battery_charging_capability_status(&mut state.ucsi, 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()); } @@ -147,9 +140,9 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } } - /// Upate the CCI connector change field based on the current pending port - fn set_cci_connector_change(&self, state: &mut State, cci: &mut GlobalCci) { - if let Some(current_port) = state.pending_ports.front() { + /// 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() { // UCSI connector numbers are 1-based cci.set_connector_change(GlobalPortId(current_port.0 + 1)); } else { @@ -159,10 +152,10 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive } /// Acknowledge the current connector change and move to the next if present - async fn ack_connector_change(&self, state: &mut State, cci: &mut GlobalCci) { + async fn ack_connector_change(&mut self, cci: &mut GlobalCci) { // Pop the just acknowledged port and move to the next if present - if let Some(_current_port) = state.pending_ports.pop_front() { - if let Some(next_port) = state.pending_ports.front() { + if let Some(_current_port) = self.state.ucsi.pending_ports.pop_front() { + if let Some(next_port) = self.state.ucsi.pending_ports.front() { debug!("ACK_CCI processed, next pending port: {:?}", next_port); self.context .broadcast_message(Event::UcsiCci(UsciChangeIndicator { @@ -178,12 +171,11 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive warn!("Received ACK_CCI with no pending connector changes"); } - self.set_cci_connector_change(state, cci); + self.set_cci_connector_change(cci); } /// Process a UCSI command - pub async fn process_ucsi_command(&self, command: &GlobalCommand) -> UcsiResponse { - let state = &mut self.state.lock().await; + pub async fn process_ucsi_command(&mut self, command: &GlobalCommand) -> UcsiResponse { let mut next_input = Some(PpmInput::Command(command)); let mut response = UcsiResponse { notify_opm: false, @@ -196,7 +188,7 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive // Using a loop allows all logic to be centralized loop { let output = if let Some(next_input) = next_input.take() { - state.ucsi.ppm_state_machine.consume(next_input) + self.state.ucsi.ppm_state_machine.consume(next_input) } else { error!("Unexpected end of state machine processing"); return UcsiResponse { @@ -226,12 +218,12 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive match command { ucsi::GlobalCommand::PpmCommand(ppm_command) => { response.data = self - .process_ppm_command(&mut state.ucsi, ppm_command) + .process_ppm_command(ppm_command) .map(|inner| inner.map(ResponseData::Ppm)); } ucsi::GlobalCommand::LpmCommand(lpm_command) => { response.data = self - .process_lpm_command(state, lpm_command) + .process_lpm_command(lpm_command) .await .map(|inner| inner.map(ResponseData::Lpm)); } @@ -240,20 +232,20 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive // Don't return yet, need to inform state machine that command is complete } PpmOutput::OpmNotifyCommandComplete => { - response.notify_opm = state.ucsi.notifications_enabled.cmd_complete(); + response.notify_opm = self.state.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 state.ucsi, &mut response.cci); + self.set_cci_connector_change(&mut response.cci); return response; } PpmOutput::AckComplete(ack) => { - response.notify_opm = state.ucsi.notifications_enabled.cmd_complete(); + response.notify_opm = self.state.ucsi.notifications_enabled.cmd_complete(); if ack.command_complete() { response.cci.set_ack_command(true); } if ack.connector_change() { - self.ack_connector_change(&mut state.ucsi, &mut response.cci).await; + self.ack_connector_change(&mut response.cci).await; } return response; @@ -261,18 +253,18 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive PpmOutput::ResetComplete => { // Resets don't follow the normal command execution flow // So do any reset processing here - self.process_ppm_reset(&mut state.ucsi); + self.process_ppm_reset(); // Don't notify OPM because it'll poll response.notify_opm = false; response.cci = Cci::new_reset_complete(); - self.set_cci_connector_change(&mut state.ucsi, &mut response.cci); + self.set_cci_connector_change(&mut response.cci); return response; } PpmOutput::OpmNotifyBusy => { // Notify if notifications are enabled in general - response.notify_opm = !state.ucsi.notifications_enabled.is_empty(); + response.notify_opm = !self.state.ucsi.notifications_enabled.is_empty(); response.cci.set_busy(true); - self.set_cci_connector_change(&mut state.ucsi, &mut response.cci); + self.set_cci_connector_change(&mut response.cci); return response; } }, @@ -281,7 +273,7 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive response.notify_opm = false; response.cci = Cci::default(); response.data = Ok(None); - self.set_cci_connector_change(&mut state.ucsi, &mut response.cci); + self.set_cci_connector_change(&mut response.cci); return response; } } @@ -290,12 +282,11 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive /// Handle PD port events, update UCSI state, and generate corresponding UCSI notifications pub(super) async fn handle_ucsi_port_event( - &self, + &mut self, port_id: GlobalPortId, port_event: PortStatusChanged, port_status: &PortStatus, ) { - let state = &mut self.state.lock().await.ucsi; let mut ucsi_event = ConnectorStatusChange::default(); ucsi_event.set_connect_change(port_event.plug_inserted_or_removed()); @@ -317,36 +308,48 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive ucsi_event.set_battery_charging_status_change(true); // Power negotiation completed, battery charging capability status is now valid - if state.valid_battery_charging_capability.insert(port_id).is_err() { + if self + .state + .ucsi + .valid_battery_charging_capability + .insert(port_id) + .is_err() + { error!("Valid battery charging capability overflow for port {:?}", port_id); } } if !port_status.is_connected() { // Reset battery charging capability status when disconnected - let _ = state.valid_battery_charging_capability.remove(&port_id); + let _ = self.state.ucsi.valid_battery_charging_capability.remove(&port_id); } - if ucsi_event.filter_enabled(state.notifications_enabled).is_empty() { + if ucsi_event + .filter_enabled(self.state.ucsi.notifications_enabled) + .is_empty() + { trace!("{:?}: event received, but no UCSI notifications enabled", port_id); return; } - self.pend_ucsi_port(state, port_id).await; + self.pend_ucsi_port(port_id).await; } /// Pend UCSI events for all connected ports - pub(super) async fn pend_ucsi_connected_ports(&self, state: &mut super::State) { - for (port_id, port_status) in state.port_status.iter().enumerate() { - if port_status.is_connected() { - self.pend_ucsi_port(&mut state.ucsi, GlobalPortId(port_id as u8)).await; + 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() { + let port_id = GlobalPortId(i as u8); + if self.state.port_status[i].is_connected() { + self.pend_ucsi_port(port_id).await; } } } /// Pend a UCSI event for the given port - async fn pend_ucsi_port(&self, state: &mut State, port_id: GlobalPortId) { - if state.pending_ports.iter().any(|pending| *pending == port_id) { + async fn pend_ucsi_port(&mut self, port_id: GlobalPortId) { + if self.state.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; } @@ -354,8 +357,8 @@ impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceive // 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 = state.pending_ports.is_empty(); - if state.pending_ports.push_back(port_id).is_ok() { + 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 { port: port_id, diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index 6a50ee332..d92590709 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,13 +1,11 @@ //! VDM (Vendor Defined Messages) related functionality. -use embedded_services::event::Receiver; use embedded_usb_pd::{GlobalPortId, PdError}; -use power_policy_interface::service::event::EventData as PowerPolicyEventData; use type_c_interface::port::{AttnVdm, OtherVdm}; use super::Service; -impl<'a, PowerReceiver: Receiver> Service<'a, PowerReceiver> { +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 diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index 84f30b61c..ce446aaa9 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,4 +1,3 @@ -use core::future::Future; use embedded_services::{ error, event::{self, Receiver}, @@ -7,36 +6,26 @@ use embedded_services::{ }; use power_policy_interface::service::event::EventData as PowerPolicyEventData; -use crate::{service::Service, wrapper::ControllerWrapper}; +use crate::{ + service::{EventReceiver, Service}, + wrapper::ControllerWrapper, +}; -/// Task to run the Type-C service, takes a closure to customize the event loop -pub async fn task_closure< - 'a, - M, - D, - S, - V, - PowerReceiver: Receiver, - Fut: Future, - F: Fn(&'a Service<'a, PowerReceiver>) -> Fut, - const N: usize, ->( - service: &'static Service<'a, PowerReceiver>, - wrappers: [&'a ControllerWrapper<'a, M, D, S, V>; N], - cfu_client: &'a cfu_service::CfuClient, - f: F, +/// Task to run the Type-C service, running the default event loop +pub async fn task, const N: usize>( + service: &'static impl Lockable>, + mut event_receiver: EventReceiver<'static, PowerReceiver>, + wrappers: [&'static ControllerWrapper<'static, M, D, S, V>; N], + cfu_client: &'static cfu_service::CfuClient, ) where M: embassy_sync::blocking_mutex::raw::RawMutex, - D: Lockable, + D: embedded_services::sync::Lockable, S: event::Sender, V: crate::wrapper::FwOfferValidator, - D::Inner: type_c_interface::port::Controller, + ::Inner: type_c_interface::port::Controller, { info!("Starting type-c task"); - // TODO: move this service to use the new power policy event subscribers and receivers - // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 - for controller_wrapper in wrappers { if controller_wrapper.register(cfu_client).is_err() { error!("Failed to register a controller"); @@ -45,31 +34,16 @@ pub async fn task_closure< } loop { - f(service).await; - } -} - -/// Task to run the Type-C service, running the default event loop -pub async fn task<'a, M, D, S, V, PowerReceiver: Receiver, const N: usize>( - service: &'static Service<'a, PowerReceiver>, - wrappers: [&'a ControllerWrapper<'a, M, D, S, V>; N], - cfu_client: &'a cfu_service::CfuClient, -) where - M: embassy_sync::blocking_mutex::raw::RawMutex, - D: embedded_services::sync::Lockable, - S: event::Sender, - V: crate::wrapper::FwOfferValidator, - ::Inner: type_c_interface::port::Controller, -{ - task_closure( - service, - wrappers, - cfu_client, - |service: &Service<'_, PowerReceiver>| async { - if let Err(e) = service.process_next_event().await { - error!("Type-C service processing error: {:#?}", e); + let event = match event_receiver.wait_next().await { + Ok(event) => event, + Err(e) => { + error!("Error waiting for event: {:#?}", e); + continue; } - }, - ) - .await; + }; + + if let Err(e) = service.lock().await.process_event(event).await { + error!("Type-C service processing error: {:#?}", e); + } + } } From 07aa6e3139b590663401041408a6723bb2eb25f1 Mon Sep 17 00:00:00 2001 From: Robert Zieba Date: Wed, 1 Apr 2026 10:45:56 -0700 Subject: [PATCH 56/79] Restore commented out `opm_task` --- examples/std/src/bin/type_c/ucsi.rs | 131 +++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index bdb8a9a63..42c166038 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -24,8 +24,8 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_interface::port::ControllerId; use type_c_interface::service::context::Context; -use type_c_service::service::{EventReceiver, Service}; use type_c_service::service::config::Config; +use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -62,7 +62,134 @@ type ServiceType = Service<'static>; #[embassy_executor::task] async fn opm_task(_context: &'static Context, _state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { - // ... rest of opm_task remains the same ... + // TODO: migrate this logic to an integration test when we move away from 'static lifetimes. + /*const CAPABILITY: PowerCapability = PowerCapability { + voltage_mv: 20000, + current_ma: 5000, + }; + + info!("Resetting PPM..."); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::PpmReset)) + .await + .into(); + let response = response.unwrap(); + if !response.cci.reset_complete() || response.cci.error() { + error!("PPM reset failed: {:?}", response.cci); + } else { + info!("PPM reset successful"); + } + + info!("Set Notification enable..."); + let mut notifications = NotificationEnable::default(); + notifications.set_cmd_complete(true); + notifications.set_connect_change(true); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::SetNotificationEnable( + ppm::set_notification_enable::Args { + notification_enable: notifications, + }, + ))) + .await + .into(); + let response = response.unwrap(); + if !response.cci.cmd_complete() || response.cci.error() { + error!("Set Notification enable failed: {:?}", response.cci); + } else { + info!("Set Notification enable successful"); + } + + info!("Sending command complete ack..."); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { + ack: *Ack::default().set_command_complete(true), + }))) + .await + .into(); + let response = response.unwrap(); + if !response.cci.ack_command() || response.cci.error() { + error!("Sending command complete ack failed: {:?}", response.cci); + } else { + info!("Sending command complete ack successful"); + } + + info!("Connecting sink on port 0"); + state[0].connect_sink(CAPABILITY, false).await; + info!("Connecting sink on port 1"); + state[1].connect_sink(CAPABILITY, false).await; + + // Ensure connect flow has time to complete + embassy_time::Timer::after_millis(1000).await; + + info!("Port 0: Get connector status..."); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::LpmCommand(lpm::GlobalCommand::new( + GlobalPortId(0), + lpm::CommandData::GetConnectorStatus, + ))) + .await + .into(); + let response = response.unwrap(); + if !response.cci.cmd_complete() || response.cci.error() { + error!("Get connector status failed: {:?}", response.cci); + } else { + info!( + "Get connector status successful, connector change: {:?}", + response.cci.connector_change() + ); + } + + info!("Sending command complete ack..."); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { + ack: *Ack::default().set_command_complete(true).set_connector_change(true), + }))) + .await + .into(); + let response = response.unwrap(); + if !response.cci.ack_command() || response.cci.error() { + error!("Sending command complete ack failed: {:?}", response.cci); + } else { + info!( + "Sending command complete ack successful, connector change: {:?}", + response.cci.connector_change() + ); + } + + info!("Port 1: Get connector status..."); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::LpmCommand(lpm::GlobalCommand::new( + GlobalPortId(1), + lpm::CommandData::GetConnectorStatus, + ))) + .await + .into(); + let response = response.unwrap(); + if !response.cci.cmd_complete() || response.cci.error() { + error!("Get connector status failed: {:?}", response.cci); + } else { + info!( + "Get connector status successful, connector change: {:?}", + response.cci.connector_change() + ); + } + + info!("Sending command complete ack..."); + let response: UcsiResponseResult = context + .execute_ucsi_command_external(Command::PpmCommand(ppm::Command::AckCcCi(ppm::ack_cc_ci::Args { + ack: *Ack::default().set_command_complete(true).set_connector_change(true), + }))) + .await + .into(); + let response = response.unwrap(); + if !response.cci.ack_command() || response.cci.error() { + error!("Sending command complete ack failed: {:?}", response.cci); + } else { + info!( + "Sending command complete ack successful, connector change: {:?}", + response.cci.connector_change() + ); + }*/ } #[embassy_executor::task(pool_size = 2)] From 3f3ca6ae2b8eb615f04a2a3fc534231751d86a4c Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:27:29 -0700 Subject: [PATCH 57/79] Time-alarm service: Split interface and relay handling (#776) This change splits the interface for the time-alarm service into two crates - an 'interface' crate that contains traits and support types for the public interface for the time-alarm service, and a 'relay' crate that's written against the 'interface' crate and handles relaying messages to and from a time-alarm service implementation over MCTP. This split should allow OEMs to substitute their own service implementations if they want to and still reuse our relay code (or write their own relay against the stock service implementation). Additionally, splitting the relay handling from the service implementation should also allow us to add support for different kinds of relay handlers (e.g. HID) without needing to pay the code size cost of having that code on platforms that aren't using it since it's no longer invasive on the service type. --- Cargo.lock | 21 ++- Cargo.toml | 6 +- embedded-service/src/relay/mod.rs | 22 +-- examples/rt685s-evk/Cargo.lock | 19 ++- examples/rt685s-evk/Cargo.toml | 5 +- examples/rt685s-evk/src/bin/time_alarm.rs | 16 ++- .../Cargo.toml | 6 +- .../src/acpi_timestamp.rs | 8 ++ time-alarm-service-interface/src/lib.rs | 133 ++++++++++++++++++ time-alarm-service-relay/Cargo.toml | 25 ++++ time-alarm-service-relay/src/lib.rs | 59 ++++++++ .../src/serialization.rs | 103 ++------------ time-alarm-service/Cargo.toml | 4 +- time-alarm-service/src/lib.rs | 67 ++------- time-alarm-service/tests/tad_test.rs | 8 +- uart-service/Cargo.toml | 11 -- 16 files changed, 320 insertions(+), 193 deletions(-) rename {time-alarm-service-messages => time-alarm-service-interface}/Cargo.toml (71%) rename {time-alarm-service-messages => time-alarm-service-interface}/src/acpi_timestamp.rs (87%) create mode 100644 time-alarm-service-interface/src/lib.rs create mode 100644 time-alarm-service-relay/Cargo.toml create mode 100644 time-alarm-service-relay/src/lib.rs rename time-alarm-service-messages/src/lib.rs => time-alarm-service-relay/src/serialization.rs (81%) diff --git a/Cargo.lock b/Cargo.lock index cb379bdfd..3acdfc114 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2354,24 +2354,35 @@ dependencies = [ "log", "odp-service-common", "time-alarm-service", - "time-alarm-service-messages", + "time-alarm-service-interface", "tokio", "zerocopy", ] [[package]] -name = "time-alarm-service-messages" +name = "time-alarm-service-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", "embedded-mcu-hal", - "embedded-services", "log", "num_enum", "zerocopy", ] +[[package]] +name = "time-alarm-service-relay" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-mcu-hal", + "embedded-services", + "log", + "num_enum", + "time-alarm-service-interface", +] + [[package]] name = "tokio" version = "1.47.1" @@ -2567,9 +2578,7 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" name = "uart-service" version = "0.1.0" dependencies = [ - "battery-service-messages", "bitfield 0.17.0", - "debug-service-messages", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -2579,8 +2588,6 @@ dependencies = [ "log", "mctp-rs", "num_enum", - "thermal-service-messages", - "time-alarm-service-messages", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ad4fe567f..d3558a57d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ members = [ "power-button-service", "power-policy-service", "time-alarm-service", - "time-alarm-service-messages", + "time-alarm-service-interface", + "time-alarm-service-relay", "type-c-service", "debug-service", "debug-service-messages", @@ -105,7 +106,8 @@ serde = { version = "1.0.*", default-features = false } static_cell = "2.1.0" toml = { version = "0.8", default-features = false } thermal-service-messages = { path = "./thermal-service-messages" } -time-alarm-service-messages = { path = "./time-alarm-service-messages" } +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" } syn = "2.0" tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" } diff --git a/embedded-service/src/relay/mod.rs b/embedded-service/src/relay/mod.rs index 3cc7092ee..d92cfb97f 100644 --- a/embedded-service/src/relay/mod.rs +++ b/embedded-service/src/relay/mod.rs @@ -125,7 +125,7 @@ pub mod mctp { fn process_request<'a>( &'a self, request: Self::RequestType, - ) -> impl core::future::Future + Send + 'a; + ) -> impl core::future::Future + 'a; } // Traits below this point are intended for consumption by relay services (e.g. the eSPI service), not individual services that want their messages relayed. @@ -168,7 +168,7 @@ pub mod mctp { fn process_request<'a>( &'a self, message: Self::RequestEnumType, - ) -> impl core::future::Future + Send + 'a; + ) -> impl core::future::Future + 'a; } /// This macro generates a relay type over a collection of message types, which can be used by a relay service to @@ -195,9 +195,9 @@ pub mod mctp { /// ```ignore /// /// impl_odp_mctp_relay_handler!( - /// MyRelayHanderType; - /// Battery, 0x9, battery_service::Service<'static>; - /// TimeAlarm, 0xB, time_alarm_service::Service<'static>; + /// MyRelayHandlerType; + /// Battery, 0x9, battery_service_relay::RelayHandler>; + /// TimeAlarm, 0xB, time_alarm_service_relay::RelayHandler>; /// ); /// /// let relay_handler = MyRelayHandlerType::new(battery_service_instance, time_alarm_service_instance); @@ -448,16 +448,16 @@ pub mod mctp { } - pub struct $relay_type_name<'hw> { + pub struct $relay_type_name { $( - [<$service_name:snake _handler>]: &'hw $service_handler_type, + [<$service_name:snake _handler>]: $service_handler_type, )+ } - impl<'hw> $relay_type_name<'hw> { + impl $relay_type_name { pub fn new( $( - [<$service_name:snake _handler>]: &'hw $service_handler_type, + [<$service_name:snake _handler>]: $service_handler_type, )+ ) -> Self { Self { @@ -468,7 +468,7 @@ pub mod mctp { } } - impl<'hw> $crate::relay::mctp::RelayHandler for $relay_type_name<'hw> { + impl $crate::relay::mctp::RelayHandler for $relay_type_name { type ServiceIdType = OdpService; type HeaderType = OdpHeader; type RequestEnumType = HostRequest; @@ -477,7 +477,7 @@ pub mod mctp { fn process_request<'a>( &'a self, message: HostRequest, - ) -> impl core::future::Future + Send + 'a { + ) -> impl core::future::Future + 'a { async move { match message { $( diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index ea9969a94..ee421b4f9 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1424,7 +1424,8 @@ dependencies = [ "power-policy-service", "static_cell", "time-alarm-service", - "time-alarm-service-messages", + "time-alarm-service-interface", + "time-alarm-service-relay", "tps6699x", "type-c-interface", "type-c-service", @@ -1638,22 +1639,32 @@ dependencies = [ "embedded-mcu-hal", "embedded-services", "odp-service-common", - "time-alarm-service-messages", + "time-alarm-service-interface", "zerocopy", ] [[package]] -name = "time-alarm-service-messages" +name = "time-alarm-service-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", "embedded-mcu-hal", - "embedded-services", "num_enum", "zerocopy", ] +[[package]] +name = "time-alarm-service-relay" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-mcu-hal", + "embedded-services", + "num_enum", + "time-alarm-service-interface", +] + [[package]] name = "tps6699x" version = "0.1.0" diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 98fa21e8e..e0277d38c 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -71,7 +71,10 @@ embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb type-c-service = { path = "../../type-c-service", features = ["defmt"] } type-c-interface = { path = "../../type-c-interface", features = ["defmt"] } time-alarm-service = { path = "../../time-alarm-service", features = ["defmt"] } -time-alarm-service-messages = { path = "../../time-alarm-service-messages", features = [ +time-alarm-service-interface = { path = "../../time-alarm-service-interface", features = [ + "defmt", +] } +time-alarm-service-relay = { path = "../../time-alarm-service-relay", features = [ "defmt", ] } diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index b3ea08084..5f8c0343e 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -7,9 +7,17 @@ use embedded_mcu_hal::{ }; use embedded_services::info; use static_cell::StaticCell; -use time_alarm_service_messages::{AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimeZoneOffset, AcpiTimestamp}; +use time_alarm_service_interface::{ + AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimeZoneOffset, AcpiTimestamp, TimeAlarmService, +}; use {defmt_rtt as _, panic_probe as _}; +// Type aliases to make it easier to use the service and relay handler types without needing to write out all the generic parameters every time. +// This is especially helpful for the relay handler, which has a lot of generic parameters due to the traits it needs to implement. +// +type TimeAlarmServiceType = time_alarm_service::Service<'static>; +type TimeAlarmServiceRelayHandlerType = time_alarm_service_relay::TimeAlarmServiceRelayHandler; + #[embassy_executor::main] async fn main(spawner: embassy_executor::Spawner) { let p = embassy_imxrt::init(Default::default()); @@ -25,7 +33,7 @@ async fn main(spawner: embassy_executor::Spawner) { let time_service = odp_service_common::spawn_service!( spawner, - time_alarm_service::Service<'static>, + TimeAlarmServiceType, time_alarm_service::InitParams { backing_clock: dt_clock, tz_storage: tz, @@ -40,10 +48,10 @@ async fn main(spawner: embassy_executor::Spawner) { use embedded_services::relay::mctp::impl_odp_mctp_relay_handler; impl_odp_mctp_relay_handler!( EspiRelayHandler; - TimeAlarm, 0x0B, time_alarm_service::Service<'static>; + TimeAlarm, 0x0B, crate::TimeAlarmServiceRelayHandlerType; ); - let _relay_handler = EspiRelayHandler::new(&time_service); + let _relay_handler = EspiRelayHandler::new(TimeAlarmServiceRelayHandlerType::new(time_service)); // Here, you'd normally pass _relay_handler to your relay service (e.g. eSPI service). // In this example, we're not leveraging a relay service, so we'll just demonstrate some direct calls. diff --git a/time-alarm-service-messages/Cargo.toml b/time-alarm-service-interface/Cargo.toml similarity index 71% rename from time-alarm-service-messages/Cargo.toml rename to time-alarm-service-interface/Cargo.toml index 7d883b7a0..982f8f6c9 100644 --- a/time-alarm-service-messages/Cargo.toml +++ b/time-alarm-service-interface/Cargo.toml @@ -1,6 +1,5 @@ [package] -name = "time-alarm-service-messages" -description = "Time and Alarm service message definitions" +name = "time-alarm-service-interface" version.workspace = true edition.workspace = true license.workspace = true @@ -11,13 +10,12 @@ bitfield.workspace = true defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } embedded-mcu-hal.workspace = true -embedded-services.workspace = true num_enum.workspace = true zerocopy = { workspace = true, features = ["derive"] } [features] defmt = ["dep:defmt", "embedded-mcu-hal/defmt"] -log = ["dep:log", "embedded-services/log"] +log = ["dep:log"] [lints] workspace = true diff --git a/time-alarm-service-messages/src/acpi_timestamp.rs b/time-alarm-service-interface/src/acpi_timestamp.rs similarity index 87% rename from time-alarm-service-messages/src/acpi_timestamp.rs rename to time-alarm-service-interface/src/acpi_timestamp.rs index 25d99b52f..cc29abe70 100644 --- a/time-alarm-service-messages/src/acpi_timestamp.rs +++ b/time-alarm-service-interface/src/acpi_timestamp.rs @@ -59,6 +59,7 @@ impl From<&AcpiTimestamp> for RawAcpiTimestamp { // ------------------------------------------------- +/// The current daylight savings time status of the timer. #[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] @@ -77,6 +78,7 @@ pub enum AcpiDaylightSavingsTimeStatus { // ------------------------------------------------- +/// The time offset from UTC of the system's time zone, expressed in minutes #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct AcpiTimeZoneOffset { @@ -84,6 +86,7 @@ pub struct AcpiTimeZoneOffset { } impl AcpiTimeZoneOffset { + /// Constructs a time zone offset with the given number of minutes from UTC. Valid values are -1440 to 1440 (inclusive). pub fn new(minutes_from_utc: i16) -> Result { if !(-1440..=1440).contains(&minutes_from_utc) { Err(DatetimeClockError::UnsupportedDatetime) @@ -92,11 +95,13 @@ impl AcpiTimeZoneOffset { } } + /// The number of minutes that the time zone is offset from UTC. pub fn minutes_from_utc(&self) -> i16 { self.minutes_from_utc } } +/// The time zone of the system, either unknown or specified as a number of minutes from UTC. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcpiTimeZone { @@ -130,6 +135,7 @@ impl From for i16 { // ------------------------------------------------- +/// A timestamp as specified in the ACPI spec, including time zone and daylight savings time status. #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Clone, Copy, Debug, PartialEq)] pub struct AcpiTimestamp { @@ -139,6 +145,7 @@ pub struct AcpiTimestamp { } impl AcpiTimestamp { + /// Converts this timestamp into the raw byte format specified in the ACPI spec version 6.4, section 9.18.3 (_GRT), with the Valid bit set. pub fn as_bytes(&self) -> [u8; core::mem::size_of::()] /* 16 */ { // Size is guaranteed to be correct by zerocopy, but zerocopy returns as a slice rather than an array, // and we need to return an owned array, so we need to convert. @@ -150,6 +157,7 @@ impl AcpiTimestamp { .expect("Size is guaranteed to be the size of RawAcpiTimestamp") } + /// Attempt to parse an ACPI timestamp from a byte slice. Returns an error if the slice does not represent a valid ACPI timestamp as specified in ACPI spec version 6.4, section 9.18.4 (_SRT). pub fn try_from_bytes(bytes: &[u8]) -> Result { let raw = RawAcpiTimestamp::ref_from_bytes( bytes diff --git a/time-alarm-service-interface/src/lib.rs b/time-alarm-service-interface/src/lib.rs new file mode 100644 index 000000000..71a6f621d --- /dev/null +++ b/time-alarm-service-interface/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] + +mod acpi_timestamp; +pub use acpi_timestamp::{AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimeZoneOffset, AcpiTimestamp}; + +use bitfield::bitfield; +use embedded_mcu_hal::time::DatetimeClockError; + +/// The number of seconds before a timer will expire. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AlarmTimerSeconds(pub u32); +impl AlarmTimerSeconds { + /// The alarm is not running and will never expire. + pub const DISABLED: Self = Self(u32::MAX); +} + +impl Default for AlarmTimerSeconds { + fn default() -> Self { + Self::DISABLED + } +} + +// ------------------------------------------------- + +/// If a timer is on the wrong power source when it expires, the number of seconds after switching to the correct +/// power source that must elapse on the correct power source before the timer actually triggers a wake event. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AlarmExpiredWakePolicy(pub u32); +impl AlarmExpiredWakePolicy { + /// The timer will trigger a wake event immediately upon switching to the correct power source. + pub const INSTANTLY: Self = Self(0); + + /// The timer will never trigger a wake event if it expires on the wrong power source, even if it later switches to the correct power source. + pub const NEVER: Self = Self(u32::MAX); +} + +impl Default for AlarmExpiredWakePolicy { + fn default() -> Self { + Self::NEVER + } +} + +// ------------------------------------------------- + +/// ACPI timer ID as defined in the ACPI spec. +#[derive(Clone, Copy, Debug, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub enum AcpiTimerId { + /// The timer that is active when the system is on external power. + AcPower = 0, + + /// The timer that is active when the system is on battery power. + DcPower = 1, +} + +impl AcpiTimerId { + /// Gets the timer ID for the other power source. + pub fn get_other_timer_id(&self) -> Self { + match self { + AcpiTimerId::AcPower => AcpiTimerId::DcPower, + AcpiTimerId::DcPower => AcpiTimerId::AcPower, + } + } +} + +bitfield!( + /// Describes the current status of a timer, including whether it has expired and whether it triggered a wake event. + #[derive(Copy, Clone, Default, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct TimerStatus(u32); + impl Debug; + bool; + pub timer_expired, set_timer_expired: 0; + pub timer_triggered_wake, set_timer_triggered_wake: 1; +); + +// ------------------------------------------------- + +bitfield!( + /// Describes the capabilities of a time-alarm device. Details on semantics of individual fields are available in the ACPI spec, version 6.4, section 9.18.2 + #[derive(Copy, Clone, Default, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct TimeAlarmDeviceCapabilities(u32); + impl Debug; + bool; + pub ac_wake_implemented, set_ac_wake_implemented: 0; + pub dc_wake_implemented, set_dc_wake_implemented: 1; + pub realtime_implemented, set_realtime_implemented: 2; + pub realtime_accuracy_in_milliseconds, set_realtime_accuracy_in_milliseconds: 3; + pub get_wake_status_supported, set_get_wake_status_supported: 4; + pub ac_s4_wake_supported, set_ac_s4_wake_supported: 5; + pub ac_s5_wake_supported, set_ac_s5_wake_supported: 6; + pub dc_s4_wake_supported, set_dc_s4_wake_supported: 7; + pub dc_s5_wake_supported, set_dc_s5_wake_supported: 8; +); + +/// The interface for a time-alarm service, which implements the ACPI Time and Alarm device specification. +/// See the ACPI spec version 6.4, section 9.18, for more details on the expected behavior of each method. +pub trait TimeAlarmService { + /// Query the capabilities of the time-alarm device. Analogous to ACPI TAD's _GCP method. + fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities; + + /// Query the current time. Analogous to ACPI TAD's _GRT method. + fn get_real_time(&self) -> Result; + + /// Change the current time. Analogous to ACPI TAD's _SRT method. + fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError>; + + /// Query the current wake status. Analogous to ACPI TAD's _GWS method. + fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus; + + /// Clear the current wake status. Analogous to ACPI TAD's _CWS method. + fn clear_wake_status(&self, timer_id: AcpiTimerId); + + /// Configures behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _STP method. + fn set_expired_timer_policy( + &self, + timer_id: AcpiTimerId, + policy: AlarmExpiredWakePolicy, + ) -> Result<(), DatetimeClockError>; + + /// Query current behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _TIP method. + fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy; + + /// Change the expiry time for the given timer. Analogous to ACPI TAD's _STV method. + fn set_timer_value(&self, timer_id: AcpiTimerId, timer_value: AlarmTimerSeconds) -> Result<(), DatetimeClockError>; + + /// Query the expiry time for the given timer. Analogous to ACPI TAD's _TIV method. + fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result; +} diff --git a/time-alarm-service-relay/Cargo.toml b/time-alarm-service-relay/Cargo.toml new file mode 100644 index 000000000..611ede75e --- /dev/null +++ b/time-alarm-service-relay/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "time-alarm-service-relay" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } +embedded-services.workspace = true +embedded-mcu-hal.workspace = true +num_enum.workspace = true +time-alarm-service-interface.workspace = true + +[features] +defmt = [ + "dep:defmt", + "embedded-mcu-hal/defmt", + "time-alarm-service-interface/defmt", +] +log = ["dep:log", "embedded-services/log"] + +[lints] +workspace = true diff --git a/time-alarm-service-relay/src/lib.rs b/time-alarm-service-relay/src/lib.rs new file mode 100644 index 000000000..ed444a394 --- /dev/null +++ b/time-alarm-service-relay/src/lib.rs @@ -0,0 +1,59 @@ +#![no_std] + +use time_alarm_service_interface::TimeAlarmService; + +mod serialization; +pub use serialization::{AcpiTimeAlarmRequest, AcpiTimeAlarmResponse, AcpiTimeAlarmResult}; + +/// A relay handler that converts MCTP messages into function calls against the time-alarm service. +pub struct TimeAlarmServiceRelayHandler { + service: T, +} + +impl TimeAlarmServiceRelayHandler { + /// Construct a new relay handler that transmits requests to the given time-alarm service. + pub fn new(service: T) -> Self { + Self { service } + } +} + +impl embedded_services::relay::mctp::RelayServiceHandlerTypes for TimeAlarmServiceRelayHandler { + type RequestType = AcpiTimeAlarmRequest; + type ResultType = AcpiTimeAlarmResult; +} + +impl embedded_services::relay::mctp::RelayServiceHandler for TimeAlarmServiceRelayHandler { + async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { + match request { + AcpiTimeAlarmRequest::GetCapabilities => { + Ok(AcpiTimeAlarmResponse::Capabilities(self.service.get_capabilities())) + } + AcpiTimeAlarmRequest::GetRealTime => Ok(AcpiTimeAlarmResponse::RealTime(self.service.get_real_time()?)), + AcpiTimeAlarmRequest::SetRealTime(timestamp) => { + self.service.set_real_time(timestamp)?; + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::GetWakeStatus(timer_id) => Ok(AcpiTimeAlarmResponse::TimerStatus( + self.service.get_wake_status(timer_id), + )), + AcpiTimeAlarmRequest::ClearWakeStatus(timer_id) => { + self.service.clear_wake_status(timer_id); + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::SetExpiredTimerPolicy(timer_id, timer_policy) => { + self.service.set_expired_timer_policy(timer_id, timer_policy)?; + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id) => Ok(AcpiTimeAlarmResponse::WakePolicy( + self.service.get_expired_timer_policy(timer_id), + )), + AcpiTimeAlarmRequest::SetTimerValue(timer_id, timer_value) => { + self.service.set_timer_value(timer_id, timer_value)?; + Ok(AcpiTimeAlarmResponse::OkNoData) + } + AcpiTimeAlarmRequest::GetTimerValue(timer_id) => Ok(AcpiTimeAlarmResponse::TimerSeconds( + self.service.get_timer_value(timer_id)?, + )), + } + } +} diff --git a/time-alarm-service-messages/src/lib.rs b/time-alarm-service-relay/src/serialization.rs similarity index 81% rename from time-alarm-service-messages/src/lib.rs rename to time-alarm-service-relay/src/serialization.rs index 26b6e40f7..7eb3d7abd 100644 --- a/time-alarm-service-messages/src/lib.rs +++ b/time-alarm-service-relay/src/serialization.rs @@ -1,11 +1,9 @@ -#![no_std] - -mod acpi_timestamp; -pub use acpi_timestamp::{AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimeZoneOffset, AcpiTimestamp}; - -use bitfield::bitfield; use core::array::TryFromSliceError; use embedded_services::relay::{MessageSerializationError, SerializableMessage}; +use time_alarm_service_interface::{ + AcpiDaylightSavingsTimeStatus, AcpiTimerId, AcpiTimestamp, AlarmExpiredWakePolicy, AlarmTimerSeconds, + TimeAlarmDeviceCapabilities, TimerStatus, +}; /// Message types for the ACPI Time and Alarm device service. /// These are directly analogous to the ACPI Time and Alarm device methods. @@ -153,97 +151,26 @@ impl SerializableMessage for AcpiTimeAlarmRequest { // ------------------------------------------------- -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AlarmTimerSeconds(pub u32); -impl AlarmTimerSeconds { - pub const DISABLED: Self = Self(u32::MAX); -} - -impl Default for AlarmTimerSeconds { - fn default() -> Self { - Self::DISABLED - } -} - -// ------------------------------------------------- - -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AlarmExpiredWakePolicy(pub u32); -impl AlarmExpiredWakePolicy { - #[allow(dead_code)] - pub const INSTANTLY: Self = Self(0); - pub const NEVER: Self = Self(u32::MAX); -} - -impl Default for AlarmExpiredWakePolicy { - fn default() -> Self { - Self::NEVER - } -} - -// ------------------------------------------------- - -// Timer ID as defined in the ACPI spec. -#[derive(Clone, Copy, Debug, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u32)] -pub enum AcpiTimerId { - AcPower = 0, - DcPower = 1, -} - -impl AcpiTimerId { - pub fn get_other_timer_id(&self) -> Self { - match self { - AcpiTimerId::AcPower => AcpiTimerId::DcPower, - AcpiTimerId::DcPower => AcpiTimerId::AcPower, - } - } -} - -bitfield!( - #[derive(Copy, Clone, Default, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub struct TimerStatus(u32); - impl Debug; - bool; - pub timer_expired, set_timer_expired: 0; - pub timer_triggered_wake, set_timer_triggered_wake: 1; -); - -// ------------------------------------------------- - -bitfield!( - #[derive(Copy, Clone, Default, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub struct TimeAlarmDeviceCapabilities(u32); - impl Debug; - bool; - pub ac_wake_implemented, set_ac_wake_implemented: 0; - pub dc_wake_implemented, set_dc_wake_implemented: 1; - pub realtime_implemented, set_realtime_implemented: 2; - pub realtime_accuracy_in_milliseconds, set_realtime_accuracy_in_milliseconds: 3; - pub get_wake_status_supported, set_get_wake_status_supported: 4; - pub ac_s4_wake_supported, set_ac_s4_wake_supported: 5; - pub ac_s5_wake_supported, set_ac_s5_wake_supported: 6; - pub dc_s4_wake_supported, set_dc_s4_wake_supported: 7; - pub dc_s5_wake_supported, set_dc_s5_wake_supported: 8; -); - -// ------------------------------------------------- - +/// Response types for the ACPI Time and Alarm device service. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcpiTimeAlarmResponse { + /// Response to a _GCP request, containing the capabilities of the time-alarm device. Capabilities(TimeAlarmDeviceCapabilities), + + /// Response to a _GRT request, containing the current real time according to the time-alarm device. RealTime(AcpiTimestamp), + + /// Response to a _GWS request, containing the current status of the specified timer. TimerStatus(TimerStatus), + + /// Response to a _TIP request, containing the current wake policy for the specified timer. WakePolicy(AlarmExpiredWakePolicy), + + /// Response to a _TIV request, containing the current timer value for the specified timer. TimerSeconds(AlarmTimerSeconds), - /// Operation succeeded, but there's no data to return. + /// Operation succeeded, but there's no data to return - response to methods that just return a boolean - _SRT, _CWS, _STP, _STV OkNoData, } diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index 7109bf2f3..d9c933b84 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -17,7 +17,7 @@ embassy-time.workspace = true embedded-mcu-hal.workspace = true embedded-services.workspace = true odp-service-common.workspace = true -time-alarm-service-messages.workspace = true +time-alarm-service-interface.workspace = true zerocopy.workspace = true [features] @@ -27,7 +27,7 @@ defmt = [ "embassy-time/defmt", "embassy-sync/defmt", "embassy-executor/defmt", - "time-alarm-service-messages/defmt", + "time-alarm-service-interface/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 0ff0b9389..08afc019e 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -7,7 +7,7 @@ use embedded_mcu_hal::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; use embedded_services::GlobalRawMutex; use embedded_services::{info, warn}; -use time_alarm_service_messages::*; +use time_alarm_service_interface::*; mod timer; use timer::Timer; @@ -317,37 +317,38 @@ impl<'hw> odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'h } /// Control handle for the time-alarm service. Use this to manipulate the time on the service. +#[derive(Clone, Copy)] pub struct Service<'hw> { inner: &'hw ServiceInner<'hw>, } -impl<'hw> Service<'hw> { - pub fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { +impl<'hw> TimeAlarmService for Service<'hw> { + fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { self.inner.get_capabilities() } /// Query the current time. Analogous to ACPI TAD's _GRT method. - pub fn get_real_time(&self) -> Result { + fn get_real_time(&self) -> Result { self.inner.get_real_time() } /// Change the current time. Analogous to ACPI TAD's _SRT method. - pub fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { + fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { self.inner.set_real_time(timestamp) } /// Query the current wake status. Analogous to ACPI TAD's _GWS method. - pub fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus { + fn get_wake_status(&self, timer_id: AcpiTimerId) -> TimerStatus { self.inner.get_wake_status(timer_id) } /// Clear the current wake status. Analogous to ACPI TAD's _CWS method. - pub fn clear_wake_status(&self, timer_id: AcpiTimerId) { + fn clear_wake_status(&self, timer_id: AcpiTimerId) { self.inner.clear_wake_status(timer_id); } /// Configures behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _STP method. - pub fn set_expired_timer_policy( + fn set_expired_timer_policy( &self, timer_id: AcpiTimerId, policy: AlarmExpiredWakePolicy, @@ -356,21 +357,17 @@ impl<'hw> Service<'hw> { } /// Query current behavior when the timer expires while the system is on the other power source. Analogous to ACPI TAD's _TIP method. - pub fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy { + fn get_expired_timer_policy(&self, timer_id: AcpiTimerId) -> AlarmExpiredWakePolicy { self.inner.get_expired_timer_policy(timer_id) } /// Change the expiry time for the given timer. Analogous to ACPI TAD's _STV method. - pub fn set_timer_value( - &self, - timer_id: AcpiTimerId, - timer_value: AlarmTimerSeconds, - ) -> Result<(), DatetimeClockError> { + fn set_timer_value(&self, timer_id: AcpiTimerId, timer_value: AlarmTimerSeconds) -> Result<(), DatetimeClockError> { self.inner.set_timer_value(timer_id, timer_value) } /// Query the expiry time for the given timer. Analogous to ACPI TAD's _TIV method. - pub fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result { + fn get_timer_value(&self, timer_id: AcpiTimerId) -> Result { self.inner.get_timer_value(timer_id) } } @@ -395,43 +392,3 @@ impl<'hw> odp_service_common::runnable_service::Service<'hw> for Service<'hw> { Ok((Self { inner: service }, Runner { service })) } } - -impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { - type RequestType = AcpiTimeAlarmRequest; - type ResultType = AcpiTimeAlarmResult; -} - -impl<'hw> embedded_services::relay::mctp::RelayServiceHandler for Service<'hw> { - async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { - use time_alarm_service_messages::AcpiTimeAlarmResponse; - match request { - AcpiTimeAlarmRequest::GetCapabilities => Ok(AcpiTimeAlarmResponse::Capabilities(self.get_capabilities())), - AcpiTimeAlarmRequest::GetRealTime => Ok(AcpiTimeAlarmResponse::RealTime(self.get_real_time()?)), - AcpiTimeAlarmRequest::SetRealTime(timestamp) => { - self.set_real_time(timestamp)?; - Ok(AcpiTimeAlarmResponse::OkNoData) - } - AcpiTimeAlarmRequest::GetWakeStatus(timer_id) => { - Ok(AcpiTimeAlarmResponse::TimerStatus(self.get_wake_status(timer_id))) - } - AcpiTimeAlarmRequest::ClearWakeStatus(timer_id) => { - self.clear_wake_status(timer_id); - Ok(AcpiTimeAlarmResponse::OkNoData) - } - AcpiTimeAlarmRequest::SetExpiredTimerPolicy(timer_id, timer_policy) => { - self.set_expired_timer_policy(timer_id, timer_policy)?; - Ok(AcpiTimeAlarmResponse::OkNoData) - } - AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id) => Ok(AcpiTimeAlarmResponse::WakePolicy( - self.get_expired_timer_policy(timer_id), - )), - AcpiTimeAlarmRequest::SetTimerValue(timer_id, timer_value) => { - self.set_timer_value(timer_id, timer_value)?; - Ok(AcpiTimeAlarmResponse::OkNoData) - } - AcpiTimeAlarmRequest::GetTimerValue(timer_id) => { - Ok(AcpiTimeAlarmResponse::TimerSeconds(self.get_timer_value(timer_id)?)) - } - } - } -} diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index 73a92d09c..14359395e 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -8,7 +8,7 @@ mod test { use embedded_mcu_hal::time::{Datetime, DatetimeClock}; use odp_service_common::runnable_service::{Service, ServiceRunner}; - use time_alarm_service_messages as msg; + use time_alarm_service_interface::{AcpiDaylightSavingsTimeStatus, AcpiTimeZone, AcpiTimestamp, TimeAlarmService}; use time_alarm_service::mock::*; @@ -96,10 +96,10 @@ mod test { let begin = service.get_real_time().unwrap(); assert_eq!(begin.datetime.to_unix_time_seconds(), TEST_UNIX_TIME); - let target_timestamp = msg::AcpiTimestamp { + let target_timestamp = AcpiTimestamp { datetime: Datetime::from_unix_time_seconds(TEST_UNIX_TIME), - time_zone: msg::AcpiTimeZone::Unknown, - dst_status: msg::AcpiDaylightSavingsTimeStatus::Adjusted, + time_zone: AcpiTimeZone::Unknown, + dst_status: AcpiDaylightSavingsTimeStatus::Adjusted, }; service.set_real_time(target_timestamp).unwrap(); diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml index 8049d9d28..1bb57b81f 100644 --- a/uart-service/Cargo.toml +++ b/uart-service/Cargo.toml @@ -22,13 +22,6 @@ mctp-rs = { workspace = true } embedded-io-async.workspace = true num_enum.workspace = true -# TODO Service message type crates are a temporary dependency until we can parameterize -# the supported messages types at UART service creation time. -battery-service-messages.workspace = true -debug-service-messages.workspace = true -thermal-service-messages.workspace = true -time-alarm-service-messages.workspace = true - [features] default = [] defmt = [ @@ -38,10 +31,6 @@ defmt = [ "embassy-time/defmt-timestamp-uptime", "embassy-sync/defmt", "mctp-rs/defmt", - "thermal-service-messages/defmt", - "battery-service-messages/defmt", - "debug-service-messages/defmt", - "time-alarm-service-messages/defmt", ] log = ["dep:log", "embedded-services/log", "embassy-time/log"] From 9989647d05f95cef7e79301579a087a828ba7fba Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Sat, 11 Apr 2026 10:28:32 -0700 Subject: [PATCH 58/79] Battery service: Split interface and relay logic into separate crates (#780) This change splits the interface for the battery service into two crates - an 'interface' crate that contains traits and support types for the public interface for the battery service, and a 'relay' crate that's written against the 'interface' crate and handles relaying messages to and from a battery service implementation over MCTP. This split should allow OEMs to substitute their own service implementations if they want to and still reuse our relay code (or write their own relay against the stock service implementation). Additionally, splitting the relay handling from the service implementation should also allow us to add support for different kinds of relay handlers (e.g. HID) without needing to pay the code size cost of having that code on platforms that aren't using it since it's no longer invasive on the service type. This change only moves the pieces of the battery interface that are required for the relay service into a trait; a future change will migrate the pieces that are targeted at other code running on the EC into the trait as well. --- Cargo.lock | 17 +- Cargo.toml | 8 +- battery-service-interface/Cargo.toml | 18 + battery-service-interface/src/lib.rs | 202 ++++++ battery-service-messages/src/lib.rs | 677 ------------------ .../Cargo.toml | 14 +- battery-service-relay/src/lib.rs | 100 +++ battery-service-relay/src/serialization.rs | 611 ++++++++++++++++ battery-service/Cargo.toml | 9 +- battery-service/src/acpi.rs | 150 ++-- battery-service/src/context.rs | 44 +- battery-service/src/device.rs | 2 +- battery-service/src/lib.rs | 107 ++- battery-service/src/mock.rs | 6 +- examples/pico-de-gallo/Cargo.lock | 10 +- examples/rt633/Cargo.lock | 9 +- examples/rt685s-evk/Cargo.lock | 37 +- examples/std/Cargo.lock | 10 +- 18 files changed, 1132 insertions(+), 899 deletions(-) create mode 100644 battery-service-interface/Cargo.toml create mode 100644 battery-service-interface/src/lib.rs delete mode 100644 battery-service-messages/src/lib.rs rename {battery-service-messages => battery-service-relay}/Cargo.toml (68%) create mode 100644 battery-service-relay/src/lib.rs create mode 100644 battery-service-relay/src/serialization.rs diff --git a/Cargo.lock b/Cargo.lock index 3acdfc114..316b90384 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,7 +209,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ - "battery-service-messages", + "battery-service-interface", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -218,18 +218,25 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", - "heapless", "log", - "mctp-rs", "odp-service-common", "power-policy-interface", - "zerocopy", ] [[package]] -name = "battery-service-messages" +name = "battery-service-interface" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-batteries-async", + "log", +] + +[[package]] +name = "battery-service-relay" version = "0.1.0" dependencies = [ + "battery-service-interface", "defmt 0.3.100", "embedded-batteries-async", "embedded-services", diff --git a/Cargo.toml b/Cargo.toml index d3558a57d..b78043344 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ resolver = "2" members = [ "battery-service", - "battery-service-messages", + "battery-service-interface", + "battery-service-relay", "thermal-service", "thermal-service-messages", "cfu-service", @@ -55,7 +56,8 @@ unwrap_used = "deny" [workspace.dependencies] aligned = "0.4" anyhow = "1.0" -battery-service-messages = { path = "./battery-service-messages" } +battery-service-interface = { path = "./battery-service-interface" } +battery-service-relay = { path = "./battery-service-relay" } bitfield = "0.17.0" bitflags = "2.8.0" bitvec = { version = "1.0.1", default-features = false } @@ -74,7 +76,7 @@ embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" embassy-sync = "0.7.2" embassy-time = "0.5.0" embassy-time-driver = "0.2.1" -embedded-batteries-async = "0.3" +embedded-batteries-async = "0.3.4" cfu-service = { path = "./cfu-service" } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } embedded-hal = "1.0" diff --git a/battery-service-interface/Cargo.toml b/battery-service-interface/Cargo.toml new file mode 100644 index 000000000..f73417a88 --- /dev/null +++ b/battery-service-interface/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "battery-service-interface" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } +embedded-batteries-async.workspace = true + +[lints] +workspace = true + +[features] +defmt = ["dep:defmt", "embedded-batteries-async/defmt"] +log = ["dep:log"] diff --git a/battery-service-interface/src/lib.rs b/battery-service-interface/src/lib.rs new file mode 100644 index 000000000..f93f5caab --- /dev/null +++ b/battery-service-interface/src/lib.rs @@ -0,0 +1,202 @@ +#![no_std] + +pub use embedded_batteries_async::acpi::{ + BatteryState, BatterySwapCapability, BatteryTechnology, Bct, BctReturnResult, Bma, Bmc, BmcControlFlags, Bmd, + BmdCapabilityFlags, BmdStatusFlags, Bms, Bpc, Bps, Bpt, BstReturn, Btm, BtmReturnResult, Btp, PowerSource, + PowerSourceState, PowerThresholdSupport, PowerUnit, PsrReturn, StaReturn, +}; + +/// Standard Battery Service Model Number String Size +pub const STD_BIX_MODEL_SIZE: usize = 8; +/// Standard Battery Service Serial Number String Size +pub const STD_BIX_SERIAL_SIZE: usize = 8; +/// Standard Battery Service Battery Type String Size +pub const STD_BIX_BATTERY_SIZE: usize = 8; +/// Standard Battery Service OEM Info String Size +pub const STD_BIX_OEM_SIZE: usize = 8; +/// Standard Power Policy Service Model Number String Size +pub const STD_PIF_MODEL_SIZE: usize = 8; +/// Standard Power Policy Serial Number String Size +pub const STD_PIF_SERIAL_SIZE: usize = 8; +/// Standard Power Policy Service OEM Info String Size +pub const STD_PIF_OEM_SIZE: usize = 8; + +#[derive(PartialEq, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BixFixedStrings { + /// Revision of the BIX structure. Current revision is 1. + pub revision: u32, + /// Unit used for capacity and rate values. + pub power_unit: PowerUnit, + /// Design capacity of the battery (in mWh or mAh). + pub design_capacity: u32, + /// Last full charge capacity (in mWh or mAh). + pub last_full_charge_capacity: u32, + /// Battery technology type. + pub battery_technology: BatteryTechnology, + /// Design voltage (in mV). + pub design_voltage: u32, + /// Warning capacity threshold (in mWh or mAh). + pub design_cap_of_warning: u32, + /// Low capacity threshold (in mWh or mAh). + pub design_cap_of_low: u32, + /// Number of charge/discharge cycles. + pub cycle_count: u32, + /// Measurement accuracy in thousandths of a percent (e.g., 80000 = 80.000%). + pub measurement_accuracy: u32, + /// Maximum supported sampling time (in ms). + pub max_sampling_time: u32, + /// Minimum supported sampling time (in ms). + pub min_sampling_time: u32, + /// Maximum supported averaging interval (in ms). + pub max_averaging_interval: u32, + /// Minimum supported averaging interval (in ms). + pub min_averaging_interval: u32, + /// Capacity granularity between low and warning (in mWh or mAh). + pub battery_capacity_granularity_1: u32, + /// Capacity granularity between warning and full (in mWh or mAh). + pub battery_capacity_granularity_2: u32, + /// OEM-specific model number (ASCIIZ). + pub model_number: [u8; STD_BIX_MODEL_SIZE], + /// OEM-specific serial number (ASCIIZ). + pub serial_number: [u8; STD_BIX_SERIAL_SIZE], + /// OEM-specific battery type (ASCIIZ). + pub battery_type: [u8; STD_BIX_BATTERY_SIZE], + /// OEM-specific information (ASCIIZ). + pub oem_info: [u8; STD_BIX_OEM_SIZE], + /// Battery swapping capability. + pub battery_swapping_capability: BatterySwapCapability, +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PifFixedStrings { + /// Bitfield describing the state and characteristics of the power source. + pub power_source_state: PowerSourceState, + /// Maximum rated output power in milliwatts (mW). + /// + /// 0xFFFFFFFF indicates the value is unavailable. + pub max_output_power: u32, + /// Maximum rated input power in milliwatts (mW). + /// + /// 0xFFFFFFFF indicates the value is unavailable. + pub max_input_power: u32, + /// OEM-specific model number (ASCIIZ). Empty string if not supported. + pub model_number: [u8; STD_PIF_MODEL_SIZE], + /// OEM-specific serial number (ASCIIZ). Empty string if not supported. + pub serial_number: [u8; STD_PIF_SERIAL_SIZE], + /// OEM-specific information (ASCIIZ). Empty string if not supported. + pub oem_info: [u8; STD_PIF_OEM_SIZE], +} + +/// Fuel gauge ID +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DeviceId(pub u8); + +pub trait BatteryService { + /// Queries the estimated time remaining until the battery reaches the specified charge level. Corresponds to ACPI's _BCT method + fn battery_charge_time( + &self, + battery_id: DeviceId, + charge_level: Bct, + ) -> impl core::future::Future>; + + /// Returns static information about the battery. Corresponds to ACPI's _BIX method. + fn battery_info( + &self, + battery_id: DeviceId, + ) -> impl core::future::Future>; + + /// Sets the averaging interval of battery capacity measurement in milliseconds. Corresponds to ACPI's _BMA method. + fn set_battery_measurement_averaging_interval( + &self, + battery_id: DeviceId, + bma: Bma, + ) -> impl core::future::Future>; + + /// Battery maintenance control. Corresponds to ACPI's _BMC method. + fn battery_maintenance_control( + &self, + battery_id: DeviceId, + bmc: Bmc, + ) -> impl core::future::Future>; + + /// Retrieves battery maintenance data. Corresponds to ACPI's _BMD method. + fn battery_maintenance_data( + &self, + battery_id: DeviceId, + ) -> impl core::future::Future>; + + /// Sets the battery measurement sampling time in milliseconds. Corresponds to ACPI's _BMS method. + fn set_battery_measurement_sampling_time( + &self, + battery_id: DeviceId, + battery_measurement_sampling: Bms, + ) -> impl core::future::Future>; + + /// Queries the current power characteristics of the battery. Corresponds to ACPI's _BPC method. + fn battery_power_characteristics( + &self, + battery_id: DeviceId, + ) -> impl core::future::Future>; + + /// Queries the current state of the battery. Corresponds to ACPI's _BPS method. + fn battery_power_state( + &self, + battery_id: DeviceId, + ) -> impl core::future::Future>; + + /// Sets battery power threshold. Corresponds to ACPI's _BPT method. + fn set_battery_power_threshold( + &self, + battery_id: DeviceId, + power_threshold: Bpt, + ) -> impl core::future::Future>; + + /// Queries the battery's current estimated remaining capacity. Corresponds to ACPI's _BST method. + fn battery_status( + &self, + battery_id: DeviceId, + ) -> impl core::future::Future>; + + /// Queries the estimated time remaining until the battery is fully discharged at the current discharge rate. Corresponds to ACPI's _BTM method. + fn battery_time_to_empty( + &self, + battery_id: DeviceId, + battery_discharge_rate: Btm, + ) -> impl core::future::Future>; + + /// Sets a battery trip point. Corresponds to ACPI's _BTP method. + fn set_battery_trip_point( + &self, + battery_id: DeviceId, + btp: Btp, + ) -> impl core::future::Future>; + + /// Queries whether the battery is currently in use (i.e., providing power to the system). Corresponds to ACPI's _PSR method. + fn is_in_use(&self, battery_id: DeviceId) -> impl core::future::Future>; + + /// Queries information about the battery's power source. Corresponds to ACPI's _PIF method. + fn power_source_information( + &self, + power_source_id: DeviceId, + ) -> impl core::future::Future>; + + /// Queries the battery's status. Corresponds to ACPI's _STA method. + fn device_status( + &self, + battery_id: DeviceId, + ) -> impl core::future::Future>; +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Errors that can occur when interacting with the battery service. +pub enum BatteryError { + /// The specified battery ID does not correspond to any known battery. + UnknownDeviceId, + + /// An unknown error occurred while processing the request. + UnspecifiedFailure, +} diff --git a/battery-service-messages/src/lib.rs b/battery-service-messages/src/lib.rs deleted file mode 100644 index c00bf352e..000000000 --- a/battery-service-messages/src/lib.rs +++ /dev/null @@ -1,677 +0,0 @@ -#![no_std] - -use embedded_batteries_async::acpi::ThresholdId; -pub use embedded_batteries_async::acpi::{ - BatteryState, BatterySwapCapability, BatteryTechnology, Bct, BctReturnResult, Bma, Bmc, BmcControlFlags, Bmd, - BmdCapabilityFlags, BmdStatusFlags, Bms, Bpc, Bps, Bpt, BstReturn, Btm, BtmReturnResult, Btp, PowerSource, - PowerSourceState, PowerThresholdSupport, PowerUnit, PsrReturn, StaReturn, -}; -use embedded_services::relay::{MessageSerializationError, SerializableMessage}; - -// Unfortunately `TryFrom` is not implemented by embedded-batteries for these types - -/// Attempt to convert a `u32` to a `PowerUnit`. -pub fn power_unit_try_from_u32(value: u32) -> Result { - match value { - 0 => Ok(PowerUnit::MilliWatts), - 1 => Ok(PowerUnit::MilliAmps), - _ => Err(MessageSerializationError::InvalidPayload("Invalid PowerUnit")), - } -} - -/// Attempt to convert a `u32` to a `BatteryTechnology`. -pub fn bat_tech_try_from_u32(value: u32) -> Result { - match value { - 0 => Ok(BatteryTechnology::Primary), - 1 => Ok(BatteryTechnology::Secondary), - _ => Err(MessageSerializationError::InvalidPayload("Invalid BatteryTechnology")), - } -} - -/// Attempt to convert a `u32` to a `BatterySwapCapability`. -pub fn bat_swap_try_from_u32(value: u32) -> Result { - match value { - 0 => Ok(BatterySwapCapability::NonSwappable), - 1 => Ok(BatterySwapCapability::ColdSwappable), - 2 => Ok(BatterySwapCapability::HotSwappable), - _ => Err(MessageSerializationError::InvalidPayload("Invalid BatteryTechnology")), - } -} - -/// Attempt to convert a `u32` to a `ThresholdId`. -pub fn thres_id_try_from_u32(value: u32) -> Result { - match value { - 0 => Ok(ThresholdId::ClearAll), - 1 => Ok(ThresholdId::InstantaneousPeakPower), - 2 => Ok(ThresholdId::SustainablePeakPower), - _ => Err(MessageSerializationError::InvalidPayload("Invalid ThresholdId")), - } -} - -/// Attempt to convert a `u32` to a `PowerSource`. -pub fn pwr_src_try_from_u32(value: u32) -> Result { - match value { - 0 => Ok(PowerSource::Offline), - 1 => Ok(PowerSource::Online), - _ => Err(MessageSerializationError::InvalidPayload("Invalid PowerSource")), - } -} - -/// Standard Battery Service Model Number String Size -pub const STD_BIX_MODEL_SIZE: usize = 8; -/// Standard Battery Service Serial Number String Size -pub const STD_BIX_SERIAL_SIZE: usize = 8; -/// Standard Battery Service Battery Type String Size -pub const STD_BIX_BATTERY_SIZE: usize = 8; -/// Standard Battery Service OEM Info String Size -pub const STD_BIX_OEM_SIZE: usize = 8; -/// Standard Power Policy Service Model Number String Size -pub const STD_PIF_MODEL_SIZE: usize = 8; -/// Standard Power Policy Serial Number String Size -pub const STD_PIF_SERIAL_SIZE: usize = 8; -/// Standard Power Policy Service OEM Info String Size -pub const STD_PIF_OEM_SIZE: usize = 8; - -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] -#[repr(u16)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// ACPI Battery Methods -enum BatteryCmd { - /// Battery Information eXtended - GetBix = 1, - /// Battery Status - GetBst = 2, - /// Power Source - GetPsr = 3, - /// Power source InFormation - GetPif = 4, - /// Battery Power State - GetBps = 5, - /// Battery Trip Point - SetBtp = 6, - /// Battery Power Threshold - SetBpt = 7, - /// Battery Power Characteristics - GetBpc = 8, - /// Battery Maintenance Control - SetBmc = 9, - /// Battery Maintenance Data - GetBmd = 10, - /// Battery Charge Time - GetBct = 11, - /// Battery Time - GetBtm = 12, - /// Battery Measurement Sampling Time - SetBms = 13, - /// Battery Measurement Averaging Interval - SetBma = 14, - /// Device Status - GetSta = 15, -} - -impl From<&AcpiBatteryRequest> for BatteryCmd { - fn from(request: &AcpiBatteryRequest) -> Self { - match request { - AcpiBatteryRequest::BatteryGetBixRequest { .. } => BatteryCmd::GetBix, - AcpiBatteryRequest::BatteryGetBstRequest { .. } => BatteryCmd::GetBst, - AcpiBatteryRequest::BatteryGetPsrRequest { .. } => BatteryCmd::GetPsr, - AcpiBatteryRequest::BatteryGetPifRequest { .. } => BatteryCmd::GetPif, - AcpiBatteryRequest::BatteryGetBpsRequest { .. } => BatteryCmd::GetBps, - AcpiBatteryRequest::BatterySetBtpRequest { .. } => BatteryCmd::SetBtp, - AcpiBatteryRequest::BatterySetBptRequest { .. } => BatteryCmd::SetBpt, - AcpiBatteryRequest::BatteryGetBpcRequest { .. } => BatteryCmd::GetBpc, - AcpiBatteryRequest::BatterySetBmcRequest { .. } => BatteryCmd::SetBmc, - AcpiBatteryRequest::BatteryGetBmdRequest { .. } => BatteryCmd::GetBmd, - AcpiBatteryRequest::BatteryGetBctRequest { .. } => BatteryCmd::GetBct, - AcpiBatteryRequest::BatteryGetBtmRequest { .. } => BatteryCmd::GetBtm, - AcpiBatteryRequest::BatterySetBmsRequest { .. } => BatteryCmd::SetBms, - AcpiBatteryRequest::BatterySetBmaRequest { .. } => BatteryCmd::SetBma, - AcpiBatteryRequest::BatteryGetStaRequest { .. } => BatteryCmd::GetSta, - } - } -} - -impl From<&AcpiBatteryResponse> for BatteryCmd { - fn from(response: &AcpiBatteryResponse) -> Self { - match response { - AcpiBatteryResponse::BatteryGetBixResponse { .. } => BatteryCmd::GetBix, - AcpiBatteryResponse::BatteryGetBstResponse { .. } => BatteryCmd::GetBst, - AcpiBatteryResponse::BatteryGetPsrResponse { .. } => BatteryCmd::GetPsr, - AcpiBatteryResponse::BatteryGetPifResponse { .. } => BatteryCmd::GetPif, - AcpiBatteryResponse::BatteryGetBpsResponse { .. } => BatteryCmd::GetBps, - AcpiBatteryResponse::BatterySetBtpResponse { .. } => BatteryCmd::SetBtp, - AcpiBatteryResponse::BatterySetBptResponse { .. } => BatteryCmd::SetBpt, - AcpiBatteryResponse::BatteryGetBpcResponse { .. } => BatteryCmd::GetBpc, - AcpiBatteryResponse::BatterySetBmcResponse { .. } => BatteryCmd::SetBmc, - AcpiBatteryResponse::BatteryGetBmdResponse { .. } => BatteryCmd::GetBmd, - AcpiBatteryResponse::BatteryGetBctResponse { .. } => BatteryCmd::GetBct, - AcpiBatteryResponse::BatteryGetBtmResponse { .. } => BatteryCmd::GetBtm, - AcpiBatteryResponse::BatterySetBmsResponse { .. } => BatteryCmd::SetBms, - AcpiBatteryResponse::BatterySetBmaResponse { .. } => BatteryCmd::SetBma, - AcpiBatteryResponse::BatteryGetStaResponse { .. } => BatteryCmd::GetSta, - } - } -} - -const BIX_MODEL_NUM_START_IDX: usize = 64; -const BIX_MODEL_NUM_END_IDX: usize = BIX_MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; -const BIX_SERIAL_NUM_START_IDX: usize = BIX_MODEL_NUM_END_IDX; -const BIX_SERIAL_NUM_END_IDX: usize = BIX_SERIAL_NUM_START_IDX + STD_BIX_SERIAL_SIZE; -const BIX_BATTERY_TYPE_START_IDX: usize = BIX_SERIAL_NUM_END_IDX; -const BIX_BATTERY_TYPE_END_IDX: usize = BIX_BATTERY_TYPE_START_IDX + STD_BIX_BATTERY_SIZE; -const BIX_OEM_INFO_START_IDX: usize = BIX_BATTERY_TYPE_END_IDX; -const BIX_OEM_INFO_END_IDX: usize = BIX_OEM_INFO_START_IDX + STD_BIX_OEM_SIZE; - -#[derive(PartialEq, Clone, Copy, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BixFixedStrings { - /// Revision of the BIX structure. Current revision is 1. - pub revision: u32, - /// Unit used for capacity and rate values. - pub power_unit: PowerUnit, - /// Design capacity of the battery (in mWh or mAh). - pub design_capacity: u32, - /// Last full charge capacity (in mWh or mAh). - pub last_full_charge_capacity: u32, - /// Battery technology type. - pub battery_technology: BatteryTechnology, - /// Design voltage (in mV). - pub design_voltage: u32, - /// Warning capacity threshold (in mWh or mAh). - pub design_cap_of_warning: u32, - /// Low capacity threshold (in mWh or mAh). - pub design_cap_of_low: u32, - /// Number of charge/discharge cycles. - pub cycle_count: u32, - /// Measurement accuracy in thousandths of a percent (e.g., 80000 = 80.000%). - pub measurement_accuracy: u32, - /// Maximum supported sampling time (in ms). - pub max_sampling_time: u32, - /// Minimum supported sampling time (in ms). - pub min_sampling_time: u32, - /// Maximum supported averaging interval (in ms). - pub max_averaging_interval: u32, - /// Minimum supported averaging interval (in ms). - pub min_averaging_interval: u32, - /// Capacity granularity between low and warning (in mWh or mAh). - pub battery_capacity_granularity_1: u32, - /// Capacity granularity between warning and full (in mWh or mAh). - pub battery_capacity_granularity_2: u32, - /// OEM-specific model number (ASCIIZ). - pub model_number: [u8; STD_BIX_MODEL_SIZE], - /// OEM-specific serial number (ASCIIZ). - pub serial_number: [u8; STD_BIX_SERIAL_SIZE], - /// OEM-specific battery type (ASCIIZ). - pub battery_type: [u8; STD_BIX_BATTERY_SIZE], - /// OEM-specific information (ASCIIZ). - pub oem_info: [u8; STD_BIX_OEM_SIZE], - /// Battery swapping capability. - pub battery_swapping_capability: BatterySwapCapability, -} - -// TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? -impl BixFixedStrings { - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result { - if dst_slice.len() < BIX_OEM_INFO_END_IDX { - return Err(MessageSerializationError::BufferTooSmall); - } - - Ok(safe_put_dword(dst_slice, 0, self.revision)? - + safe_put_dword(dst_slice, 4, self.power_unit.into())? - + safe_put_dword(dst_slice, 8, self.design_capacity)? - + safe_put_dword(dst_slice, 12, self.last_full_charge_capacity)? - + safe_put_dword(dst_slice, 16, self.battery_technology.into())? - + safe_put_dword(dst_slice, 20, self.design_voltage)? - + safe_put_dword(dst_slice, 24, self.design_cap_of_warning)? - + safe_put_dword(dst_slice, 28, self.design_cap_of_low)? - + safe_put_dword(dst_slice, 32, self.cycle_count)? - + safe_put_dword(dst_slice, 36, self.measurement_accuracy)? - + safe_put_dword(dst_slice, 40, self.max_sampling_time)? - + safe_put_dword(dst_slice, 44, self.min_sampling_time)? - + safe_put_dword(dst_slice, 48, self.max_averaging_interval)? - + safe_put_dword(dst_slice, 52, self.min_averaging_interval)? - + safe_put_dword(dst_slice, 56, self.battery_capacity_granularity_1)? - + safe_put_dword(dst_slice, 60, self.battery_capacity_granularity_2)? - + safe_put_bytes(dst_slice, BIX_MODEL_NUM_START_IDX, &self.model_number)? - + safe_put_bytes(dst_slice, BIX_SERIAL_NUM_START_IDX, &self.serial_number)? - + safe_put_bytes(dst_slice, BIX_BATTERY_TYPE_START_IDX, &self.battery_type)? - + safe_put_bytes(dst_slice, BIX_OEM_INFO_START_IDX, &self.oem_info)? - + safe_put_dword(dst_slice, BIX_OEM_INFO_END_IDX, self.battery_swapping_capability.into())?) - } - - pub fn from_bytes(src_slice: &[u8]) -> Result { - Ok(Self { - revision: safe_get_dword(src_slice, 0)?, - power_unit: power_unit_try_from_u32(safe_get_dword(src_slice, 4)?)?, - design_capacity: safe_get_dword(src_slice, 8)?, - last_full_charge_capacity: safe_get_dword(src_slice, 12)?, - battery_technology: bat_tech_try_from_u32(safe_get_dword(src_slice, 16)?)?, - design_voltage: safe_get_dword(src_slice, 20)?, - design_cap_of_warning: safe_get_dword(src_slice, 24)?, - design_cap_of_low: safe_get_dword(src_slice, 28)?, - cycle_count: safe_get_dword(src_slice, 32)?, - measurement_accuracy: safe_get_dword(src_slice, 36)?, - max_sampling_time: safe_get_dword(src_slice, 40)?, - min_sampling_time: safe_get_dword(src_slice, 44)?, - max_averaging_interval: safe_get_dword(src_slice, 48)?, - min_averaging_interval: safe_get_dword(src_slice, 52)?, - battery_capacity_granularity_1: safe_get_dword(src_slice, 56)?, - battery_capacity_granularity_2: safe_get_dword(src_slice, 60)?, - model_number: safe_get_bytes::(src_slice, BIX_MODEL_NUM_START_IDX)?, - serial_number: safe_get_bytes::(src_slice, BIX_SERIAL_NUM_START_IDX)?, - battery_type: safe_get_bytes::(src_slice, BIX_BATTERY_TYPE_START_IDX)?, - oem_info: safe_get_bytes::(src_slice, BIX_OEM_INFO_START_IDX)?, - battery_swapping_capability: bat_swap_try_from_u32(safe_get_dword(src_slice, BIX_OEM_INFO_END_IDX)?)?, - }) - } -} - -const PIF_MODEL_NUM_START_IDX: usize = 12; -const PIF_MODEL_NUM_END_IDX: usize = PIF_MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; -const PIF_SERIAL_NUM_START_IDX: usize = PIF_MODEL_NUM_END_IDX; -const PIF_SERIAL_NUM_END_IDX: usize = PIF_SERIAL_NUM_START_IDX + STD_BIX_SERIAL_SIZE; -const PIF_OEM_INFO_START_IDX: usize = PIF_SERIAL_NUM_END_IDX; -const PIF_OEM_INFO_END_IDX: usize = PIF_OEM_INFO_START_IDX + STD_BIX_OEM_SIZE; - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PifFixedStrings { - /// Bitfield describing the state and characteristics of the power source. - pub power_source_state: PowerSourceState, - /// Maximum rated output power in milliwatts (mW). - /// - /// 0xFFFFFFFF indicates the value is unavailable. - pub max_output_power: u32, - /// Maximum rated input power in milliwatts (mW). - /// - /// 0xFFFFFFFF indicates the value is unavailable. - pub max_input_power: u32, - /// OEM-specific model number (ASCIIZ). Empty string if not supported. - pub model_number: [u8; STD_BIX_MODEL_SIZE], - /// OEM-specific serial number (ASCIIZ). Empty string if not supported. - pub serial_number: [u8; STD_BIX_SERIAL_SIZE], - /// OEM-specific information (ASCIIZ). Empty string if not supported. - pub oem_info: [u8; STD_BIX_OEM_SIZE], -} - -impl PifFixedStrings { - pub fn to_bytes(self, dst_slice: &mut [u8]) -> Result { - if dst_slice.len() < PIF_OEM_INFO_END_IDX { - return Err(MessageSerializationError::BufferTooSmall); - } - - Ok(safe_put_dword(dst_slice, 0, self.power_source_state.bits())? - + safe_put_dword(dst_slice, 4, self.max_output_power)? - + safe_put_dword(dst_slice, 8, self.max_input_power)? - + safe_put_bytes(dst_slice, PIF_MODEL_NUM_START_IDX, &self.model_number)? - + safe_put_bytes(dst_slice, PIF_SERIAL_NUM_START_IDX, &self.serial_number)? - + safe_put_bytes(dst_slice, PIF_OEM_INFO_START_IDX, &self.oem_info)?) - } - - pub fn from_bytes(src_slice: &[u8]) -> Result { - Ok(Self { - power_source_state: PowerSourceState::from_bits(safe_get_dword(src_slice, 0)?) - .ok_or(MessageSerializationError::InvalidPayload("Invalid PowerSourceState"))?, - max_output_power: safe_get_dword(src_slice, 4)?, - max_input_power: safe_get_dword(src_slice, 8)?, - model_number: safe_get_bytes::(src_slice, PIF_MODEL_NUM_START_IDX)?, - serial_number: safe_get_bytes::(src_slice, PIF_SERIAL_NUM_START_IDX)?, - oem_info: safe_get_bytes::(src_slice, PIF_OEM_INFO_START_IDX)?, - }) - } -} - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum AcpiBatteryRequest { - BatteryGetBixRequest { battery_id: u8 }, - BatteryGetBstRequest { battery_id: u8 }, - BatteryGetPsrRequest { battery_id: u8 }, - BatteryGetPifRequest { battery_id: u8 }, - BatteryGetBpsRequest { battery_id: u8 }, - BatterySetBtpRequest { battery_id: u8, btp: Btp }, - BatterySetBptRequest { battery_id: u8, bpt: Bpt }, - BatteryGetBpcRequest { battery_id: u8 }, - BatterySetBmcRequest { battery_id: u8, bmc: Bmc }, - BatteryGetBmdRequest { battery_id: u8 }, - BatteryGetBctRequest { battery_id: u8, bct: Bct }, - BatteryGetBtmRequest { battery_id: u8, btm: Btm }, - BatterySetBmsRequest { battery_id: u8, bms: Bms }, - BatterySetBmaRequest { battery_id: u8, bma: Bma }, - BatteryGetStaRequest { battery_id: u8 }, -} - -impl SerializableMessage for AcpiBatteryRequest { - fn serialize(self, buffer: &mut [u8]) -> Result { - match self { - Self::BatteryGetBixRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatteryGetBstRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatteryGetPsrRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatteryGetPifRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatteryGetBpsRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatterySetBtpRequest { battery_id, btp } => { - Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, btp.trip_point)?) - } - Self::BatterySetBptRequest { battery_id, bpt } => Ok(safe_put_u8(buffer, 0, battery_id)? - + safe_put_dword(buffer, 1, bpt.revision)? - + safe_put_dword(buffer, 5, bpt.threshold_id as u32)? - + safe_put_dword(buffer, 9, bpt.threshold_value)?), - Self::BatteryGetBpcRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatterySetBmcRequest { battery_id, bmc } => { - Ok(safe_put_u8(buffer, 0, battery_id)? - + safe_put_dword(buffer, 1, bmc.maintenance_control_flags.bits())?) - } - Self::BatteryGetBmdRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - Self::BatteryGetBctRequest { battery_id, bct } => { - Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bct.charge_level_percent)?) - } - Self::BatteryGetBtmRequest { battery_id, btm } => { - Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, btm.discharge_rate)?) - } - Self::BatterySetBmsRequest { battery_id, bms } => { - Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bms.sampling_time_ms)?) - } - Self::BatterySetBmaRequest { battery_id, bma } => { - Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bma.averaging_interval_ms)?) - } - Self::BatteryGetStaRequest { battery_id } => safe_put_u8(buffer, 0, battery_id), - } - } - - fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { - Ok( - match BatteryCmd::try_from(discriminant) - .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? - { - BatteryCmd::GetBix => Self::BatteryGetBixRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::GetBst => Self::BatteryGetBstRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::GetPsr => Self::BatteryGetPsrRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::GetPif => Self::BatteryGetPifRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::GetBps => Self::BatteryGetBpsRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::SetBtp => Self::BatterySetBtpRequest { - battery_id: safe_get_u8(buffer, 0)?, - btp: Btp { - trip_point: safe_get_dword(buffer, 1)?, - }, - }, - BatteryCmd::SetBpt => Self::BatterySetBptRequest { - battery_id: safe_get_u8(buffer, 0)?, - bpt: Bpt { - revision: safe_get_dword(buffer, 1)?, - threshold_id: thres_id_try_from_u32(safe_get_dword(buffer, 5)?)?, - threshold_value: safe_get_dword(buffer, 9)?, - }, - }, - BatteryCmd::GetBpc => Self::BatteryGetBpcRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::SetBmc => Self::BatterySetBmcRequest { - battery_id: safe_get_u8(buffer, 0)?, - bmc: Bmc { - maintenance_control_flags: BmcControlFlags::from_bits_retain(safe_get_dword(buffer, 1)?), - }, - }, - BatteryCmd::GetBmd => Self::BatteryGetBmdRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - BatteryCmd::GetBct => Self::BatteryGetBctRequest { - battery_id: safe_get_u8(buffer, 0)?, - bct: Bct { - charge_level_percent: safe_get_dword(buffer, 1)?, - }, - }, - BatteryCmd::GetBtm => Self::BatteryGetBtmRequest { - battery_id: safe_get_u8(buffer, 0)?, - btm: Btm { - discharge_rate: safe_get_dword(buffer, 1)?, - }, - }, - BatteryCmd::SetBms => Self::BatterySetBmsRequest { - battery_id: safe_get_u8(buffer, 0)?, - bms: Bms { - sampling_time_ms: safe_get_dword(buffer, 1)?, - }, - }, - BatteryCmd::SetBma => Self::BatterySetBmaRequest { - battery_id: safe_get_u8(buffer, 0)?, - bma: Bma { - averaging_interval_ms: safe_get_dword(buffer, 1)?, - }, - }, - BatteryCmd::GetSta => Self::BatteryGetStaRequest { - battery_id: safe_get_u8(buffer, 0)?, - }, - }, - ) - } - - fn discriminant(&self) -> u16 { - BatteryCmd::from(self).into() - } -} - -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum AcpiBatteryResponse { - BatteryGetBixResponse { bix: BixFixedStrings }, - BatteryGetBstResponse { bst: BstReturn }, - BatteryGetPsrResponse { psr: PsrReturn }, - BatteryGetPifResponse { pif: PifFixedStrings }, - BatteryGetBpsResponse { bps: Bps }, - BatterySetBtpResponse {}, - BatterySetBptResponse {}, - BatteryGetBpcResponse { bpc: Bpc }, - BatterySetBmcResponse {}, - BatteryGetBmdResponse { bmd: Bmd }, - BatteryGetBctResponse { bct_response: BctReturnResult }, - BatteryGetBtmResponse { btm_response: BtmReturnResult }, - BatterySetBmsResponse { status: u32 }, - BatterySetBmaResponse { status: u32 }, - BatteryGetStaResponse { sta: StaReturn }, -} - -impl SerializableMessage for AcpiBatteryResponse { - fn serialize(self, buffer: &mut [u8]) -> Result { - match self { - Self::BatteryGetBixResponse { bix } => bix.to_bytes(buffer), - Self::BatteryGetBstResponse { bst } => Ok(safe_put_dword(buffer, 0, bst.battery_state.bits())? - + safe_put_dword(buffer, 4, bst.battery_present_rate)? - + safe_put_dword(buffer, 8, bst.battery_remaining_capacity)? - + safe_put_dword(buffer, 12, bst.battery_present_voltage)?), - Self::BatteryGetPsrResponse { psr } => safe_put_dword(buffer, 0, psr.power_source.into()), - - Self::BatteryGetPifResponse { pif } => pif.to_bytes(buffer), - Self::BatteryGetBpsResponse { bps } => Ok(safe_put_dword(buffer, 0, bps.revision)? - + safe_put_dword(buffer, 4, bps.instantaneous_peak_power_level)? - + safe_put_dword(buffer, 8, bps.instantaneous_peak_power_period)? - + safe_put_dword(buffer, 12, bps.sustainable_peak_power_level)? - + safe_put_dword(buffer, 16, bps.sustainable_peak_power_period)?), - Self::BatterySetBtpResponse {} => Ok(0), - Self::BatterySetBptResponse {} => Ok(0), - Self::BatteryGetBpcResponse { bpc } => Ok(safe_put_dword(buffer, 0, bpc.revision)? - + safe_put_dword(buffer, 4, bpc.power_threshold_support.bits())? - + safe_put_dword(buffer, 8, bpc.max_instantaneous_peak_power_threshold)? - + safe_put_dword(buffer, 12, bpc.max_sustainable_peak_power_threshold)?), - Self::BatterySetBmcResponse {} => Ok(0), - Self::BatteryGetBmdResponse { bmd } => Ok(safe_put_dword(buffer, 0, bmd.status_flags.bits())? - + safe_put_dword(buffer, 4, bmd.capability_flags.bits())? - + safe_put_dword(buffer, 8, bmd.recalibrate_count)? - + safe_put_dword(buffer, 12, bmd.quick_recalibrate_time)? - + safe_put_dword(buffer, 16, bmd.slow_recalibrate_time)?), - Self::BatteryGetBctResponse { bct_response } => safe_put_dword(buffer, 0, bct_response.into()), - Self::BatteryGetBtmResponse { btm_response } => safe_put_dword(buffer, 0, btm_response.into()), - Self::BatterySetBmsResponse { status } => safe_put_dword(buffer, 0, status), - Self::BatterySetBmaResponse { status } => safe_put_dword(buffer, 0, status), - Self::BatteryGetStaResponse { sta } => safe_put_dword(buffer, 0, sta.bits()), - } - } - - fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { - Ok( - match BatteryCmd::try_from(discriminant) - .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? - { - BatteryCmd::GetBix => Self::BatteryGetBixResponse { - bix: BixFixedStrings::from_bytes(buffer)?, - }, - BatteryCmd::GetBst => { - let bst = BstReturn { - battery_state: BatteryState::from_bits(safe_get_dword(buffer, 0)?) - .ok_or(MessageSerializationError::BufferTooSmall)?, - battery_present_rate: safe_get_dword(buffer, 4)?, - battery_remaining_capacity: safe_get_dword(buffer, 8)?, - battery_present_voltage: safe_get_dword(buffer, 12)?, - }; - Self::BatteryGetBstResponse { bst } - } - BatteryCmd::GetPsr => Self::BatteryGetPsrResponse { - psr: PsrReturn { - power_source: pwr_src_try_from_u32(safe_get_dword(buffer, 0)?)?, - }, - }, - BatteryCmd::GetPif => Self::BatteryGetPifResponse { - pif: PifFixedStrings::from_bytes(buffer)?, - }, - BatteryCmd::GetBps => Self::BatteryGetBpsResponse { - bps: Bps { - revision: safe_get_dword(buffer, 0)?, - instantaneous_peak_power_level: safe_get_dword(buffer, 4)?, - instantaneous_peak_power_period: safe_get_dword(buffer, 8)?, - sustainable_peak_power_level: safe_get_dword(buffer, 12)?, - sustainable_peak_power_period: safe_get_dword(buffer, 16)?, - }, - }, - BatteryCmd::SetBtp => Self::BatterySetBtpResponse {}, - BatteryCmd::SetBpt => Self::BatterySetBptResponse {}, - BatteryCmd::GetBpc => Self::BatteryGetBpcResponse { - bpc: Bpc { - revision: safe_get_dword(buffer, 0)?, - power_threshold_support: PowerThresholdSupport::from_bits(safe_get_dword(buffer, 4)?) - .ok_or(MessageSerializationError::InvalidPayload("Invalid BpcThresholdSupport"))?, - max_instantaneous_peak_power_threshold: safe_get_dword(buffer, 8)?, - max_sustainable_peak_power_threshold: safe_get_dword(buffer, 12)?, - }, - }, - BatteryCmd::SetBmc => Self::BatterySetBmcResponse {}, - BatteryCmd::GetBmd => Self::BatteryGetBmdResponse { - bmd: Bmd { - status_flags: BmdStatusFlags::from_bits(safe_get_dword(buffer, 0)?) - .ok_or(MessageSerializationError::InvalidPayload("Invalid BmdStatusFlags"))?, - capability_flags: BmdCapabilityFlags::from_bits(safe_get_dword(buffer, 4)?) - .ok_or(MessageSerializationError::InvalidPayload("Invalid BmdCapabilityFlags"))?, - recalibrate_count: safe_get_dword(buffer, 8)?, - quick_recalibrate_time: safe_get_dword(buffer, 12)?, - slow_recalibrate_time: safe_get_dword(buffer, 16)?, - }, - }, - BatteryCmd::GetBct => Self::BatteryGetBctResponse { - bct_response: safe_get_dword(buffer, 0)?.into(), - }, - BatteryCmd::GetBtm => Self::BatteryGetBtmResponse { - btm_response: safe_get_dword(buffer, 0)?.into(), - }, - BatteryCmd::SetBms => Self::BatterySetBmsResponse { - status: safe_get_dword(buffer, 0)?, - }, - BatteryCmd::SetBma => Self::BatterySetBmaResponse { - status: safe_get_dword(buffer, 0)?, - }, - BatteryCmd::GetSta => Self::BatteryGetStaResponse { - sta: StaReturn::from_bits(safe_get_dword(buffer, 0)?) - .ok_or(MessageSerializationError::InvalidPayload("Invalid STA flags"))?, - }, - }, - ) - } - - fn discriminant(&self) -> u16 { - BatteryCmd::from(self).into() - } -} - -/// Fuel gauge ID -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u16)] -pub enum AcpiBatteryError { - UnknownDeviceId = 1, - UnspecifiedFailure = 2, -} - -pub type AcpiBatteryResult = Result; - -impl SerializableMessage for AcpiBatteryError { - fn serialize(self, _buffer: &mut [u8]) -> Result { - match self { - AcpiBatteryError::UnknownDeviceId | AcpiBatteryError::UnspecifiedFailure => Ok(0), - } - } - - fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { - AcpiBatteryError::try_from(discriminant) - .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant)) - } - - fn discriminant(&self) -> u16 { - (*self).into() - } -} - -fn safe_get_u8(buffer: &[u8], index: usize) -> Result { - buffer - .get(index) - .copied() - .ok_or(MessageSerializationError::BufferTooSmall) -} - -fn safe_get_dword(buffer: &[u8], index: usize) -> Result { - let bytes = buffer - .get(index..index + 4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .try_into() - .map_err(|_| MessageSerializationError::BufferTooSmall)?; - Ok(u32::from_le_bytes(bytes)) -} - -fn safe_get_bytes(buffer: &[u8], index: usize) -> Result<[u8; N], MessageSerializationError> { - buffer - .get(index..index + N) - .ok_or(MessageSerializationError::BufferTooSmall)? - .try_into() - .map_err(|_| MessageSerializationError::BufferTooSmall) -} - -fn safe_put_u8(buffer: &mut [u8], index: usize, val: u8) -> Result { - *buffer.get_mut(index).ok_or(MessageSerializationError::BufferTooSmall)? = val; - Ok(1) -} - -fn safe_put_dword(buffer: &mut [u8], index: usize, val: u32) -> Result { - buffer - .get_mut(index..index + 4) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(&val.to_le_bytes()); - Ok(4) -} - -fn safe_put_bytes(buffer: &mut [u8], index: usize, bytes: &[u8]) -> Result { - buffer - .get_mut(index..index + bytes.len()) - .ok_or(MessageSerializationError::BufferTooSmall)? - .copy_from_slice(bytes); - Ok(bytes.len()) -} diff --git a/battery-service-messages/Cargo.toml b/battery-service-relay/Cargo.toml similarity index 68% rename from battery-service-messages/Cargo.toml rename to battery-service-relay/Cargo.toml index e771e16f4..95d477c6f 100644 --- a/battery-service-messages/Cargo.toml +++ b/battery-service-relay/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "battery-service-messages" +name = "battery-service-relay" version.workspace = true edition.workspace = true license.workspace = true @@ -8,17 +8,19 @@ repository.workspace = true [dependencies] defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } -embedded-batteries-async.workspace = true embedded-services.workspace = true num_enum.workspace = true -[lints] -workspace = true +battery-service-interface.workspace = true +embedded-batteries-async.workspace = true [features] defmt = [ "dep:defmt", "embedded-services/defmt", - "embedded-batteries-async/defmt", + "battery-service-interface/defmt", ] -log = ["dep:log", "embedded-services/log"] +log = ["dep:log", "embedded-services/log", "battery-service-interface/log"] + +[lints] +workspace = true diff --git a/battery-service-relay/src/lib.rs b/battery-service-relay/src/lib.rs new file mode 100644 index 000000000..92988ae2f --- /dev/null +++ b/battery-service-relay/src/lib.rs @@ -0,0 +1,100 @@ +#![no_std] + +use battery_service_interface::*; +use embedded_services::trace; + +mod serialization; +pub use serialization::{AcpiBatteryError, AcpiBatteryRequest, AcpiBatteryResponse, AcpiBatteryResult}; + +/// Relays messages to and from a battery service implementation over MCTP. +pub struct BatteryServiceRelayHandler { + service: S, +} + +impl BatteryServiceRelayHandler { + /// Create a new relay handler that uses the provided battery service implementation to handle requests. + pub fn new(service: S) -> Self { + Self { service } + } +} + +impl embedded_services::relay::mctp::RelayServiceHandlerTypes + for BatteryServiceRelayHandler +{ + type RequestType = serialization::AcpiBatteryRequest; + type ResultType = serialization::AcpiBatteryResult; +} + +impl embedded_services::relay::mctp::RelayServiceHandler + for BatteryServiceRelayHandler +{ + async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { + trace!("Battery service: ACPI cmd recvd"); + Ok(match request { + AcpiBatteryRequest::GetBix { battery_id } => AcpiBatteryResponse::GetBix { + bix: self.service.battery_info(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::GetBst { battery_id } => AcpiBatteryResponse::GetBst { + bst: self.service.battery_status(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::GetPsr { battery_id } => AcpiBatteryResponse::GetPsr { + psr: self.service.is_in_use(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::GetPif { battery_id } => AcpiBatteryResponse::GetPif { + pif: self.service.power_source_information(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::GetBps { battery_id } => AcpiBatteryResponse::GetBps { + bps: self.service.battery_power_state(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::SetBtp { battery_id, btp } => { + self.service.set_battery_trip_point(DeviceId(battery_id), btp).await?; + AcpiBatteryResponse::SetBtp {} + } + AcpiBatteryRequest::SetBpt { battery_id, bpt } => { + self.service + .set_battery_power_threshold(DeviceId(battery_id), bpt) + .await?; + AcpiBatteryResponse::SetBpt {} + } + + AcpiBatteryRequest::GetBpc { battery_id } => AcpiBatteryResponse::GetBpc { + bpc: self.service.battery_power_characteristics(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::SetBmc { battery_id, bmc } => { + self.service + .battery_maintenance_control(DeviceId(battery_id), bmc) + .await?; + AcpiBatteryResponse::SetBmc {} + } + AcpiBatteryRequest::GetBmd { battery_id } => AcpiBatteryResponse::GetBmd { + bmd: self.service.battery_maintenance_data(DeviceId(battery_id)).await?, + }, + AcpiBatteryRequest::GetBct { battery_id, bct } => AcpiBatteryResponse::GetBct { + bct_response: self.service.battery_charge_time(DeviceId(battery_id), bct).await?, + }, + AcpiBatteryRequest::GetBtm { battery_id, btm } => AcpiBatteryResponse::GetBtm { + btm_response: self.service.battery_time_to_empty(DeviceId(battery_id), btm).await?, + }, + + AcpiBatteryRequest::SetBms { battery_id, bms } => { + self.service + .set_battery_measurement_sampling_time(DeviceId(battery_id), bms) + .await?; + AcpiBatteryResponse::SetBms { + status: 0, // TODO once we have a working reference platform, we should consider dropping this field, since it's redundant with the error type on Result. + } + } + AcpiBatteryRequest::SetBma { battery_id, bma } => { + self.service + .set_battery_measurement_averaging_interval(DeviceId(battery_id), bma) + .await?; + AcpiBatteryResponse::SetBma { + status: 0, // TODO once we have a working reference platform, we should consider dropping this field, since it's redundant with the error type on Result. + } + } + AcpiBatteryRequest::GetSta { battery_id } => AcpiBatteryResponse::GetSta { + sta: self.service.device_status(DeviceId(battery_id)).await?, + }, + }) + } +} diff --git a/battery-service-relay/src/serialization.rs b/battery-service-relay/src/serialization.rs new file mode 100644 index 000000000..39f411a44 --- /dev/null +++ b/battery-service-relay/src/serialization.rs @@ -0,0 +1,611 @@ +use battery_service_interface::*; +use embedded_services::relay::{MessageSerializationError, SerializableMessage}; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[repr(u16)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// ACPI Battery Methods +enum BatteryCmd { + /// Battery Information eXtended + GetBix = 1, + /// Battery Status + GetBst = 2, + /// Power Source + GetPsr = 3, + /// Power source InFormation + GetPif = 4, + /// Battery Power State + GetBps = 5, + /// Battery Trip Point + SetBtp = 6, + /// Battery Power Threshold + SetBpt = 7, + /// Battery Power Characteristics + GetBpc = 8, + /// Battery Maintenance Control + SetBmc = 9, + /// Battery Maintenance Data + GetBmd = 10, + /// Battery Charge Time + GetBct = 11, + /// Battery Time + GetBtm = 12, + /// Battery Measurement Sampling Time + SetBms = 13, + /// Battery Measurement Averaging Interval + SetBma = 14, + /// Device Status + GetSta = 15, +} + +impl From<&AcpiBatteryRequest> for BatteryCmd { + fn from(request: &AcpiBatteryRequest) -> Self { + match request { + AcpiBatteryRequest::GetBix { .. } => BatteryCmd::GetBix, + AcpiBatteryRequest::GetBst { .. } => BatteryCmd::GetBst, + AcpiBatteryRequest::GetPsr { .. } => BatteryCmd::GetPsr, + AcpiBatteryRequest::GetPif { .. } => BatteryCmd::GetPif, + AcpiBatteryRequest::GetBps { .. } => BatteryCmd::GetBps, + AcpiBatteryRequest::SetBtp { .. } => BatteryCmd::SetBtp, + AcpiBatteryRequest::SetBpt { .. } => BatteryCmd::SetBpt, + AcpiBatteryRequest::GetBpc { .. } => BatteryCmd::GetBpc, + AcpiBatteryRequest::SetBmc { .. } => BatteryCmd::SetBmc, + AcpiBatteryRequest::GetBmd { .. } => BatteryCmd::GetBmd, + AcpiBatteryRequest::GetBct { .. } => BatteryCmd::GetBct, + AcpiBatteryRequest::GetBtm { .. } => BatteryCmd::GetBtm, + AcpiBatteryRequest::SetBms { .. } => BatteryCmd::SetBms, + AcpiBatteryRequest::SetBma { .. } => BatteryCmd::SetBma, + AcpiBatteryRequest::GetSta { .. } => BatteryCmd::GetSta, + } + } +} + +impl From<&AcpiBatteryResponse> for BatteryCmd { + fn from(response: &AcpiBatteryResponse) -> Self { + match response { + AcpiBatteryResponse::GetBix { .. } => BatteryCmd::GetBix, + AcpiBatteryResponse::GetBst { .. } => BatteryCmd::GetBst, + AcpiBatteryResponse::GetPsr { .. } => BatteryCmd::GetPsr, + AcpiBatteryResponse::GetPif { .. } => BatteryCmd::GetPif, + AcpiBatteryResponse::GetBps { .. } => BatteryCmd::GetBps, + AcpiBatteryResponse::SetBtp { .. } => BatteryCmd::SetBtp, + AcpiBatteryResponse::SetBpt { .. } => BatteryCmd::SetBpt, + AcpiBatteryResponse::GetBpc { .. } => BatteryCmd::GetBpc, + AcpiBatteryResponse::SetBmc { .. } => BatteryCmd::SetBmc, + AcpiBatteryResponse::GetBmd { .. } => BatteryCmd::GetBmd, + AcpiBatteryResponse::GetBct { .. } => BatteryCmd::GetBct, + AcpiBatteryResponse::GetBtm { .. } => BatteryCmd::GetBtm, + AcpiBatteryResponse::SetBms { .. } => BatteryCmd::SetBms, + AcpiBatteryResponse::SetBma { .. } => BatteryCmd::SetBma, + AcpiBatteryResponse::GetSta { .. } => BatteryCmd::GetSta, + } + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// ACPI battery device message responses as defined in ACPI spec version 6.4, section 10.2 +pub enum AcpiBatteryResponse { + /// Extended battery information. Analogous to the return value of the _BIX method. + GetBix { bix: BixFixedStrings }, + + /// Battery status. Analogous to the return value of the _BST method. + GetBst { bst: BstReturn }, + + /// Power source in use. Analogous to the return value of the _PSR method. + GetPsr { psr: PsrReturn }, + + /// Power source information. Analogous to the return value of the _PIF method. + GetPif { pif: PifFixedStrings }, + + /// Battery power state. Analogous to the return value of the _BPS method. + GetBps { bps: Bps }, + + /// Result of setting a battery trip point. Analogous to the _BTP method. Semantically equivalent to (). + SetBtp {}, + + /// Result of setting a battery power threshold. Analogous to the _BPT method. Semantically equivalent to (). + SetBpt {}, + + /// Battery power characteristics. Analogous to the return value of the _BPC method. + GetBpc { bpc: Bpc }, + + /// Result of performing a battery maintenance control operation. Analogous to the return value of the _BMC method. Semantically equivalent to (). + SetBmc {}, + + /// Battery maintenance data. Analogous to the return value of the _BMD method. + GetBmd { bmd: Bmd }, + + /// Battery charge time. Analogous to the return value of the _BCT method. + GetBct { bct_response: BctReturnResult }, + + /// Battery time to empty. Analogous to the return value of the _BTM method. + GetBtm { btm_response: BtmReturnResult }, + + /// Result of setting the battery measurement sampling time. Analogous to the _BMS method. + SetBms { status: u32 }, + + /// Result of setting the battery measurement averaging interval. Analogous to the _BMA method. + SetBma { status: u32 }, + + /// Battery device status. Analogous to the return value of the _STA method. + GetSta { sta: StaReturn }, +} + +impl SerializableMessage for AcpiBatteryResponse { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::GetBix { bix } => bix_to_bytes(bix, buffer), + Self::GetBst { bst } => Ok(safe_put_dword(buffer, 0, bst.battery_state.bits())? + + safe_put_dword(buffer, 4, bst.battery_present_rate)? + + safe_put_dword(buffer, 8, bst.battery_remaining_capacity)? + + safe_put_dword(buffer, 12, bst.battery_present_voltage)?), + Self::GetPsr { psr } => safe_put_dword(buffer, 0, psr.power_source.into()), + + Self::GetPif { pif } => pif_to_bytes(pif, buffer), + Self::GetBps { bps } => Ok(safe_put_dword(buffer, 0, bps.revision)? + + safe_put_dword(buffer, 4, bps.instantaneous_peak_power_level)? + + safe_put_dword(buffer, 8, bps.instantaneous_peak_power_period)? + + safe_put_dword(buffer, 12, bps.sustainable_peak_power_level)? + + safe_put_dword(buffer, 16, bps.sustainable_peak_power_period)?), + Self::SetBtp {} => Ok(0), + Self::SetBpt {} => Ok(0), + Self::GetBpc { bpc } => Ok(safe_put_dword(buffer, 0, bpc.revision)? + + safe_put_dword(buffer, 4, bpc.power_threshold_support.bits())? + + safe_put_dword(buffer, 8, bpc.max_instantaneous_peak_power_threshold)? + + safe_put_dword(buffer, 12, bpc.max_sustainable_peak_power_threshold)?), + Self::SetBmc {} => Ok(0), + Self::GetBmd { bmd } => Ok(safe_put_dword(buffer, 0, bmd.status_flags.bits())? + + safe_put_dword(buffer, 4, bmd.capability_flags.bits())? + + safe_put_dword(buffer, 8, bmd.recalibrate_count)? + + safe_put_dword(buffer, 12, bmd.quick_recalibrate_time)? + + safe_put_dword(buffer, 16, bmd.slow_recalibrate_time)?), + Self::GetBct { bct_response } => safe_put_dword(buffer, 0, bct_response.into()), + Self::GetBtm { btm_response } => safe_put_dword(buffer, 0, btm_response.into()), + Self::SetBms { status } => safe_put_dword(buffer, 0, status), + Self::SetBma { status } => safe_put_dword(buffer, 0, status), + Self::GetSta { sta } => safe_put_dword(buffer, 0, sta.bits()), + } + } + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match BatteryCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + BatteryCmd::GetBix => Self::GetBix { + bix: bix_from_bytes(buffer)?, + }, + BatteryCmd::GetBst => { + let bst = BstReturn { + battery_state: BatteryState::from_bits(safe_get_dword(buffer, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BatteryState"))?, + battery_present_rate: safe_get_dword(buffer, 4)?, + battery_remaining_capacity: safe_get_dword(buffer, 8)?, + battery_present_voltage: safe_get_dword(buffer, 12)?, + }; + Self::GetBst { bst } + } + BatteryCmd::GetPsr => Self::GetPsr { + psr: PsrReturn { + power_source: safe_get_dword(buffer, 0)? + .try_into() + .map_err(|_| MessageSerializationError::InvalidPayload("Invalid PowerSource"))?, + }, + }, + BatteryCmd::GetPif => Self::GetPif { + pif: pif_from_bytes(buffer)?, + }, + BatteryCmd::GetBps => Self::GetBps { + bps: Bps { + revision: safe_get_dword(buffer, 0)?, + instantaneous_peak_power_level: safe_get_dword(buffer, 4)?, + instantaneous_peak_power_period: safe_get_dword(buffer, 8)?, + sustainable_peak_power_level: safe_get_dword(buffer, 12)?, + sustainable_peak_power_period: safe_get_dword(buffer, 16)?, + }, + }, + BatteryCmd::SetBtp => Self::SetBtp {}, + BatteryCmd::SetBpt => Self::SetBpt {}, + BatteryCmd::GetBpc => Self::GetBpc { + bpc: Bpc { + revision: safe_get_dword(buffer, 0)?, + power_threshold_support: PowerThresholdSupport::from_bits(safe_get_dword(buffer, 4)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BpcThresholdSupport"))?, + max_instantaneous_peak_power_threshold: safe_get_dword(buffer, 8)?, + max_sustainable_peak_power_threshold: safe_get_dword(buffer, 12)?, + }, + }, + BatteryCmd::SetBmc => Self::SetBmc {}, + BatteryCmd::GetBmd => Self::GetBmd { + bmd: Bmd { + status_flags: BmdStatusFlags::from_bits(safe_get_dword(buffer, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BmdStatusFlags"))?, + capability_flags: BmdCapabilityFlags::from_bits(safe_get_dword(buffer, 4)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid BmdCapabilityFlags"))?, + recalibrate_count: safe_get_dword(buffer, 8)?, + quick_recalibrate_time: safe_get_dword(buffer, 12)?, + slow_recalibrate_time: safe_get_dword(buffer, 16)?, + }, + }, + BatteryCmd::GetBct => Self::GetBct { + bct_response: safe_get_dword(buffer, 0)?.into(), + }, + BatteryCmd::GetBtm => Self::GetBtm { + btm_response: safe_get_dword(buffer, 0)?.into(), + }, + BatteryCmd::SetBms => Self::SetBms { + status: safe_get_dword(buffer, 0)?, + }, + BatteryCmd::SetBma => Self::SetBma { + status: safe_get_dword(buffer, 0)?, + }, + BatteryCmd::GetSta => Self::GetSta { + sta: StaReturn::from_bits(safe_get_dword(buffer, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid STA flags"))?, + }, + }, + ) + } + + fn discriminant(&self) -> u16 { + BatteryCmd::from(self).into() + } +} + +#[derive(PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcpiBatteryRequest { + /// Queries extended battery information. Analogous to ACPI's _BIX method. + GetBix { battery_id: u8 }, + + /// Queries battery status. Analogous to ACPI's _BST method. + GetBst { battery_id: u8 }, + + /// Queries the current power source. Analogous to ACPI's _PSR method. + GetPsr { battery_id: u8 }, + + /// Queries information about the battery's power source. Analogous to ACPI's _PIF method. + GetPif { battery_id: u8 }, + + /// Queries information about the current power delivery capabilities of the battery. Analogous to ACPI's _BPS method. + GetBps { battery_id: u8 }, + + /// Sets a battery trip point. Analogous to ACPI's _BTP method. + SetBtp { battery_id: u8, btp: Btp }, + + /// Sets a battery power threshold. Analogous to ACPI's _BPT method. + SetBpt { battery_id: u8, bpt: Bpt }, + + /// Queries the current power characteristics of the battery. Analogous to ACPI's _BPC method. + GetBpc { battery_id: u8 }, + + /// Performs a battery maintenance control operation. Analogous to ACPI's _BMC method. + SetBmc { battery_id: u8, bmc: Bmc }, + + /// Queries battery maintenance data. Analogous to ACPI's _BMD method. + GetBmd { battery_id: u8 }, + + /// Queries the estimated time remaining to charge the battery to the specified level. Analogous to ACPI's _BCT method. + GetBct { battery_id: u8, bct: Bct }, + + /// Queries the estimated time remaining until the battery is discharged to the specified level. Analogous to ACPI's _BTM method. + GetBtm { battery_id: u8, btm: Btm }, + + /// Sets the sampling time of battery measurements in milliseconds. Analogous to ACPI's _BMS method. + SetBms { battery_id: u8, bms: Bms }, + + /// Sets the averaging interval of battery measurements in milliseconds. Analogous to ACPI's _BMA method. + SetBma { battery_id: u8, bma: Bma }, + + /// Queries the current status of the battery device. Analogous to ACPI's _STA method. + GetSta { battery_id: u8 }, +} + +impl SerializableMessage for AcpiBatteryRequest { + fn serialize(self, buffer: &mut [u8]) -> Result { + match self { + Self::GetBix { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::GetBst { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::GetPsr { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::GetPif { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::GetBps { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::SetBtp { battery_id, btp } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, btp.trip_point)?) + } + Self::SetBpt { battery_id, bpt } => Ok(safe_put_u8(buffer, 0, battery_id)? + + safe_put_dword(buffer, 1, bpt.revision)? + + safe_put_dword(buffer, 5, bpt.threshold_id as u32)? + + safe_put_dword(buffer, 9, bpt.threshold_value)?), + Self::GetBpc { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::SetBmc { battery_id, bmc } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + + safe_put_dword(buffer, 1, bmc.maintenance_control_flags.bits())?) + } + Self::GetBmd { battery_id } => safe_put_u8(buffer, 0, battery_id), + Self::GetBct { battery_id, bct } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bct.charge_level_percent)?) + } + Self::GetBtm { battery_id, btm } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, btm.discharge_rate)?) + } + Self::SetBms { battery_id, bms } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bms.sampling_time_ms)?) + } + Self::SetBma { battery_id, bma } => { + Ok(safe_put_u8(buffer, 0, battery_id)? + safe_put_dword(buffer, 1, bma.averaging_interval_ms)?) + } + Self::GetSta { battery_id } => safe_put_u8(buffer, 0, battery_id), + } + } + + fn deserialize(discriminant: u16, buffer: &[u8]) -> Result { + Ok( + match BatteryCmd::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? + { + BatteryCmd::GetBix => Self::GetBix { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBst => Self::GetBst { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetPsr => Self::GetPsr { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetPif => Self::GetPif { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBps => Self::GetBps { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::SetBtp => Self::SetBtp { + battery_id: safe_get_u8(buffer, 0)?, + btp: Btp { + trip_point: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBpt => Self::SetBpt { + battery_id: safe_get_u8(buffer, 0)?, + bpt: Bpt { + revision: safe_get_dword(buffer, 1)?, + threshold_id: safe_get_dword(buffer, 5)? + .try_into() + .map_err(|_| MessageSerializationError::InvalidPayload("Invalid ThresholdId"))?, + threshold_value: safe_get_dword(buffer, 9)?, + }, + }, + BatteryCmd::GetBpc => Self::GetBpc { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::SetBmc => Self::SetBmc { + battery_id: safe_get_u8(buffer, 0)?, + bmc: Bmc { + maintenance_control_flags: BmcControlFlags::from_bits_retain(safe_get_dword(buffer, 1)?), + }, + }, + BatteryCmd::GetBmd => Self::GetBmd { + battery_id: safe_get_u8(buffer, 0)?, + }, + BatteryCmd::GetBct => Self::GetBct { + battery_id: safe_get_u8(buffer, 0)?, + bct: Bct { + charge_level_percent: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::GetBtm => Self::GetBtm { + battery_id: safe_get_u8(buffer, 0)?, + btm: Btm { + discharge_rate: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBms => Self::SetBms { + battery_id: safe_get_u8(buffer, 0)?, + bms: Bms { + sampling_time_ms: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::SetBma => Self::SetBma { + battery_id: safe_get_u8(buffer, 0)?, + bma: Bma { + averaging_interval_ms: safe_get_dword(buffer, 1)?, + }, + }, + BatteryCmd::GetSta => Self::GetSta { + battery_id: safe_get_u8(buffer, 0)?, + }, + }, + ) + } + + fn discriminant(&self) -> u16 { + BatteryCmd::from(self).into() + } +} + +/// Serializable result type for battery operations. +pub type AcpiBatteryResult = Result; + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u16)] +/// Errors that can occur while processing ACPI battery requests. +pub enum AcpiBatteryError { + /// The provided battery ID does not correspond to any known battery device. + UnknownDeviceId = 1, + + /// An unspecified error occurred while processing the request. + UnspecifiedFailure = 2, +} + +impl SerializableMessage for AcpiBatteryError { + fn serialize(self, _buffer: &mut [u8]) -> Result { + match self { + AcpiBatteryError::UnknownDeviceId | AcpiBatteryError::UnspecifiedFailure => Ok(0), + } + } + + fn deserialize(discriminant: u16, _buffer: &[u8]) -> Result { + AcpiBatteryError::try_from(discriminant) + .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant)) + } + + fn discriminant(&self) -> u16 { + (*self).into() + } +} + +impl From for AcpiBatteryError { + fn from(error: BatteryError) -> Self { + match error { + BatteryError::UnknownDeviceId => AcpiBatteryError::UnknownDeviceId, + BatteryError::UnspecifiedFailure => AcpiBatteryError::UnspecifiedFailure, + } + } +} + +fn safe_get_u8(buffer: &[u8], index: usize) -> Result { + buffer + .get(index) + .copied() + .ok_or(MessageSerializationError::BufferTooSmall) +} + +fn safe_get_dword(buffer: &[u8], index: usize) -> Result { + let bytes = buffer + .get(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall)?; + Ok(u32::from_le_bytes(bytes)) +} + +fn safe_get_bytes(buffer: &[u8], index: usize) -> Result<[u8; N], MessageSerializationError> { + buffer + .get(index..index + N) + .ok_or(MessageSerializationError::BufferTooSmall)? + .try_into() + .map_err(|_| MessageSerializationError::BufferTooSmall) +} + +fn safe_put_u8(buffer: &mut [u8], index: usize, val: u8) -> Result { + *buffer.get_mut(index).ok_or(MessageSerializationError::BufferTooSmall)? = val; + Ok(1) +} + +fn safe_put_dword(buffer: &mut [u8], index: usize, val: u32) -> Result { + buffer + .get_mut(index..index + 4) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(&val.to_le_bytes()); + Ok(4) +} + +fn safe_put_bytes(buffer: &mut [u8], index: usize, bytes: &[u8]) -> Result { + buffer + .get_mut(index..index + bytes.len()) + .ok_or(MessageSerializationError::BufferTooSmall)? + .copy_from_slice(bytes); + Ok(bytes.len()) +} + +const BIX_MODEL_NUM_START_IDX: usize = 64; +const BIX_MODEL_NUM_END_IDX: usize = BIX_MODEL_NUM_START_IDX + STD_BIX_MODEL_SIZE; +const BIX_SERIAL_NUM_START_IDX: usize = BIX_MODEL_NUM_END_IDX; +const BIX_SERIAL_NUM_END_IDX: usize = BIX_SERIAL_NUM_START_IDX + STD_BIX_SERIAL_SIZE; +const BIX_BATTERY_TYPE_START_IDX: usize = BIX_SERIAL_NUM_END_IDX; +const BIX_BATTERY_TYPE_END_IDX: usize = BIX_BATTERY_TYPE_START_IDX + STD_BIX_BATTERY_SIZE; +const BIX_OEM_INFO_START_IDX: usize = BIX_BATTERY_TYPE_END_IDX; +const BIX_OEM_INFO_END_IDX: usize = BIX_OEM_INFO_START_IDX + STD_BIX_OEM_SIZE; + +fn bix_to_bytes(bix: BixFixedStrings, dst_slice: &mut [u8]) -> Result { + if dst_slice.len() < BIX_OEM_INFO_END_IDX + core::mem::size_of::() { + return Err(MessageSerializationError::BufferTooSmall); + } + + Ok(safe_put_dword(dst_slice, 0, bix.revision)? + + safe_put_dword(dst_slice, 4, bix.power_unit.into())? + + safe_put_dword(dst_slice, 8, bix.design_capacity)? + + safe_put_dword(dst_slice, 12, bix.last_full_charge_capacity)? + + safe_put_dword(dst_slice, 16, bix.battery_technology.into())? + + safe_put_dword(dst_slice, 20, bix.design_voltage)? + + safe_put_dword(dst_slice, 24, bix.design_cap_of_warning)? + + safe_put_dword(dst_slice, 28, bix.design_cap_of_low)? + + safe_put_dword(dst_slice, 32, bix.cycle_count)? + + safe_put_dword(dst_slice, 36, bix.measurement_accuracy)? + + safe_put_dword(dst_slice, 40, bix.max_sampling_time)? + + safe_put_dword(dst_slice, 44, bix.min_sampling_time)? + + safe_put_dword(dst_slice, 48, bix.max_averaging_interval)? + + safe_put_dword(dst_slice, 52, bix.min_averaging_interval)? + + safe_put_dword(dst_slice, 56, bix.battery_capacity_granularity_1)? + + safe_put_dword(dst_slice, 60, bix.battery_capacity_granularity_2)? + + safe_put_bytes(dst_slice, BIX_MODEL_NUM_START_IDX, &bix.model_number)? + + safe_put_bytes(dst_slice, BIX_SERIAL_NUM_START_IDX, &bix.serial_number)? + + safe_put_bytes(dst_slice, BIX_BATTERY_TYPE_START_IDX, &bix.battery_type)? + + safe_put_bytes(dst_slice, BIX_OEM_INFO_START_IDX, &bix.oem_info)? + + safe_put_dword(dst_slice, BIX_OEM_INFO_END_IDX, bix.battery_swapping_capability.into())?) +} + +fn bix_from_bytes(src_slice: &[u8]) -> Result { + Ok(BixFixedStrings { + revision: safe_get_dword(src_slice, 0)?, + power_unit: safe_get_dword(src_slice, 4)? + .try_into() + .map_err(|_| MessageSerializationError::InvalidPayload("Invalid PowerUnit"))?, + design_capacity: safe_get_dword(src_slice, 8)?, + last_full_charge_capacity: safe_get_dword(src_slice, 12)?, + battery_technology: safe_get_dword(src_slice, 16)? + .try_into() + .map_err(|_| MessageSerializationError::InvalidPayload("Invalid BatteryTechnology"))?, + design_voltage: safe_get_dword(src_slice, 20)?, + design_cap_of_warning: safe_get_dword(src_slice, 24)?, + design_cap_of_low: safe_get_dword(src_slice, 28)?, + cycle_count: safe_get_dword(src_slice, 32)?, + measurement_accuracy: safe_get_dword(src_slice, 36)?, + max_sampling_time: safe_get_dword(src_slice, 40)?, + min_sampling_time: safe_get_dword(src_slice, 44)?, + max_averaging_interval: safe_get_dword(src_slice, 48)?, + min_averaging_interval: safe_get_dword(src_slice, 52)?, + battery_capacity_granularity_1: safe_get_dword(src_slice, 56)?, + battery_capacity_granularity_2: safe_get_dword(src_slice, 60)?, + model_number: safe_get_bytes::(src_slice, BIX_MODEL_NUM_START_IDX)?, + serial_number: safe_get_bytes::(src_slice, BIX_SERIAL_NUM_START_IDX)?, + battery_type: safe_get_bytes::(src_slice, BIX_BATTERY_TYPE_START_IDX)?, + oem_info: safe_get_bytes::(src_slice, BIX_OEM_INFO_START_IDX)?, + battery_swapping_capability: safe_get_dword(src_slice, BIX_OEM_INFO_END_IDX)? + .try_into() + .map_err(|_| MessageSerializationError::InvalidPayload("Invalid BatterySwappingCapability"))?, + }) +} + +const PIF_MODEL_NUM_START_IDX: usize = 12; +const PIF_MODEL_NUM_END_IDX: usize = PIF_MODEL_NUM_START_IDX + STD_PIF_MODEL_SIZE; +const PIF_SERIAL_NUM_START_IDX: usize = PIF_MODEL_NUM_END_IDX; +const PIF_SERIAL_NUM_END_IDX: usize = PIF_SERIAL_NUM_START_IDX + STD_PIF_SERIAL_SIZE; +const PIF_OEM_INFO_START_IDX: usize = PIF_SERIAL_NUM_END_IDX; +const PIF_OEM_INFO_END_IDX: usize = PIF_OEM_INFO_START_IDX + STD_PIF_OEM_SIZE; + +fn pif_to_bytes(pif: PifFixedStrings, dst_slice: &mut [u8]) -> Result { + if dst_slice.len() < PIF_OEM_INFO_END_IDX { + return Err(MessageSerializationError::BufferTooSmall); + } + + Ok(safe_put_dword(dst_slice, 0, pif.power_source_state.bits())? + + safe_put_dword(dst_slice, 4, pif.max_output_power)? + + safe_put_dword(dst_slice, 8, pif.max_input_power)? + + safe_put_bytes(dst_slice, PIF_MODEL_NUM_START_IDX, &pif.model_number)? + + safe_put_bytes(dst_slice, PIF_SERIAL_NUM_START_IDX, &pif.serial_number)? + + safe_put_bytes(dst_slice, PIF_OEM_INFO_START_IDX, &pif.oem_info)?) +} + +fn pif_from_bytes(src_slice: &[u8]) -> Result { + Ok(PifFixedStrings { + power_source_state: PowerSourceState::from_bits(safe_get_dword(src_slice, 0)?) + .ok_or(MessageSerializationError::InvalidPayload("Invalid PowerSourceState"))?, + max_output_power: safe_get_dword(src_slice, 4)?, + max_input_power: safe_get_dword(src_slice, 8)?, + model_number: safe_get_bytes::(src_slice, PIF_MODEL_NUM_START_IDX)?, + serial_number: safe_get_bytes::(src_slice, PIF_SERIAL_NUM_START_IDX)?, + oem_info: safe_get_bytes::(src_slice, PIF_OEM_INFO_START_IDX)?, + }) +} diff --git a/battery-service/Cargo.toml b/battery-service/Cargo.toml index 0483e8162..316594a87 100644 --- a/battery-service/Cargo.toml +++ b/battery-service/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] defmt = { workspace = true, optional = true } -battery-service-messages.workspace = true +battery-service-interface.workspace = true embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true @@ -22,25 +22,22 @@ embedded-hal.workspace = true embedded-services.workspace = true log = { workspace = true, optional = true } odp-service-common.workspace = true -zerocopy.workspace = true -mctp-rs = { workspace = true, features = ["espi"] } -heapless.workspace = true power-policy-interface.workspace = true [features] default = [] defmt = [ "dep:defmt", - "battery-service-messages/defmt", + "battery-service-interface/defmt", "embedded-services/defmt", "embassy-time/defmt", "embassy-sync/defmt", "embedded-batteries-async/defmt", - "mctp-rs/defmt", "power-policy-interface/defmt", ] log = [ "dep:log", + "battery-service-interface/log", "embedded-services/log", "embassy-time/log", "embassy-sync/log", diff --git a/battery-service/src/acpi.rs b/battery-service/src/acpi.rs index f0656a7db..52d0074de 100644 --- a/battery-service/src/acpi.rs +++ b/battery-service/src/acpi.rs @@ -1,18 +1,19 @@ #![allow(dead_code)] use core::ops::Deref; +use battery_service_interface::BatteryError; use embedded_batteries_async::acpi::{PowerSourceState, PowerUnit}; use embedded_services::{info, trace}; -use battery_service_messages::{ - AcpiBatteryResponse, BixFixedStrings, DeviceId, PifFixedStrings, STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, - STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, +use battery_service_interface::{ + BctReturnResult, BixFixedStrings, Bmd, Bpc, Bps, BstReturn, BtmReturnResult, DeviceId, PifFixedStrings, PsrReturn, + STD_BIX_BATTERY_SIZE, STD_BIX_MODEL_SIZE, STD_BIX_OEM_SIZE, STD_BIX_SERIAL_SIZE, STD_PIF_MODEL_SIZE, + STD_PIF_OEM_SIZE, STD_PIF_SERIAL_SIZE, StaReturn, }; use power_policy_interface::capability::PowerCapability; use crate::{ - AcpiBatteryError, context::PsuState, device::{DynamicBatteryMsgs, StaticBatteryMsgs}, }; @@ -181,222 +182,173 @@ pub(crate) fn compute_pif(psu_state: &PsuState) -> PifFixedStrings { } impl crate::context::Context { - // TODO Move these to a trait - pub(super) async fn bix_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn bix_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BIX command!"); - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; let static_cache_guard = fg.get_static_battery_cache_guarded().await; let dynamic_cache_guard = fg.get_dynamic_battery_cache_guarded().await; - Ok(AcpiBatteryResponse::BatteryGetBixResponse { - bix: compute_bix(static_cache_guard.deref(), dynamic_cache_guard.deref()) - .map_err(|_| AcpiBatteryError::UnspecifiedFailure)?, - }) + compute_bix(static_cache_guard.deref(), dynamic_cache_guard.deref()) + .map_err(|_| BatteryError::UnspecifiedFailure) } - pub(super) async fn bst_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn bst_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BST command!"); - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; - Ok(AcpiBatteryResponse::BatteryGetBstResponse { - bst: compute_bst(&fg.get_dynamic_battery_cache().await), - }) + Ok(compute_bst(&fg.get_dynamic_battery_cache().await)) } - pub(super) async fn psr_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn psr_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got PSR command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; - Ok(AcpiBatteryResponse::BatteryGetPsrResponse { - psr: compute_psr(&self.get_power_info().await), - }) + Ok(compute_psr(&self.get_power_info().await)) } - pub(super) async fn pif_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn pif_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got PIF command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; - Ok(AcpiBatteryResponse::BatteryGetPifResponse { - pif: compute_pif(&self.get_power_info().await), - }) + Ok(compute_pif(&self.get_power_info().await)) } - pub(super) async fn bps_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn bps_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BPS command!"); - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; - Ok(AcpiBatteryResponse::BatteryGetBpsResponse { - bps: compute_bps(&fg.get_dynamic_battery_cache().await), - }) + Ok(compute_bps(&fg.get_dynamic_battery_cache().await)) } pub(super) async fn btp_handler( &self, device_id: DeviceId, btp: embedded_batteries_async::acpi::Btp, - ) -> Result { + ) -> Result<(), BatteryError> { trace!("Battery service: got BTP command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; // TODO: Save trip point info!("Battery service: New BTP {}", btp.trip_point); - Ok(AcpiBatteryResponse::BatterySetBtpResponse {}) + Ok(()) } pub(super) async fn bpt_handler( &self, device_id: DeviceId, bpt: embedded_batteries_async::acpi::Bpt, - ) -> Result { + ) -> Result<(), BatteryError> { trace!("Battery service: got BPT command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; info!( "Battery service: Threshold ID: {:?}, Threshold value: {:?}", bpt.threshold_id as u32, bpt.threshold_value ); - Ok(AcpiBatteryResponse::BatterySetBptResponse {}) + Ok(()) } - pub(super) async fn bpc_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn bpc_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BPC command!"); // TODO: Save trip point - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; - Ok(AcpiBatteryResponse::BatteryGetBpcResponse { - bpc: compute_bpc(&fg.get_static_battery_cache().await), - }) + Ok(compute_bpc(&fg.get_static_battery_cache().await)) } pub(super) async fn bmc_handler( &self, device_id: DeviceId, bmc: embedded_batteries_async::acpi::Bmc, - ) -> Result { + ) -> Result<(), BatteryError> { trace!("Battery service: got BMC command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; info!("Battery service: Bmc {}", bmc.maintenance_control_flags.bits()); - Ok(AcpiBatteryResponse::BatterySetBmcResponse {}) + Ok(()) } - pub(super) async fn bmd_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn bmd_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got BMD command!"); - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; let static_cache = fg.get_static_battery_cache().await; let dynamic_cache = fg.get_dynamic_battery_cache().await; - Ok(AcpiBatteryResponse::BatteryGetBmdResponse { - bmd: compute_bmd(&static_cache, &dynamic_cache), - }) + Ok(compute_bmd(&static_cache, &dynamic_cache)) } pub(super) async fn bct_handler( &self, device_id: DeviceId, bct: embedded_batteries_async::acpi::Bct, - ) -> Result { + ) -> Result { trace!("Battery service: got BCT command!"); - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; info!("Recvd BCT charge_level_percent: {}", bct.charge_level_percent); - Ok(AcpiBatteryResponse::BatteryGetBctResponse { - bct_response: compute_bct(&bct, &fg.get_dynamic_battery_cache().await), - }) + Ok(compute_bct(&bct, &fg.get_dynamic_battery_cache().await)) } pub(super) async fn btm_handler( &self, device_id: DeviceId, btm: embedded_batteries_async::acpi::Btm, - ) -> Result { + ) -> Result { trace!("Battery service: got BTM command!"); - let fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; info!("Recvd BTM discharge_rate: {}", btm.discharge_rate); - Ok(AcpiBatteryResponse::BatteryGetBtmResponse { - btm_response: compute_btm(&btm, &fg.get_dynamic_battery_cache().await), - }) + Ok(compute_btm(&btm, &fg.get_dynamic_battery_cache().await)) } pub(super) async fn bms_handler( &self, device_id: DeviceId, bms: embedded_batteries_async::acpi::Bms, - ) -> Result { + ) -> Result<(), BatteryError> { trace!("Battery service: got BMS command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; info!("Recvd BMS sampling_time: {}", bms.sampling_time_ms); - Ok(AcpiBatteryResponse::BatterySetBmsResponse { status: 0 }) + Ok(()) } pub(super) async fn bma_handler( &self, device_id: DeviceId, bma: embedded_batteries_async::acpi::Bma, - ) -> Result { + ) -> Result<(), BatteryError> { trace!("Battery service: got BMA command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; info!("Recvd BMA averaging_interval_ms: {}", bma.averaging_interval_ms); - Ok(AcpiBatteryResponse::BatterySetBmaResponse { status: 0 }) + Ok(()) } - pub(super) async fn sta_handler(&self, device_id: DeviceId) -> Result { + pub(super) async fn sta_handler(&self, device_id: DeviceId) -> Result { trace!("Battery service: got STA command!"); - let _fg = self - .get_fuel_gauge(device_id) - .ok_or(AcpiBatteryError::UnknownDeviceId)?; + let _fg = self.get_fuel_gauge(device_id).ok_or(BatteryError::UnknownDeviceId)?; - Ok(AcpiBatteryResponse::BatteryGetStaResponse { sta: compute_sta() }) + Ok(compute_sta()) } } diff --git a/battery-service/src/context.rs b/battery-service/src/context.rs index aaaed99b2..39c5648ed 100644 --- a/battery-service/src/context.rs +++ b/battery-service/src/context.rs @@ -1,7 +1,5 @@ -use crate::AcpiBatteryError; -use crate::device::{self}; -use crate::device::{Device, FuelGaugeError}; -use battery_service_messages::{AcpiBatteryRequest, AcpiBatteryResponse, DeviceId}; +use crate::device::{self, Device, FuelGaugeError}; +use battery_service_interface::DeviceId; use embassy_sync::channel::Channel; use embassy_sync::channel::TrySendError; use embassy_sync::mutex::Mutex; @@ -398,44 +396,6 @@ impl Context { } } - pub(super) async fn process_acpi_cmd( - &self, - acpi_msg: &AcpiBatteryRequest, - ) -> Result { - match *acpi_msg { - AcpiBatteryRequest::BatteryGetBixRequest { battery_id } => self.bix_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatteryGetBstRequest { battery_id } => self.bst_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatteryGetPsrRequest { battery_id } => self.psr_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatteryGetPifRequest { battery_id } => self.pif_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatteryGetBpsRequest { battery_id } => self.bps_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatterySetBtpRequest { battery_id, btp } => { - self.btp_handler(DeviceId(battery_id), btp).await - } - AcpiBatteryRequest::BatterySetBptRequest { battery_id, bpt } => { - self.bpt_handler(DeviceId(battery_id), bpt).await - } - AcpiBatteryRequest::BatteryGetBpcRequest { battery_id } => self.bpc_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatterySetBmcRequest { battery_id, bmc } => { - self.bmc_handler(DeviceId(battery_id), bmc).await - } - AcpiBatteryRequest::BatteryGetBmdRequest { battery_id } => self.bmd_handler(DeviceId(battery_id)).await, - AcpiBatteryRequest::BatteryGetBctRequest { battery_id, bct } => { - self.bct_handler(DeviceId(battery_id), bct).await - } - AcpiBatteryRequest::BatteryGetBtmRequest { battery_id, btm } => { - self.btm_handler(DeviceId(battery_id), btm).await - } - - AcpiBatteryRequest::BatterySetBmsRequest { battery_id, bms } => { - self.bms_handler(DeviceId(battery_id), bms).await - } - AcpiBatteryRequest::BatterySetBmaRequest { battery_id, bma } => { - self.bma_handler(DeviceId(battery_id), bma).await - } - AcpiBatteryRequest::BatteryGetStaRequest { battery_id } => self.sta_handler(DeviceId(battery_id)).await, - } - } - pub(crate) fn get_fuel_gauge(&self, id: DeviceId) -> Option<&'static Device> { for device in &self.fuel_gauges { if let Some(data) = device.data::() { diff --git a/battery-service/src/device.rs b/battery-service/src/device.rs index 3c0f73785..e4a3f28c0 100644 --- a/battery-service/src/device.rs +++ b/battery-service/src/device.rs @@ -9,7 +9,7 @@ use embedded_batteries_async::{ }; use embedded_services::{GlobalRawMutex, Node, NodeContainer, SyncCell}; -pub use battery_service_messages::DeviceId; +pub use battery_service_interface::DeviceId; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/battery-service/src/lib.rs b/battery-service/src/lib.rs index c0fbcb3a8..8548dbb81 100644 --- a/battery-service/src/lib.rs +++ b/battery-service/src/lib.rs @@ -2,13 +2,17 @@ use core::{any::Any, convert::Infallible}; -use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest, AcpiBatteryResult}; use context::BatteryEvent; use embedded_services::{ comms::{self, EndpointID}, error, info, trace, }; +use battery_service_interface::{ + BatteryError, Bct, BctReturnResult, BixFixedStrings, Bma, Bmc, Bmd, Bms, Bpc, Bps, Bpt, BstReturn, Btm, + BtmReturnResult, Btp, DeviceId, PifFixedStrings, PsrReturn, StaReturn, +}; + mod acpi; pub mod context; pub mod controller; @@ -153,6 +157,91 @@ impl<'hw, const N: usize> Service<'hw, N> { } } +impl<'hw, const N: usize> battery_service_interface::BatteryService for Service<'hw, N> { + async fn battery_charge_time( + &self, + battery_id: DeviceId, + charge_level: Bct, + ) -> Result { + self.inner.context.bct_handler(battery_id, charge_level).await + } + + async fn battery_info(&self, battery_id: DeviceId) -> Result { + self.inner.context.bix_handler(battery_id).await + } + + async fn set_battery_measurement_averaging_interval( + &self, + battery_id: DeviceId, + bma: Bma, + ) -> Result<(), BatteryError> { + self.inner.context.bma_handler(battery_id, bma).await + } + + async fn battery_maintenance_control(&self, battery_id: DeviceId, bmc: Bmc) -> Result<(), BatteryError> { + self.inner.context.bmc_handler(battery_id, bmc).await + } + + async fn battery_maintenance_data(&self, battery_id: DeviceId) -> Result { + self.inner.context.bmd_handler(battery_id).await + } + + async fn set_battery_measurement_sampling_time( + &self, + battery_id: DeviceId, + battery_measurement_sampling: Bms, + ) -> Result<(), BatteryError> { + self.inner + .context + .bms_handler(battery_id, battery_measurement_sampling) + .await + } + + async fn battery_power_characteristics(&self, battery_id: DeviceId) -> Result { + self.inner.context.bpc_handler(battery_id).await + } + + async fn battery_power_state(&self, battery_id: DeviceId) -> Result { + self.inner.context.bps_handler(battery_id).await + } + + async fn set_battery_power_threshold( + &self, + battery_id: DeviceId, + power_threshold: Bpt, + ) -> Result<(), BatteryError> { + self.inner.context.bpt_handler(battery_id, power_threshold).await + } + + async fn battery_status(&self, battery_id: DeviceId) -> Result { + self.inner.context.bst_handler(battery_id).await + } + + async fn battery_time_to_empty( + &self, + battery_id: DeviceId, + battery_discharge_rate: Btm, + ) -> Result { + self.inner.context.btm_handler(battery_id, battery_discharge_rate).await + } + + async fn set_battery_trip_point(&self, battery_id: DeviceId, btp: Btp) -> Result<(), BatteryError> { + self.inner.context.btp_handler(battery_id, btp).await + } + + async fn is_in_use(&self, battery_id: DeviceId) -> Result { + self.inner.context.psr_handler(battery_id).await + } + + async fn power_source_information(&self, power_source_id: DeviceId) -> Result { + self.inner.context.pif_handler(power_source_id).await + } + + async fn device_status(&self, battery_id: DeviceId) -> Result { + self.inner.context.sta_handler(battery_id).await + } +} + /// Errors that can occur during battery service initialization. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -192,22 +281,6 @@ where } } -impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'_, N> { - type RequestType = AcpiBatteryRequest; - type ResultType = AcpiBatteryResult; -} - -impl embedded_services::relay::mctp::RelayServiceHandler for Service<'_, N> { - async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { - trace!("Battery service: ACPI cmd recvd"); - let response = self.inner.context.process_acpi_cmd(&request).await; - if let Err(e) = response { - error!("Battery service command failed: {:?}", e) - } - response - } -} - impl comms::MailboxDelegate for ServiceInner { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { if let Some(event) = message.data.get::() { diff --git a/battery-service/src/mock.rs b/battery-service/src/mock.rs index 25ba29dea..d852ea65f 100644 --- a/battery-service/src/mock.rs +++ b/battery-service/src/mock.rs @@ -174,11 +174,11 @@ impl crate::controller::Controller for MockBatteryDriver { min_averaging_interval: Default::default(), cap_granularity_1: Default::default(), cap_granularity_2: Default::default(), - power_threshold_support: battery_service_messages::PowerThresholdSupport::empty(), + power_threshold_support: battery_service_interface::PowerThresholdSupport::empty(), max_instant_pwr_threshold: Default::default(), max_sus_pwr_threshold: Default::default(), - bmc_flags: battery_service_messages::BmcControlFlags::empty(), - bmd_capability: battery_service_messages::BmdCapabilityFlags::empty(), + bmc_flags: battery_service_interface::BmcControlFlags::empty(), + bmd_capability: battery_service_interface::BmdCapabilityFlags::empty(), bmd_recalibrate_count: Default::default(), bmd_quick_recalibrate_time: Default::default(), bmd_slow_recalibrate_time: Default::default(), diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 46376776a..e32f34de6 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -170,7 +170,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ - "battery-service-messages", + "battery-service-interface", "embassy-futures", "embassy-sync", "embassy-time", @@ -178,21 +178,17 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", - "heapless 0.8.0", "log", - "mctp-rs", "odp-service-common", "power-policy-interface", - "zerocopy", ] [[package]] -name = "battery-service-messages" +name = "battery-service-interface" version = "0.1.0" dependencies = [ "embedded-batteries-async", - "embedded-services", - "num_enum", + "log", ] [[package]] diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index aca2363b1..56cef1eb1 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ - "battery-service-messages", + "battery-service-interface", "defmt 0.3.100", "embassy-futures", "embassy-sync", @@ -50,21 +50,16 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", - "heapless", - "mctp-rs", "odp-service-common", "power-policy-interface", - "zerocopy", ] [[package]] -name = "battery-service-messages" +name = "battery-service-interface" version = "0.1.0" dependencies = [ "defmt 0.3.100", "embedded-batteries-async", - "embedded-services", - "num_enum", ] [[package]] diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index ee421b4f9..889bfe641 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -174,6 +174,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "bitfield-struct" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -548,7 +559,7 @@ dependencies = [ "itertools 0.11.0", "mimxrt600-fcb 0.2.2", "mimxrt633s-pac", - "mimxrt685s-pac 0.5.0", + "mimxrt685s-pac", "nb 1.1.0", "paste", "rand_core", @@ -612,7 +623,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e14d288a59ef41f4e05468eae9b1c9fef6866977cea86d3f1a1ced295b6cab" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", "bitflags 2.9.4", "defmt 0.3.100", "embedded-hal 1.0.0", @@ -625,7 +636,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b8996d7168535579180a0eead82efaba718ebd598782f986bfd635458259df2" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.10.1", "bitflags 2.9.4", "embedded-hal 1.0.0", "zerocopy", @@ -633,11 +644,11 @@ dependencies = [ [[package]] name = "embedded-batteries-async" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6465f32edac01ccd4d55931896177b3a61a5286205c5269f30b91ce7dbf51b5e" +checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.12.1", "embedded-batteries 0.3.1", "embedded-hal 1.0.0", ] @@ -1096,18 +1107,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "mimxrt685s-pac" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0b80e5add9dc74500acbb1ca70248e237d242b77988631e41db40a225f3a40" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "vcell", -] - [[package]] name = "mimxrt685s-pac" version = "0.5.0" @@ -1415,7 +1414,7 @@ dependencies = [ "embedded-usb-pd", "futures", "mimxrt600-fcb 0.1.0", - "mimxrt685s-pac 0.4.0", + "mimxrt685s-pac", "odp-service-common", "panic-probe", "platform-service", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index a4c0d09dd..218efb11c 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -155,7 +155,7 @@ dependencies = [ name = "battery-service" version = "0.1.0" dependencies = [ - "battery-service-messages", + "battery-service-interface", "embassy-futures", "embassy-sync", "embassy-time", @@ -163,21 +163,17 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-services", - "heapless", "log", - "mctp-rs", "odp-service-common", "power-policy-interface", - "zerocopy", ] [[package]] -name = "battery-service-messages" +name = "battery-service-interface" version = "0.1.0" dependencies = [ "embedded-batteries-async", - "embedded-services", - "num_enum", + "log", ] [[package]] From 802cd35536c5e467715de2c1f972d060785aa789 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Mon, 13 Apr 2026 07:34:36 -0700 Subject: [PATCH 59/79] thermal-service: Separate interface from implementation (#777) This PR overhauls the thermal-service a bit. Essentially, the thermal interface has been split from the implementation into a trait, IPC has been removed and replaced with direct async calls, and a relay service handler wrapper has been introduced. Instead of one monolithic "thermal service", there are really 3 services here: sensor service, fan service, and thermal service, each with their own interfaces. The thermal service now mainly exists as the relay handler, since it can keep track of the different spawned sensor and fan services by ID, therefore knowing which sensors and fans to talk to when a request comes from the host. Also split into a thermal service relay handler type which is intended to wrap any thermal service implementation. There are some things I will likely want to tweak in the future based off some work Robert has been doing such as better use of some of the event sender/receiver traits and use of the Lockable trait (I heavily use interior mutability with direct dep on embassy Mutex, not sure if I can somehow make this generic over the Lockable trait?). With the removal of IPC, need to also think about how to handle heterogeneous collections of sensors and fans. Currently this design just assumes the user will need to handle that on their own, by introducing enum dispatch or something behind the scenes, but will investigate if there are ways to make that easier (such as using the `enum_dispatch` crate and marking these traits). This is of course a large breaking change so will announce on Zulip after merge. Resolves #756 Resolves #781 --- Cargo.lock | 19 +- Cargo.toml | 6 +- embedded-service/src/event.rs | 23 +- examples/std/Cargo.lock | 16 +- examples/std/Cargo.toml | 2 +- examples/std/src/bin/thermal.rs | 115 ++- thermal-service-interface/Cargo.toml | 23 + thermal-service-interface/src/fan.rs | 133 +++ thermal-service-interface/src/lib.rs | 17 + thermal-service-interface/src/sensor.rs | 98 +++ .../Cargo.toml | 3 +- thermal-service-relay/src/lib.rs | 223 +++++ .../src/serialization.rs | 46 +- thermal-service/Cargo.toml | 9 +- thermal-service/src/context.rs | 61 -- thermal-service/src/fan.rs | 799 +++++++----------- thermal-service/src/lib.rs | 127 ++- thermal-service/src/mock/fan.rs | 17 +- thermal-service/src/mock/mod.rs | 50 -- thermal-service/src/mock/sensor.rs | 16 +- thermal-service/src/mptf.rs | 311 ------- thermal-service/src/sensor.rs | 662 +++++---------- thermal-service/src/utils.rs | 23 +- 23 files changed, 1236 insertions(+), 1563 deletions(-) create mode 100644 thermal-service-interface/Cargo.toml create mode 100644 thermal-service-interface/src/fan.rs create mode 100644 thermal-service-interface/src/lib.rs create mode 100644 thermal-service-interface/src/sensor.rs rename {thermal-service-messages => thermal-service-relay}/Cargo.toml (81%) create mode 100644 thermal-service-relay/src/lib.rs rename thermal-service-messages/src/lib.rs => thermal-service-relay/src/serialization.rs (89%) delete mode 100644 thermal-service/src/context.rs delete mode 100644 thermal-service/src/mptf.rs diff --git a/Cargo.lock b/Cargo.lock index 316b90384..d59e46237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2273,25 +2273,32 @@ dependencies = [ "embassy-sync", "embassy-time", "embedded-fans-async", - "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-sensors-hal-async", "embedded-services", "heapless", "log", - "mctp-rs", "odp-service-common", - "thermal-service-messages", - "uuid", + "thermal-service-interface", +] + +[[package]] +name = "thermal-service-interface" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embassy-time", + "embedded-fans-async", + "embedded-sensors-hal-async", ] [[package]] -name = "thermal-service-messages" +name = "thermal-service-relay" version = "0.1.0" dependencies = [ "defmt 0.3.100", "embedded-services", "num_enum", + "thermal-service-interface", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index b78043344..ffa634e5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,8 @@ members = [ "battery-service-interface", "battery-service-relay", "thermal-service", - "thermal-service-messages", + "thermal-service-interface", + "thermal-service-relay", "cfu-service", "embedded-service", "espi-service", @@ -107,7 +108,8 @@ rstest = { version = "0.26.1", default-features = false } serde = { version = "1.0.*", default-features = false } static_cell = "2.1.0" toml = { version = "0.8", default-features = false } -thermal-service-messages = { path = "./thermal-service-messages" } +thermal-service-interface = { path = "./thermal-service-interface" } +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" } diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index ec02e7c53..a24736761 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -4,7 +4,8 @@ use core::{future::ready, marker::PhantomData}; use crate::error; use embassy_sync::{ - channel::{DynamicReceiver, DynamicSender}, + blocking_mutex::raw::RawMutex, + channel::{DynamicReceiver, DynamicSender, Receiver as ChannelReceiver, Sender as ChannelSender}, pubsub::{DynImmediatePublisher, DynSubscriber, WaitResult}, }; @@ -84,6 +85,26 @@ impl Receiver for DynSubscriber<'_, E> { } } +impl Sender for ChannelSender<'_, M, E, N> { + fn try_send(&mut self, event: E) -> Option<()> { + ChannelSender::try_send(self, event).ok() + } + + fn send(&mut self, event: E) -> impl Future { + ChannelSender::send(self, event) + } +} + +impl Receiver for ChannelReceiver<'_, M, E, N> { + fn try_next(&mut self) -> Option { + ChannelReceiver::try_receive(self).ok() + } + + fn wait_next(&mut self) -> impl Future { + ChannelReceiver::receive(self) + } +} + /// A sender that discards all events sent to it. pub struct NoopSender; diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 218efb11c..bf6cec719 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1656,7 +1656,7 @@ dependencies = [ "power-policy-service", "static_cell", "thermal-service", - "thermal-service-messages", + "thermal-service-interface", "type-c-interface", "type-c-service", ] @@ -1715,25 +1715,21 @@ dependencies = [ "embassy-sync", "embassy-time", "embedded-fans-async", - "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-sensors-hal-async", "embedded-services", "heapless", "log", - "mctp-rs", "odp-service-common", - "thermal-service-messages", - "uuid", + "thermal-service-interface", ] [[package]] -name = "thermal-service-messages" +name = "thermal-service-interface" version = "0.1.0" dependencies = [ - "embedded-services", - "num_enum", - "uuid", + "embassy-time", + "embedded-fans-async", + "embedded-sensors-hal-async", ] [[package]] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index ce6047a7e..dd60ffb6c 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -44,7 +44,7 @@ type-c-interface = { path = "../../type-c-interface", features = ["log"] } embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } -thermal-service-messages = { path = "../../thermal-service-messages" } +thermal-service-interface = { path = "../../thermal-service-interface" } env_logger = "0.11.8" log = "0.4.14" diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index f7585071e..fa3075494 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -1,47 +1,83 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::once_lock::OnceLock; +use embassy_sync::channel::{Channel, Receiver as ChannelReceiver, Sender as ChannelSender}; use embassy_time::Timer; -use embedded_services::{error, info}; +use embedded_services::GlobalRawMutex; +use embedded_services::{info, warn}; use static_cell::StaticCell; use thermal_service as ts; +use thermal_service_interface::ThermalService; +use thermal_service_interface::fan::FanService; +use thermal_service_interface::sensor; +use thermal_service_interface::sensor::SensorService; + +// More readable type aliases for sensor, fan, and thermal services used in this example +type MockSensorService = ts::sensor::Service< + 'static, + ts::mock::sensor::MockSensor, + ChannelSender<'static, GlobalRawMutex, sensor::Event, 4>, + 16, +>; +type MockFanService = + ts::fan::Service<'static, ts::mock::fan::MockFan, MockSensorService, embedded_services::event::NoopSender, 16>; +type MockThermalService = ts::Service<'static, MockSensorService, MockFanService>; #[embassy_executor::task] async fn run(spawner: Spawner) { embedded_services::init().await; - static SENSOR: StaticCell = StaticCell::new(); - let sensor = SENSOR.init(ts::mock::new_sensor()); - - static FAN: StaticCell = StaticCell::new(); - let fan = FAN.init(ts::mock::new_fan()); - - static SENSORS: StaticCell<[&'static ts::sensor::Device; 1]> = StaticCell::new(); - let sensors = SENSORS.init([sensor.device()]); + // Create a backing channel for sensor events to be sent on + static SENSOR_EVENT_CHANNEL: StaticCell> = StaticCell::new(); + let sensor_event_channel = SENSOR_EVENT_CHANNEL.init(Channel::new()); - static FANS: StaticCell<[&'static ts::fan::Device; 1]> = StaticCell::new(); - let fans = FANS.init([fan.device()]); + // Then create the list of senders for the sensor service to use + // Though we are only using one sender in this example, an abitrary number could be used + static SENSOR_SENDERS: StaticCell<[ChannelSender<'static, GlobalRawMutex, sensor::Event, 4>; 1]> = + StaticCell::new(); + let event_senders = SENSOR_SENDERS.init([sensor_event_channel.sender()]); - static STORAGE: OnceLock> = OnceLock::new(); - let thermal_service = ts::Service::init(&STORAGE, sensors, fans).await; - - let _fan_service = odp_service_common::spawn_service!( + // Spawn the sensor service which will begin running and generating events + let sensor_service = odp_service_common::spawn_service!( spawner, - ts::fan::Service<'static, ts::mock::fan::MockFan, 16>, - ts::fan::InitParams { fan, thermal_service } + MockSensorService, + ts::sensor::InitParams { + driver: ts::mock::sensor::MockSensor::new(), + config: ts::mock::sensor::MockSensor::config(), + event_senders, + } ) - .expect("Failed to spawn fan service"); + .expect("Failed to spawn sensor service"); - let _sensor_service = odp_service_common::spawn_service!( + // Spawn the fan service which uses the above sensor service for automatic speed control + // In this example, we use an empty event sender list since the fan won't generate any events + let fan_service = odp_service_common::spawn_service!( spawner, - ts::sensor::Service<'static, ts::mock::sensor::MockSensor, 16>, - ts::sensor::InitParams { - sensor, - thermal_service + MockFanService, + ts::fan::InitParams { + driver: ts::mock::fan::MockFan::new(), + config: ts::mock::fan::MockFan::config(), + sensor_service, + event_senders: &mut [], } ) - .expect("Failed to spawn sensor service"); + .expect("Failed to spawn fan service"); + + // The thermal service accepts slices of associated sensors and fans, + // so we need static lifetime here since the thermal service handle is passed to task + static SENSORS: StaticCell<[MockSensorService; 1]> = StaticCell::new(); + let sensors = SENSORS.init([sensor_service]); + + static FANS: StaticCell<[MockFanService; 1]> = StaticCell::new(); + let fans = FANS.init([fan_service]); + + // The thermal service handle mainly exists for host relaying, but this example does not make use of that + // + // However, we can still use the thermal service handle to access registered sensors and fans by id + static RESOURCES: StaticCell> = StaticCell::new(); + let resources = RESOURCES.init(ts::Resources::default()); + let thermal_service = ts::Service::init(resources, ts::InitParams { sensors, fans }); spawner.must_spawn(monitor(thermal_service)); + spawner.must_spawn(sensor_event_listener(sensor_event_channel.receiver())); } fn main() { @@ -55,21 +91,24 @@ fn main() { } #[embassy_executor::task] -async fn monitor(service: &'static ts::Service<'static>) { +async fn sensor_event_listener(receiver: ChannelReceiver<'static, GlobalRawMutex, sensor::Event, 4>) { + loop { + let event = receiver.receive().await; + warn!("Sensor event: {:?}", event); + } +} + +#[embassy_executor::task] +async fn monitor(service: MockThermalService) { loop { - match service - .execute_sensor_request(ts::mock::MOCK_SENSOR_ID, ts::sensor::Request::GetTemp) - .await - { - Ok(ts::sensor::ResponseData::Temp(temp)) => info!("Mock sensor temp: {} C", temp), - _ => error!("Failed to monitor mock sensor temp"), + if let Some(sensor) = service.sensor(0) { + let temp = sensor.temperature().await; + info!("Mock sensor temp: {} C", temp); } - match service - .execute_fan_request(ts::mock::MOCK_FAN_ID, ts::fan::Request::GetRpm) - .await - { - Ok(ts::fan::ResponseData::Rpm(rpm)) => info!("Mock fan RPM: {}", rpm), - _ => error!("Failed to monitor mock fan RPM"), + + if let Some(fan) = service.fan(0) { + let rpm = fan.rpm().await; + info!("Mock fan RPM: {}", rpm); } Timer::after_secs(1).await; diff --git a/thermal-service-interface/Cargo.toml b/thermal-service-interface/Cargo.toml new file mode 100644 index 000000000..f3ff7885a --- /dev/null +++ b/thermal-service-interface/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "thermal-service-interface" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +defmt = { workspace = true, optional = true } +embassy-time.workspace = true +embedded-fans-async = "0.2.0" +embedded-sensors-hal-async = "0.3.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-time/defmt", + "embedded-fans-async/defmt", + "embedded-sensors-hal-async/defmt", +] diff --git a/thermal-service-interface/src/fan.rs b/thermal-service-interface/src/fan.rs new file mode 100644 index 000000000..86a23a263 --- /dev/null +++ b/thermal-service-interface/src/fan.rs @@ -0,0 +1,133 @@ +use core::future::Future; +use embassy_time::Duration; +use embedded_fans_async::{Fan, RpmSense}; +use embedded_sensors_hal_async::temperature::DegreesCelsius; + +/// Ensures all necessary traits are implemented for the underlying fan driver. +pub trait Driver: Fan + RpmSense {} + +/// Fan error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Fan encountered a hardware failure. + Hardware, +} + +/// Fan event. +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Event { + /// Fan encountered a failure. + Failure(Error), +} + +/// Fan on (running) state. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OnState { + /// Fan is on and running at its minimum speed. + Min, + /// Fan is ramping up or down along a curve in response to a temperature change. + Ramping, + /// Fan is running at its maximum speed. + Max, +} + +/// Fan state. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + /// Fan is off. + Off, + /// Fan is on in the specified [`OnState`]. + On(OnState), +} + +/// Fan service interface trait. +pub trait FanService { + /// Enable automatic fan control. + /// + /// This allows the fan to automatically change [`State`] based on periodic readings from an associated temperature sensor. + fn enable_auto_control(&self) -> impl Future>; + /// Returns the most recently sampled RPM measurement. + fn rpm(&self) -> impl Future; + /// Returns the minimum RPM supported by the fan. + fn min_rpm(&self) -> impl Future; + /// Returns the maximum RPM supported by the fan. + fn max_rpm(&self) -> impl Future; + /// Returns the average RPM over a sampling period. + fn rpm_average(&self) -> impl Future; + /// Immediately samples the fan for an RPM measurement and returns the result. + fn rpm_immediate(&self) -> impl Future>; + /// Sets the fan to run at the specified RPM (and disables automatic control). + fn set_rpm(&self, rpm: u16) -> impl Future>; + /// Sets the fan to run at the specified duty cycle percentage (and disables automatic control). + fn set_duty_percent(&self, duty: u8) -> impl Future>; + /// Stops the fan (and disables automatic control). + fn stop(&self) -> impl Future>; + /// Set the rate at which RPM measurements are sampled. + fn set_rpm_sampling_period(&self, period: Duration) -> impl Future; + /// Set the rate at which the fan will update its RPM in response to a temperature change when in automatic control mode. + fn set_rpm_update_period(&self, period: Duration) -> impl Future; + /// Returns the temperature at which the fan will change to the specified [`OnState`] when in automatic control mode. + fn state_temp(&self, state: OnState) -> impl Future; + /// Sets the temperature at which the fan will change to the specified [`OnState`] when in automatic control mode. + fn set_state_temp(&self, state: OnState, temp: DegreesCelsius) -> impl Future; +} + +impl FanService for &T { + fn enable_auto_control(&self) -> impl Future> { + T::enable_auto_control(self) + } + + fn rpm(&self) -> impl Future { + T::rpm(self) + } + + fn min_rpm(&self) -> impl Future { + T::min_rpm(self) + } + + fn max_rpm(&self) -> impl Future { + T::max_rpm(self) + } + + fn rpm_average(&self) -> impl Future { + T::rpm_average(self) + } + + fn rpm_immediate(&self) -> impl Future> { + T::rpm_immediate(self) + } + + fn set_rpm(&self, rpm: u16) -> impl Future> { + T::set_rpm(self, rpm) + } + + fn set_duty_percent(&self, duty: u8) -> impl Future> { + T::set_duty_percent(self, duty) + } + + fn stop(&self) -> impl Future> { + T::stop(self) + } + + fn set_rpm_sampling_period(&self, period: Duration) -> impl Future { + T::set_rpm_sampling_period(self, period) + } + + fn set_rpm_update_period(&self, period: Duration) -> impl Future { + T::set_rpm_update_period(self, period) + } + + fn state_temp(&self, state: OnState) -> impl Future { + T::state_temp(self, state) + } + + fn set_state_temp(&self, state: OnState, temp: DegreesCelsius) -> impl Future { + T::set_state_temp(self, state, temp) + } +} diff --git a/thermal-service-interface/src/lib.rs b/thermal-service-interface/src/lib.rs new file mode 100644 index 000000000..b1e56a100 --- /dev/null +++ b/thermal-service-interface/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] + +pub mod fan; +pub mod sensor; + +/// Thermal service interface trait. +pub trait ThermalService { + /// Associated type for registered sensor services. + type Sensor: sensor::SensorService; + /// Associated type for registered fan services. + type Fan: fan::FanService; + + /// Retrieve a handle to the sensor service with the specified instance ID, if it exists. + fn sensor(&self, id: u8) -> Option; + /// Retrieve a handle to the fan service with the specified instance ID, if it exists. + fn fan(&self, id: u8) -> Option; +} diff --git a/thermal-service-interface/src/sensor.rs b/thermal-service-interface/src/sensor.rs new file mode 100644 index 000000000..70769e1bb --- /dev/null +++ b/thermal-service-interface/src/sensor.rs @@ -0,0 +1,98 @@ +use core::future::Future; +use embassy_time::Duration; +use embedded_sensors_hal_async::temperature::{DegreesCelsius, TemperatureSensor}; + +/// Ensures all necessary traits are implemented for the underlying sensor driver. +pub trait Driver: TemperatureSensor {} + +/// Sensor error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Sensor encountered a hardware failure. + Hardware, + /// Retry attempts to communicate with sensor exhausted. + RetryExhausted, +} + +/// Sensor event. +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Event { + /// A sensor threshold was exceeded. + ThresholdExceeded(Threshold), + /// A sensor threshold which was previously exceeded is now cleared. + ThresholdCleared(Threshold), + /// Sensor encountered a failure. + Failure(Error), +} + +/// Sensor threshold types. +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Threshold { + /// The temperature threshold below which a warning event is generated. + WarnLow, + /// The temperature threshold above which a warning event is generated. + WarnHigh, + /// The temperature threshold above which a prochot event is generated. + Prochot, + /// The temperature threshold above which a critical event is generated. + Critical, +} + +/// Sensor service interface trait +pub trait SensorService { + /// Returns the most recently sampled temperature measurement in degrees Celsius. + fn temperature(&self) -> impl Future; + /// Returns the average temperature over a sampling period in degrees Celsius. + fn temperature_average(&self) -> impl Future; + /// Immediately samples the sensor for a temperature measurement and returns the result in degrees Celsius. + fn temperature_immediate(&self) -> impl Future>; + /// Sets the temperature for which a sensor event will be generated when the threshold is exceeded, in degrees Celsius. + fn set_threshold(&self, threshold: Threshold, value: DegreesCelsius) -> impl Future; + /// Returns the temperature threshold value for the specified threshold type in degrees Celsius. + fn threshold(&self, threshold: Threshold) -> impl Future; + /// Sets the rate at which temperature measurements are sampled. + fn set_sample_period(&self, period: Duration) -> impl Future; + /// Enable periodic temperature sampling. + fn enable_sampling(&self) -> impl Future; + /// Disable periodic temperature sampling. + fn disable_sampling(&self) -> impl Future; +} + +impl SensorService for &T { + async fn temperature(&self) -> DegreesCelsius { + T::temperature(self).await + } + + async fn temperature_average(&self) -> DegreesCelsius { + T::temperature_average(self).await + } + + async fn temperature_immediate(&self) -> Result { + T::temperature_immediate(self).await + } + + async fn set_threshold(&self, threshold: Threshold, value: DegreesCelsius) { + T::set_threshold(self, threshold, value).await + } + + async fn threshold(&self, threshold: Threshold) -> DegreesCelsius { + T::threshold(self, threshold).await + } + + async fn set_sample_period(&self, period: Duration) { + T::set_sample_period(self, period).await + } + + async fn enable_sampling(&self) { + T::enable_sampling(self).await + } + + async fn disable_sampling(&self) { + T::disable_sampling(self).await + } +} diff --git a/thermal-service-messages/Cargo.toml b/thermal-service-relay/Cargo.toml similarity index 81% rename from thermal-service-messages/Cargo.toml rename to thermal-service-relay/Cargo.toml index 2d9dbc37e..43f7c1dbd 100644 --- a/thermal-service-messages/Cargo.toml +++ b/thermal-service-relay/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "thermal-service-messages" +name = "thermal-service-relay" version.workspace = true edition.workspace = true license.workspace = true @@ -8,6 +8,7 @@ repository.workspace = true [dependencies] defmt = { workspace = true, optional = true } embedded-services.workspace = true +thermal-service-interface.workspace = true num_enum.workspace = true uuid.workspace = true diff --git a/thermal-service-relay/src/lib.rs b/thermal-service-relay/src/lib.rs new file mode 100644 index 000000000..fceae864d --- /dev/null +++ b/thermal-service-relay/src/lib.rs @@ -0,0 +1,223 @@ +#![no_std] + +mod serialization; + +pub use serialization::{ThermalError, ThermalRequest, ThermalResponse, ThermalResult}; +use thermal_service_interface::ThermalService; +use thermal_service_interface::fan::{self, FanService}; +use thermal_service_interface::sensor::{self, SensorService}; + +/// DeciKelvin temperature representation. +/// +/// This exists because the host to EC interface expects DeciKelvin, +/// though internally we still use Celsius for ease of use. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DeciKelvin(pub u32); + +impl DeciKelvin { + /// Convert from degrees Celsius to DeciKelvin. + pub const fn from_celsius(c: f32) -> Self { + Self(((c + 273.15) * 10.0) as u32) + } + + /// Convert from DeciKelvin to degrees Celsius. + pub const fn to_celsius(self) -> f32 { + (self.0 as f32 / 10.0) - 273.15 + } +} + +/// MPTF Standard UUIDs which the thermal service understands. +pub mod uuid_standard { + /// The critical temperature threshold of a sensor. + pub const CRT_TEMP: uuid::Bytes = uuid::uuid!("218246e7-baf6-45f1-aa13-07e4845256b8").to_bytes_le(); + /// The prochot temperature threshold of a sensor. + pub const PROC_HOT_TEMP: uuid::Bytes = uuid::uuid!("22dc52d2-fd0b-47ab-95b8-26552f9831a5").to_bytes_le(); + /// The temperature threshold at which a fan should turn on and begin running at its minimum RPM. + pub const FAN_MIN_TEMP: uuid::Bytes = uuid::uuid!("ba17b567-c368-48d5-bc6f-a312a41583c1").to_bytes_le(); + /// The temperature threshold at which a fan should start ramping up. + pub const FAN_RAMP_TEMP: uuid::Bytes = uuid::uuid!("3a62688c-d95b-4d2d-bacc-90d7a5816bcd").to_bytes_le(); + /// The temperature threshold at which a fan should be at max speed. + pub const FAN_MAX_TEMP: uuid::Bytes = uuid::uuid!("dcb758b1-f0fd-4ec7-b2c0-ef1e2a547b76").to_bytes_le(); + /// The minimum RPM a fan is capable of running at reliably. + pub const FAN_MIN_RPM: uuid::Bytes = uuid::uuid!("db261c77-934b-45e2-9742-256c62badb7a").to_bytes_le(); + /// The maximum RPM a fan is capable of running at reliably. + pub const FAN_MAX_RPM: uuid::Bytes = uuid::uuid!("5cf839df-8be7-42b9-9ac5-3403ca2c8a6a").to_bytes_le(); + /// The current RPM of a fan. + pub const FAN_CURRENT_RPM: uuid::Bytes = uuid::uuid!("adf95492-0776-4ffc-84f3-b6c8b5269683").to_bytes_le(); +} + +/// Thermal service relay handler which wraps a thermal service instance. +pub struct ThermalServiceRelayHandler { + service: T, +} + +impl ThermalServiceRelayHandler { + /// Create a new thermal service relay handler. + pub fn new(service: T) -> Self { + Self { service } + } + + async fn sensor_get_tmp(&self, instance_id: u8) -> ThermalResult { + let sensor = self.service.sensor(instance_id).ok_or(ThermalError::InvalidParameter)?; + let temp = sensor.temperature().await; + Ok(ThermalResponse::ThermalGetTmpResponse { + temperature: DeciKelvin::from_celsius(temp), + }) + } + + async fn sensor_set_warn_thrs( + &self, + instance_id: u8, + _timeout: u32, + low: DeciKelvin, + high: DeciKelvin, + ) -> ThermalResult { + let sensor = self.service.sensor(instance_id).ok_or(ThermalError::InvalidParameter)?; + sensor.set_threshold(sensor::Threshold::WarnLow, low.to_celsius()).await; + sensor + .set_threshold(sensor::Threshold::WarnHigh, high.to_celsius()) + .await; + Ok(ThermalResponse::ThermalSetThrsResponse) + } + + async fn get_var_handler(&self, instance_id: u8, var_uuid: uuid::Bytes) -> ThermalResult { + match var_uuid { + uuid_standard::CRT_TEMP => self.sensor_get_thrs(instance_id, sensor::Threshold::Critical).await, + uuid_standard::PROC_HOT_TEMP => self.sensor_get_thrs(instance_id, sensor::Threshold::Prochot).await, + uuid_standard::FAN_MIN_TEMP => self.fan_get_state_temp(instance_id, fan::OnState::Min).await, + uuid_standard::FAN_RAMP_TEMP => self.fan_get_state_temp(instance_id, fan::OnState::Ramping).await, + uuid_standard::FAN_MAX_TEMP => self.fan_get_state_temp(instance_id, fan::OnState::Max).await, + uuid_standard::FAN_MIN_RPM => self.fan_get_min_rpm(instance_id).await, + uuid_standard::FAN_MAX_RPM => self.fan_get_max_rpm(instance_id).await, + uuid_standard::FAN_CURRENT_RPM => self.fan_get_rpm(instance_id).await, + _ => Err(ThermalError::InvalidParameter), + } + } + + async fn set_var_handler(&self, instance_id: u8, var_uuid: uuid::Bytes, set_var: u32) -> ThermalResult { + match var_uuid { + uuid_standard::CRT_TEMP => { + self.sensor_set_thrs(instance_id, sensor::Threshold::Critical, set_var) + .await + } + uuid_standard::PROC_HOT_TEMP => { + self.sensor_set_thrs(instance_id, sensor::Threshold::Prochot, set_var) + .await + } + uuid_standard::FAN_MIN_TEMP => { + self.fan_set_state_temp(instance_id, fan::OnState::Min, DeciKelvin(set_var)) + .await + } + uuid_standard::FAN_RAMP_TEMP => { + self.fan_set_state_temp(instance_id, fan::OnState::Ramping, DeciKelvin(set_var)) + .await + } + uuid_standard::FAN_MAX_TEMP => { + self.fan_set_state_temp(instance_id, fan::OnState::Max, DeciKelvin(set_var)) + .await + } + uuid_standard::FAN_CURRENT_RPM => { + let rpm = u16::try_from(set_var).map_err(|_| ThermalError::InvalidParameter)?; + self.fan_set_rpm(instance_id, rpm).await + } + _ => Err(ThermalError::InvalidParameter), + } + } + + async fn fan_get_state_temp(&self, instance_id: u8, state: fan::OnState) -> ThermalResult { + let fan = self.service.fan(instance_id).ok_or(ThermalError::InvalidParameter)?; + let temp = fan.state_temp(state).await; + Ok(ThermalResponse::ThermalGetVarResponse { + val: DeciKelvin::from_celsius(temp).0, + }) + } + + async fn fan_get_rpm(&self, instance_id: u8) -> ThermalResult { + let fan = self.service.fan(instance_id).ok_or(ThermalError::InvalidParameter)?; + let rpm = fan.rpm().await; + Ok(ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) + } + + async fn fan_get_min_rpm(&self, instance_id: u8) -> ThermalResult { + let fan = self.service.fan(instance_id).ok_or(ThermalError::InvalidParameter)?; + let rpm = fan.min_rpm().await; + Ok(ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) + } + + async fn fan_get_max_rpm(&self, instance_id: u8) -> ThermalResult { + let fan = self.service.fan(instance_id).ok_or(ThermalError::InvalidParameter)?; + let rpm = fan.max_rpm().await; + Ok(ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) + } + + async fn sensor_set_thrs(&self, instance_id: u8, threshold: sensor::Threshold, threshold_dk: u32) -> ThermalResult { + let sensor = self.service.sensor(instance_id).ok_or(ThermalError::InvalidParameter)?; + sensor + .set_threshold(threshold, DeciKelvin(threshold_dk).to_celsius()) + .await; + Ok(ThermalResponse::ThermalSetVarResponse) + } + + async fn sensor_get_thrs(&self, instance_id: u8, threshold: sensor::Threshold) -> ThermalResult { + let sensor = self.service.sensor(instance_id).ok_or(ThermalError::InvalidParameter)?; + let temp = sensor.threshold(threshold).await; + Ok(ThermalResponse::ThermalGetVarResponse { + val: DeciKelvin::from_celsius(temp).0, + }) + } + + async fn sensor_get_warn_thrs(&self, instance_id: u8) -> ThermalResult { + let sensor = self.service.sensor(instance_id).ok_or(ThermalError::InvalidParameter)?; + let low = sensor.threshold(sensor::Threshold::WarnLow).await; + let high = sensor.threshold(sensor::Threshold::WarnHigh).await; + Ok(ThermalResponse::ThermalGetThrsResponse { + timeout: 0, + low: DeciKelvin::from_celsius(low), + high: DeciKelvin::from_celsius(high), + }) + } + + async fn fan_set_state_temp(&self, instance_id: u8, state: fan::OnState, temp: DeciKelvin) -> ThermalResult { + let fan = self.service.fan(instance_id).ok_or(ThermalError::InvalidParameter)?; + fan.set_state_temp(state, temp.to_celsius()).await; + Ok(ThermalResponse::ThermalSetVarResponse) + } + + async fn fan_set_rpm(&self, instance_id: u8, rpm: u16) -> ThermalResult { + let fan = self.service.fan(instance_id).ok_or(ThermalError::InvalidParameter)?; + fan.set_rpm(rpm).await.map_err(|_| ThermalError::HardwareError)?; + Ok(ThermalResponse::ThermalSetVarResponse) + } +} + +impl embedded_services::relay::mctp::RelayServiceHandlerTypes for ThermalServiceRelayHandler { + type RequestType = ThermalRequest; + type ResultType = ThermalResult; +} + +impl embedded_services::relay::mctp::RelayServiceHandler for ThermalServiceRelayHandler { + async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { + match request { + ThermalRequest::ThermalGetTmpRequest { instance_id } => self.sensor_get_tmp(instance_id).await, + ThermalRequest::ThermalSetThrsRequest { + instance_id, + timeout, + low, + high, + } => self.sensor_set_warn_thrs(instance_id, timeout, low, high).await, + ThermalRequest::ThermalGetThrsRequest { instance_id } => self.sensor_get_warn_thrs(instance_id).await, + // Revisit: Don't currently have a good strategy for handling this request + ThermalRequest::ThermalSetScpRequest { .. } => Err(ThermalError::InvalidParameter), + ThermalRequest::ThermalGetVarRequest { + instance_id, var_uuid, .. + } => self.get_var_handler(instance_id, var_uuid).await, + ThermalRequest::ThermalSetVarRequest { + instance_id, + var_uuid, + set_var, + .. + } => self.set_var_handler(instance_id, var_uuid, set_var).await, + } + } +} diff --git a/thermal-service-messages/src/lib.rs b/thermal-service-relay/src/serialization.rs similarity index 89% rename from thermal-service-messages/src/lib.rs rename to thermal-service-relay/src/serialization.rs index c2b371ab9..6b1a7a63d 100644 --- a/thermal-service-messages/src/lib.rs +++ b/thermal-service-relay/src/serialization.rs @@ -1,20 +1,7 @@ -#![no_std] - +use crate::DeciKelvin; use embedded_services::relay::{MessageSerializationError, SerializableMessage}; -/// 16-bit variable length -pub type VarLen = u16; - -/// Instance ID -pub type InstanceId = u8; - -/// Time in milliseconds -pub type Milliseconds = u32; - -/// MPTF expects temperatures in tenth Kelvins -pub type DeciKelvin = u32; - -/// Standard MPTF requests expected by the thermal subsystem +// Standard MPTF requests expected by the thermal subsystem #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] #[repr(u16)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -67,7 +54,7 @@ pub enum ThermalRequest { }, ThermalSetThrsRequest { instance_id: u8, - timeout: Milliseconds, + timeout: u32, low: DeciKelvin, high: DeciKelvin, }, @@ -82,18 +69,17 @@ pub enum ThermalRequest { }, ThermalGetVarRequest { instance_id: u8, - len: VarLen, // TODO why is there a len here? as far as I can tell we're always discarding it, and I think values are only u32? + len: u16, var_uuid: uuid::Bytes, }, ThermalSetVarRequest { instance_id: u8, - len: VarLen, // TODO why is there a len here? as far as I can tell we're always discarding it, and I think values are only u32? + len: u16, var_uuid: uuid::Bytes, set_var: u32, }, } -// TODO this is essentially a hand-written reinterpret_cast - can we codegen some of this instead? impl SerializableMessage for ThermalRequest { fn serialize(self, buffer: &mut [u8]) -> Result { match self { @@ -105,8 +91,8 @@ impl SerializableMessage for ThermalRequest { high, } => Ok(safe_put_u8(buffer, 0, instance_id)? + safe_put_dword(buffer, 1, timeout)? - + safe_put_dword(buffer, 5, low)? - + safe_put_dword(buffer, 9, high)?), + + safe_put_dword(buffer, 5, low.0)? + + safe_put_dword(buffer, 9, high.0)?), Self::ThermalGetThrsRequest { instance_id } => safe_put_u8(buffer, 0, instance_id), Self::ThermalSetScpRequest { instance_id, @@ -147,8 +133,8 @@ impl SerializableMessage for ThermalRequest { ThermalCmd::SetThrs => Self::ThermalSetThrsRequest { instance_id: safe_get_u8(buffer, 0)?, timeout: safe_get_dword(buffer, 1)?, - low: safe_get_dword(buffer, 5)?, - high: safe_get_dword(buffer, 9)?, + low: DeciKelvin(safe_get_dword(buffer, 5)?), + high: DeciKelvin(safe_get_dword(buffer, 9)?), }, ThermalCmd::GetThrs => Self::ThermalGetThrsRequest { instance_id: safe_get_u8(buffer, 0)?, @@ -188,7 +174,7 @@ pub enum ThermalResponse { }, ThermalSetThrsResponse, ThermalGetThrsResponse { - timeout: Milliseconds, + timeout: u32, low: DeciKelvin, high: DeciKelvin, }, @@ -202,10 +188,10 @@ pub enum ThermalResponse { impl SerializableMessage for ThermalResponse { fn serialize(self, buffer: &mut [u8]) -> Result { match self { - Self::ThermalGetTmpResponse { temperature } => safe_put_dword(buffer, 0, temperature), + Self::ThermalGetTmpResponse { temperature } => safe_put_dword(buffer, 0, temperature.0), Self::ThermalGetThrsResponse { timeout, low, high } => Ok(safe_put_dword(buffer, 0, timeout)? - + safe_put_dword(buffer, 4, low)? - + safe_put_dword(buffer, 8, high)?), + + safe_put_dword(buffer, 4, low.0)? + + safe_put_dword(buffer, 8, high.0)?), Self::ThermalGetVarResponse { val } => safe_put_dword(buffer, 0, val), Self::ThermalSetVarResponse | Self::ThermalSetScpResponse | Self::ThermalSetThrsResponse => Ok(0), } @@ -217,13 +203,13 @@ impl SerializableMessage for ThermalResponse { .map_err(|_| MessageSerializationError::UnknownMessageDiscriminant(discriminant))? { ThermalCmd::GetTmp => Self::ThermalGetTmpResponse { - temperature: safe_get_dword(buffer, 0)?, + temperature: DeciKelvin(safe_get_dword(buffer, 0)?), }, ThermalCmd::SetThrs => Self::ThermalSetThrsResponse, ThermalCmd::GetThrs => Self::ThermalGetThrsResponse { timeout: safe_get_dword(buffer, 0)?, - low: safe_get_dword(buffer, 4)?, - high: safe_get_dword(buffer, 8)?, + low: DeciKelvin(safe_get_dword(buffer, 4)?), + high: DeciKelvin(safe_get_dword(buffer, 8)?), }, ThermalCmd::SetScp => Self::ThermalSetScpResponse, ThermalCmd::GetVar => Self::ThermalGetVarResponse { diff --git a/thermal-service/Cargo.toml b/thermal-service/Cargo.toml index 1da4f8e66..aaa7f6290 100644 --- a/thermal-service/Cargo.toml +++ b/thermal-service/Cargo.toml @@ -13,16 +13,12 @@ log = { workspace = true, optional = true } embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true -embedded-hal-async.workspace = true -embedded-hal.workspace = true embedded-services.workspace = true heapless.workspace = true odp-service-common.workspace = true -thermal-service-messages.workspace = true -uuid.workspace = true +thermal-service-interface.workspace = true embedded-fans-async = "0.2.0" embedded-sensors-hal-async = "0.3.0" -mctp-rs = { workspace = true, features = ["espi"] } [features] default = [] @@ -33,8 +29,7 @@ defmt = [ "embassy-sync/defmt", "embedded-fans-async/defmt", "embedded-sensors-hal-async/defmt", - "mctp-rs/defmt", - "thermal-service-messages/defmt", + "thermal-service-interface/defmt", ] log = [ "dep:log", diff --git a/thermal-service/src/context.rs b/thermal-service/src/context.rs deleted file mode 100644 index fa1cc442d..000000000 --- a/thermal-service/src/context.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Thermal service context -use crate::{Event, fan, sensor}; -use embassy_sync::channel::Channel; -use embedded_services::GlobalRawMutex; - -pub(crate) struct Context<'hw> { - // Registered temperature sensors - sensors: &'hw [&'hw sensor::Device], - // Registered fans - fans: &'hw [&'hw fan::Device], - // Event queue - events: Channel, -} - -impl<'hw> Context<'hw> { - pub(crate) const fn new(sensors: &'hw [&'hw sensor::Device], fans: &'hw [&'hw fan::Device]) -> Self { - Self { - sensors, - fans, - events: Channel::new(), - } - } - - pub(crate) fn sensors(&self) -> &[&sensor::Device] { - self.sensors - } - - pub(crate) fn get_sensor(&self, id: sensor::DeviceId) -> Option<&sensor::Device> { - self.sensors.iter().find(|sensor| sensor.id() == id).copied() - } - - pub(crate) async fn execute_sensor_request( - &self, - id: sensor::DeviceId, - request: sensor::Request, - ) -> sensor::Response { - let sensor = self.get_sensor(id).ok_or(sensor::Error::InvalidRequest)?; - sensor.execute_request(request).await - } - - pub(crate) fn fans(&self) -> &[&fan::Device] { - self.fans - } - - pub(crate) fn get_fan(&self, id: fan::DeviceId) -> Option<&fan::Device> { - self.fans.iter().find(|fan| fan.id() == id).copied() - } - - pub(crate) async fn execute_fan_request(&self, id: fan::DeviceId, request: fan::Request) -> fan::Response { - let fan = self.get_fan(id).ok_or(fan::Error::InvalidRequest)?; - fan.execute_request(request).await - } - - pub(crate) async fn send_event(&self, event: Event) { - self.events.send(event).await - } - - pub(crate) async fn wait_event(&self) -> Event { - self.events.receive().await - } -} diff --git a/thermal-service/src/fan.rs b/thermal-service/src/fan.rs index 7ba11fafe..3e0b24715 100644 --- a/thermal-service/src/fan.rs +++ b/thermal-service/src/fan.rs @@ -1,606 +1,405 @@ -//! Fan Device -use crate::Event; use crate::utils::SampleBuf; +use core::marker::PhantomData; use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; -use embassy_time::Timer; -use embedded_fans_async::{self as fan_traits, Error as HardwareError}; +use embassy_time::{Duration, Timer}; +use embedded_fans_async::Error as _; use embedded_sensors_hal_async::temperature::DegreesCelsius; -use embedded_services::GlobalRawMutex; -use embedded_services::ipc::deferred as ipc; -use embedded_services::{error, trace}; - -/// Convenience type for Fan response result -pub type Response = Result; - -/// Allows OEM to implement custom requests -/// -/// The default response is to return an error on unrecognized requests -pub trait CustomRequestHandler { - fn handle_custom_request(&self, _request: Request) -> impl core::future::Future { - async { Err(Error::InvalidRequest) } +use embedded_services::event::Sender; +use embedded_services::{GlobalRawMutex, error, trace}; +use thermal_service_interface::{fan, sensor}; + +/// Fan service configuration parameters. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Rate at which to sample the fan RPM. + pub sample_period: Duration, + /// Rate at which to update the fan state based on temperature readings when auto control is enabled. + pub update_period: Duration, + /// Whether automatic fan control based on temperature is enabled. + pub auto_control: bool, + /// Hysteresis value to prevent rapid toggling between fan states when temperature is around a state transition point. + pub hysteresis: DegreesCelsius, + /// Temperature at which the fan will turn on and begin running at its minimum RPM. + pub min_temp: DegreesCelsius, + /// Temperature at which the fan will follow a speed curve between its minimum and maximum RPM. + pub ramp_temp: DegreesCelsius, + /// Temperature at which the fan will run at its maximum RPM. + pub max_temp: DegreesCelsius, +} + +impl Default for Config { + fn default() -> Self { + Self { + sample_period: Duration::from_secs(1), + update_period: Duration::from_secs(1), + auto_control: true, + hysteresis: 2.0, + min_temp: 25.0, + ramp_temp: 35.0, + max_temp: 45.0, + } } } -/// Allows OEMs to override the default linear response ramp response of fan -pub trait RampResponseHandler: fan_traits::Fan + fan_traits::RpmSense { - fn handle_ramp_response( - &mut self, - profile: &Profile, - temp: DegreesCelsius, - ) -> impl core::future::Future> { - let fan_ramp_temp = profile.ramp_temp; - let fan_max_temp = profile.max_temp; - let min_rpm = self.min_start_rpm(); - let max_rpm = self.max_rpm(); +struct ServiceInner { + driver: Mutex, + state: Mutex, + en_signal: Signal, + config: Mutex, + samples: Mutex>, +} - // Provide a linear fan response between its min and max RPM relative to temperature between ramp start and max temp - let rpm = if temp <= fan_ramp_temp { - min_rpm - } else if temp >= fan_max_temp { - max_rpm - } else { - let ratio = (temp - fan_ramp_temp) / (fan_max_temp - fan_ramp_temp); - let range = (max_rpm - min_rpm) as f32; - min_rpm + (ratio * range) as u16 - }; +impl ServiceInner { + fn new(driver: T, config: Config) -> Self { + Self { + driver: Mutex::new(driver), + state: Mutex::new(fan::State::Off), + en_signal: Signal::new(), + config: Mutex::new(config), + samples: Mutex::new(SampleBuf::create()), + } + } - async move { - self.set_speed_rpm(rpm).await?; - Ok(()) + async fn handle_sampling(&self) { + loop { + match self.driver.lock().await.rpm().await { + Ok(rpm) => self.samples.lock().await.push(rpm), + Err(e) => error!("Fan error sampling fan rpm: {:?}", e.kind()), + } + + let period = self.config.lock().await.sample_period; + Timer::after(period).await; } } -} -/// Ensures all necessary traits are implemented for the controlling driver -pub trait Controller: RampResponseHandler + CustomRequestHandler {} + async fn change_state(&self, to: fan::State) -> Result<(), fan::Error> { + let mut driver = self.driver.lock().await; + match to { + fan::State::Off => { + driver.stop().await.map_err(|_| fan::Error::Hardware)?; + } + fan::State::On(fan::OnState::Min) => { + driver.start().await.map_err(|_| fan::Error::Hardware)?; + } + fan::State::On(fan::OnState::Ramping) => { + // Ramp state will continuously update RPM according to its ramp response function + } + fan::State::On(fan::OnState::Max) => { + let max_rpm = driver.max_rpm(); + let _ = driver.set_speed_rpm(max_rpm).await.map_err(|_| fan::Error::Hardware)?; + } + } + drop(driver); -/// Fan error type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// Invalid request - InvalidRequest, - /// Device encountered a hardware failure - Hardware, -} + let mut state = self.state.lock().await; + trace!("Fan transitioned to {:?} state from {:?} state", to, *state); + *state = to; -/// Fan request -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Request { - /// Most recent RPM measurement - GetRpm, - /// Average RPM measurement - GetAvgRpm, - /// Get Min RPM - GetMinRpm, - /// Get Max RPM - GetMaxRpm, - /// Set RPM manually and disable temperature-based control - SetRpm(u16), - /// Set duty cycle manually (in percent) and disable temperature-based control - SetDuty(u8), - /// Stop the fan and disable temperature-based control - Stop, - /// Enable temperature-based control - EnableAutoControl, - /// Set RPM sampling period (in ms) - SetSamplingPeriod(u64), - /// Set speed update period - SetSpeedUpdatePeriod(u64), - /// Get temperature which fan will turn on to minimum RPM (in degrees Celsius) - GetOnTemp, - /// Get temperature which fan will begin ramping (in degrees Celsius) - GetRampTemp, - /// Get temperature which fan will reach its max RPM (in degrees Celsius) - GetMaxTemp, - /// Set temperature which fan will turn on to minimum RPM (in degrees Celsius) - SetOnTemp(DegreesCelsius), - /// Set temperature which fan will begin ramping (in degrees Celsius) - SetRampTemp(DegreesCelsius), - /// Set temperature which fan will reach its max RPM (in degrees Celsius) - SetMaxTemp(DegreesCelsius), - /// Set hysteresis value between fan on and fan off (in degrees Celsius) - SetHysteresis(DegreesCelsius), - /// Get the profile associated with this fan - GetProfile, - /// Set the profile associated with this fan - SetProfile(Profile), - /// Custom-implemented command - Custom(u8, &'static [u8]), + Ok(()) + } } -/// Fan response -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// Response for any request that is successful but does not require data - Success, - /// RPM - Rpm(u16), - /// Temperature - Temp(DegreesCelsius), - /// Profile - Profile(Profile), - /// Custom-implemented response - Custom(&'static [u8]), +/// Fan service control handle. +pub struct Service<'hw, T: fan::Driver, S: sensor::SensorService, E: Sender, const SAMPLE_BUF_LEN: usize> { + inner: &'hw ServiceInner, + _phantom: PhantomData<(S, E)>, } -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum FanState { - Off, - On, - Ramping, - Max, +// Note: We can't derive these traits because the compiler thinks our generics then need to be Copy + Clone, +// but we only hold a reference and don't actually need to be that strict +impl, const SAMPLE_BUF_LEN: usize> Clone + for Service<'_, T, S, E, SAMPLE_BUF_LEN> +{ + fn clone(&self) -> Self { + *self + } } -/// Fan device ID new type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - -/// Fan device struct -pub struct Device { - // Device ID - id: DeviceId, - // Channel for IPC requests and responses - ipc: ipc::Channel, - // Signal for auto-control enable - auto_control_enable: Signal, +impl, const SAMPLE_BUF_LEN: usize> Copy + for Service<'_, T, S, E, SAMPLE_BUF_LEN> +{ } -impl Device { - /// Create a new fan device - pub fn new(id: DeviceId) -> Self { - Self { - id, - ipc: ipc::Channel::new(), - auto_control_enable: Signal::new(), - } +impl<'hw, T: fan::Driver, S: sensor::SensorService, E: Sender, const SAMPLE_BUF_LEN: usize> fan::FanService + for Service<'hw, T, S, E, SAMPLE_BUF_LEN> +{ + async fn enable_auto_control(&self) -> Result<(), fan::Error> { + self.inner.change_state(fan::State::Off).await?; + self.inner.config.lock().await.auto_control = true; + self.inner.en_signal.signal(()); + Ok(()) } - /// Get the device ID - pub fn id(&self) -> DeviceId { - self.id + async fn rpm(&self) -> u16 { + self.inner.samples.lock().await.recent() } - /// Execute request and wait for response - pub async fn execute_request(&self, request: Request) -> Response { - self.ipc.execute(request).await + async fn min_rpm(&self) -> u16 { + self.inner.driver.lock().await.min_rpm() } -} -/// Fan profile -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Profile { - /// Profile ID - pub id: usize, - /// ID of sensor this fan will query for auto control - pub sensor_id: crate::sensor::DeviceId, - /// Period (in ms) fan will sample its RPM - pub sample_period: u64, - /// Period (in ms) fan will update its state during auto control - pub update_period: u64, - /// Whether fan is under automatic temperature-based control or not - pub auto_control: bool, - /// Hysteresis value (in degrees Celsius) preventing fan from rapidly switching between states - pub hysteresis: DegreesCelsius, - /// Temperature (in degrees Celsius) at which fan will turn on - pub on_temp: DegreesCelsius, - /// Temperature (in degrees Celsius) at which fan will begin its ramp response - pub ramp_temp: DegreesCelsius, - /// Temperature (in degrees Celsius) at which fan will run at its max speed - pub max_temp: DegreesCelsius, -} - -impl Default for Profile { - fn default() -> Self { - Self { - id: 0, - sensor_id: crate::sensor::DeviceId(0), - sample_period: 1000, - update_period: 1000, - auto_control: true, - hysteresis: 2.0, - on_temp: 39.0, - ramp_temp: 40.0, - max_temp: 44.0, - } + async fn max_rpm(&self) -> u16 { + self.inner.driver.lock().await.max_rpm() } -} -/// Fan struct containing device for comms and driver -pub struct Fan { - // Underlying device - device: Device, - // Underlying controller - controller: Mutex, - // Fan profile - profile: Mutex, - // RPM samples - samples: Mutex>, - // State - state: Mutex, -} + async fn rpm_average(&self) -> u16 { + self.inner.samples.lock().await.average() + } -impl Fan { - /// New fan - /// - /// Sample buffer length MUST be a power of two - pub fn new(id: DeviceId, controller: T, profile: Profile) -> Self { - Self { - device: Device::new(id), - controller: Mutex::new(controller), - profile: Mutex::new(profile), - samples: Mutex::new(SampleBuf::create()), - state: Mutex::new(FanState::Off), - } + async fn rpm_immediate(&self) -> Result { + self.inner + .driver + .lock() + .await + .rpm() + .await + .map_err(|_| fan::Error::Hardware) } - /// Retrieve a reference to underlying device for registration with services - pub fn device(&self) -> &Device { - &self.device + async fn set_rpm(&self, rpm: u16) -> Result<(), fan::Error> { + self.inner + .driver + .lock() + .await + .set_speed_rpm(rpm) + .await + .map_err(|_| fan::Error::Hardware)?; + self.inner.config.lock().await.auto_control = false; + Ok(()) } - /// Retrieve a Mutex wrapping the underlying controller - /// - /// Should only be used to update OEM specific state - pub fn controller(&self) -> &Mutex { - &self.controller + async fn set_duty_percent(&self, duty: u8) -> Result<(), fan::Error> { + self.inner + .driver + .lock() + .await + .set_speed_percent(duty) + .await + .map_err(|_| fan::Error::Hardware)?; + self.inner.config.lock().await.auto_control = false; + Ok(()) } - /// Wait for fan to receive a request - pub async fn wait_request(&self) -> ipc::Request<'_, GlobalRawMutex, Request, Response> { - self.device.ipc.receive().await + async fn stop(&self) -> Result<(), fan::Error> { + self.inner + .driver + .lock() + .await + .stop() + .await + .map_err(|_| fan::Error::Hardware)?; + self.inner.config.lock().await.auto_control = false; + Ok(()) } - /// Process fan request - pub async fn process_request(&self, request: Request) -> Response { - match request { - Request::GetRpm => { - let rpm = self.samples.lock().await.recent(); - Ok(ResponseData::Rpm(rpm)) - } - Request::GetAvgRpm => { - let rpm = self.samples.lock().await.average(); - Ok(ResponseData::Rpm(rpm)) - } - Request::SetRpm(rpm) => { - self.controller - .lock() - .await - .set_speed_rpm(rpm) - .await - .map_err(|_| Error::Hardware)?; - self.profile.lock().await.auto_control = false; - Ok(ResponseData::Success) - } - Request::SetDuty(percent) => { - self.controller - .lock() - .await - .set_speed_percent(percent) - .await - .map_err(|_| Error::Hardware)?; - self.profile.lock().await.auto_control = false; - Ok(ResponseData::Success) - } - Request::Stop => { - self.change_state(FanState::Off).await?; - self.profile.lock().await.auto_control = false; - Ok(ResponseData::Success) - } - Request::GetMinRpm => { - let min_rpm = self.controller.lock().await.min_rpm(); - Ok(ResponseData::Rpm(min_rpm)) - } - Request::GetMaxRpm => { - let max_rpm = self.controller.lock().await.max_rpm(); - Ok(ResponseData::Rpm(max_rpm)) - } - Request::SetSamplingPeriod(period) => { - self.profile.lock().await.sample_period = period; - Ok(ResponseData::Success) - } - Request::EnableAutoControl => { - // Make sure we actually transition to a known state - // Next iteration of handle auto control would then put it in actual correct state - self.change_state(FanState::Off).await?; - self.profile.lock().await.auto_control = true; - self.device.auto_control_enable.signal(()); - Ok(ResponseData::Success) - } - Request::SetSpeedUpdatePeriod(period) => { - self.profile.lock().await.update_period = period; - Ok(ResponseData::Success) - } - Request::GetOnTemp => { - let temp = self.profile.lock().await.on_temp; - Ok(ResponseData::Temp(temp)) - } - Request::GetRampTemp => { - let temp = self.profile.lock().await.ramp_temp; - Ok(ResponseData::Temp(temp)) - } - Request::GetMaxTemp => { - let temp = self.profile.lock().await.max_temp; - Ok(ResponseData::Temp(temp)) - } - Request::SetOnTemp(temp) => { - self.profile.lock().await.on_temp = temp; - Ok(ResponseData::Success) - } - Request::SetRampTemp(temp) => { - self.profile.lock().await.ramp_temp = temp; - Ok(ResponseData::Success) - } - Request::SetMaxTemp(temp) => { - self.profile.lock().await.max_temp = temp; - Ok(ResponseData::Success) - } - Request::SetHysteresis(temp) => { - self.profile.lock().await.hysteresis = temp; - Ok(ResponseData::Success) - } - Request::GetProfile => { - let profile = *self.profile.lock().await; - Ok(ResponseData::Profile(profile)) - } - Request::SetProfile(profile) => { - *self.profile.lock().await = profile; - Ok(ResponseData::Success) - } - Request::Custom(_, _) => self.controller.lock().await.handle_custom_request(request).await, - } + async fn set_rpm_sampling_period(&self, period: Duration) { + self.inner.config.lock().await.sample_period = period; } - /// Wait for fan to receive a request, process it, and send a response - pub async fn wait_and_process(&self) { - let request = self.wait_request().await; - let response = self.process_request(request.command).await; - request.respond(response); + async fn set_rpm_update_period(&self, period: Duration) { + self.inner.config.lock().await.update_period = period; } - /// Waits for a IPC request, then processes it - pub async fn handle_rx(&self) { - loop { - self.wait_and_process().await; + async fn state_temp(&self, on_state: fan::OnState) -> DegreesCelsius { + let config = self.inner.config.lock().await; + match on_state { + fan::OnState::Min => config.min_temp, + fan::OnState::Ramping => config.ramp_temp, + fan::OnState::Max => config.max_temp, } } - /// Periodically samples RPM from physical fan and caches it - pub async fn handle_sampling(&self) { - loop { - match self.controller.lock().await.rpm().await { - Ok(rpm) => self.samples.lock().await.push(rpm), - Err(e) => error!("Fan {} error sampling fan rpm: {:?}", self.device.id.0, e.kind()), - } - - let period = self.profile.lock().await.sample_period; - Timer::after_millis(period).await; + async fn set_state_temp(&self, on_state: fan::OnState, temp: DegreesCelsius) { + let mut config = self.inner.config.lock().await; + match on_state { + fan::OnState::Min => config.min_temp = temp, + fan::OnState::Ramping => config.ramp_temp = temp, + fan::OnState::Max => config.max_temp = temp, } } +} - pub async fn handle_auto_control<'hw>(&self, thermal_service: &crate::Service<'hw>) { - loop { - if self.profile.lock().await.auto_control { - let temp = match thermal_service - .execute_sensor_request(self.profile.lock().await.sensor_id, crate::sensor::Request::GetTemp) - .await - { - Ok(crate::sensor::ResponseData::Temp(temp)) => temp, - _ => { - error!( - "Fan {} failed to get temperature, disabling auto control and setting speed to max", - self.device.id.0 - ); - - self.profile.lock().await.auto_control = false; - if self.controller.lock().await.set_speed_max().await.is_err() { - error!("Fan {} failed to set speed to max!", self.device.id.0); - } - - thermal_service - .send_event(Event::FanFailure(self.device.id, Error::Hardware)) - .await; - continue; - } - }; +/// Parameters required to initialize a fan service. +pub struct InitParams<'hw, T: fan::Driver, S: sensor::SensorService, E: Sender> { + /// The underlying fan driver this service will control. + pub driver: T, + /// Initial configuration for the fan service. + pub config: Config, + /// The sensor service this fan will use to get temperature readings. + pub sensor_service: S, + /// Event senders for fan events. + pub event_senders: &'hw mut [E], +} - if let Err(e) = self.handle_fan_state(temp).await { - thermal_service.send_event(Event::FanFailure(self.device.id, e)).await; - error!("Fan {} error handling fan state transition: {:?}", self.device.id.0, e); - } +/// The memory resources required by the fan. +pub struct Resources { + inner: Option>, +} - let sleep_duration = self.profile.lock().await.update_period; - Timer::after_millis(sleep_duration).await; +// Note: We can't derive Default unless we trait bound T by Default, +// but we don't want that restriction since the default is just the None case +impl Default for Resources { + fn default() -> Self { + Self { inner: None } + } +} - // Sleep until auto control is re-enabled - } else { - self.device.auto_control_enable.wait().await; - } +/// A task runner for a fan. Users must run this in an embassy task or similar async execution context. +pub struct Runner<'hw, T: fan::Driver, S: sensor::SensorService, E: Sender, const SAMPLE_BUF_LEN: usize> { + service: &'hw ServiceInner, + sensor: S, + event_senders: &'hw mut [E], +} + +impl<'hw, T: fan::Driver, S: sensor::SensorService, E: Sender, const SAMPLE_BUF_LEN: usize> + Runner<'hw, T, S, E, SAMPLE_BUF_LEN> +{ + async fn broadcast_event(&mut self, event: fan::Event) { + for sender in self.event_senders.iter_mut() { + sender.send(event).await; } } - async fn handle_fan_off_state(&self, temp: DegreesCelsius) -> Result<(), Error> { - let profile = self.profile.lock().await; + async fn ramp_response(&self, temp: DegreesCelsius) -> Result<(), fan::Error> { + let config = *self.service.config.lock().await; - if temp >= profile.on_temp { - self.change_state(FanState::On).await?; - } + let mut driver = self.service.driver.lock().await; + let min_rpm = driver.min_start_rpm(); + let max_rpm = driver.max_rpm(); - Ok(()) + // Provide a linear fan response between its min and max RPM relative to temperature between ramp start and max temp + let rpm = if temp <= config.ramp_temp { + min_rpm + } else if temp >= config.max_temp { + max_rpm + } else { + let ratio = (temp - config.ramp_temp) / (config.max_temp - config.ramp_temp); + let range = (max_rpm - min_rpm) as f32; + min_rpm + (ratio * range) as u16 + }; + + driver + .set_speed_rpm(rpm) + .await + .map(|_| ()) + .map_err(|_| fan::Error::Hardware) } - async fn handle_fan_on_state(&self, temp: DegreesCelsius) -> Result<(), Error> { - let profile = self.profile.lock().await; + async fn handle_fan_off_state(&self, temp: DegreesCelsius) -> Result<(), fan::Error> { + let config = *self.service.config.lock().await; - if temp < (profile.on_temp - profile.hysteresis) { - self.change_state(FanState::Off).await?; - } else if temp >= profile.ramp_temp { - self.change_state(FanState::Ramping).await?; + if temp >= config.min_temp { + self.service.change_state(fan::State::On(fan::OnState::Min)).await?; } Ok(()) } - async fn handle_fan_ramping_state(&self, temp: DegreesCelsius) -> Result<(), Error> { - let profile = self.profile.lock().await; + async fn handle_fan_on_state(&self, temp: DegreesCelsius) -> Result<(), fan::Error> { + let config = *self.service.config.lock().await; - if temp < (profile.ramp_temp - profile.hysteresis) { - self.change_state(FanState::On).await?; - } else if temp >= profile.max_temp { - self.change_state(FanState::Max).await?; - } else { - self.controller - .lock() - .await - .handle_ramp_response(&profile, temp) - .await - .map_err(|_| Error::Hardware)?; + if temp < (config.min_temp - config.hysteresis) { + self.service.change_state(fan::State::Off).await?; + } else if temp >= config.ramp_temp { + self.service.change_state(fan::State::On(fan::OnState::Ramping)).await?; } Ok(()) } - async fn handle_fan_max_state(&self, temp: DegreesCelsius) -> Result<(), Error> { - let profile = self.profile.lock().await; + async fn handle_fan_ramping_state(&self, temp: DegreesCelsius) -> Result<(), fan::Error> { + let config = *self.service.config.lock().await; - if temp < (profile.max_temp - profile.hysteresis) { - self.change_state(FanState::Ramping).await?; + if temp < (config.ramp_temp - config.hysteresis) { + self.service.change_state(fan::State::On(fan::OnState::Min)).await?; + } else if temp >= config.max_temp { + self.service.change_state(fan::State::On(fan::OnState::Max)).await?; + } else { + self.ramp_response(temp).await?; } Ok(()) } - async fn change_state(&self, to: FanState) -> Result<(), Error> { - let mut controller = self.controller.lock().await; - match to { - FanState::Off => { - controller.stop().await.map_err(|_| Error::Hardware)?; - } - FanState::On => { - controller.start().await.map_err(|_| Error::Hardware)?; - } - FanState::Ramping => { - // Ramp state will continuously update RPM according to its ramp response function - } - FanState::Max => { - let max_rpm = controller.max_rpm(); - let _ = controller.set_speed_rpm(max_rpm).await.map_err(|_| Error::Hardware)?; - } - } - drop(controller); + async fn handle_fan_max_state(&self, temp: DegreesCelsius) -> Result<(), fan::Error> { + let config = *self.service.config.lock().await; - let state = *self.state.lock().await; - trace!( - "Fan {} transitioned to {:?} state from {:?} state", - self.device.id.0, to, state - ); - *self.state.lock().await = to; + if temp < (config.max_temp - config.hysteresis) { + self.service.change_state(fan::State::On(fan::OnState::Ramping)).await?; + } Ok(()) } - async fn handle_fan_state(&self, temp: DegreesCelsius) -> Result<(), Error> { - // Must copy state here, if attempt to dereference in match, mutex is still held in match arms - let state = *self.state.lock().await; + async fn handle_fan_state(&self, temp: DegreesCelsius) -> Result<(), fan::Error> { + let state = *self.service.state.lock().await; match state { - FanState::Off => self.handle_fan_off_state(temp).await, - FanState::On => self.handle_fan_on_state(temp).await, - FanState::Ramping => self.handle_fan_ramping_state(temp).await, - FanState::Max => self.handle_fan_max_state(temp).await, + fan::State::Off => self.handle_fan_off_state(temp).await, + fan::State::On(fan::OnState::Min) => self.handle_fan_on_state(temp).await, + fan::State::On(fan::OnState::Ramping) => self.handle_fan_ramping_state(temp).await, + fan::State::On(fan::OnState::Max) => self.handle_fan_max_state(temp).await, } } -} - -/// The memory resources required by the fan. -pub struct Resources<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - inner: Option>, -} -// Note: We can't derive Default unless we trait bound T by Default, -// but we don't want that restriction since the default is just the None case -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Default for Resources<'hw, T, SAMPLE_BUF_LEN> { - fn default() -> Self { - Self { inner: None } - } -} + async fn handle_auto_control(&mut self) { + loop { + if self.service.config.lock().await.auto_control { + let temp = self.sensor.temperature().await; + if let Err(e) = self.handle_fan_state(temp).await { + error!("Error handling fan state transition, disabling auto control: {:?}", e); + self.service.config.lock().await.auto_control = false; + self.broadcast_event(fan::Event::Failure(e)).await; + } -struct ServiceInner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - fan: &'hw Fan, - thermal_service: &'hw crate::Service<'hw>, -} + let sleep_duration = self.service.config.lock().await.update_period; + Timer::after(sleep_duration).await; -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> ServiceInner<'hw, T, SAMPLE_BUF_LEN> { - fn new(init_params: InitParams<'hw, T, SAMPLE_BUF_LEN>) -> Self { - Self { - fan: init_params.fan, - thermal_service: init_params.thermal_service, + // Sleep until auto control is re-enabled + } else { + self.service.en_signal.wait().await; + } } } - - fn fan(&self) -> &Fan { - self.fan - } } -/// A task runner for a fan. Users must run this in an embassy task or similar async execution context. -pub struct Runner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - service: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::ServiceRunner<'hw> - for Runner<'hw, T, SAMPLE_BUF_LEN> +impl<'hw, T: fan::Driver, S: sensor::SensorService + 'hw, E: Sender + 'hw, const SAMPLE_BUF_LEN: usize> + odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw, T, S, E, SAMPLE_BUF_LEN> { - async fn run(self) -> embedded_services::Never { + async fn run(mut self) -> embedded_services::Never { + let service = self.service; loop { - let _ = embassy_futures::join::join3( - self.service.fan.handle_rx(), - self.service.fan.handle_sampling(), - self.service.fan.handle_auto_control(self.service.thermal_service), - ) - .await; + let _ = embassy_futures::join::join(service.handle_sampling(), self.handle_auto_control()).await; } } } -/// Fan service control handle. -pub struct Service<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - inner: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Service<'hw, T, SAMPLE_BUF_LEN> { - /// Get a reference to the inner fan. - pub fn fan(&self) -> &Fan { - self.inner.fan() - } -} - -/// Parameters required to initialize a fan service. -pub struct InitParams<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - /// The underlying `Fan` wrapper this service will control. - pub fan: &'hw Fan, - /// The thermal service handle for this fan to communicate with a sensor. - pub thermal_service: &'hw crate::Service<'hw>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::Service<'hw> - for Service<'hw, T, SAMPLE_BUF_LEN> +impl<'hw, T: fan::Driver, S: sensor::SensorService + 'hw, E: Sender + 'hw, const SAMPLE_BUF_LEN: usize> + odp_service_common::runnable_service::Service<'hw> for Service<'hw, T, S, E, SAMPLE_BUF_LEN> { - type Runner = Runner<'hw, T, SAMPLE_BUF_LEN>; - type Resources = Resources<'hw, T, SAMPLE_BUF_LEN>; - type ErrorType = Error; - type InitParams = InitParams<'hw, T, SAMPLE_BUF_LEN>; + type Runner = Runner<'hw, T, S, E, SAMPLE_BUF_LEN>; + type Resources = Resources; + type ErrorType = fan::Error; + type InitParams = InitParams<'hw, T, S, E>; async fn new( service_storage: &'hw mut Self::Resources, init_params: Self::InitParams, ) -> Result<(Self, Self::Runner), Self::ErrorType> { - let service = service_storage.inner.insert(ServiceInner::new(init_params)); - Ok((Self { inner: service }, Runner { service })) + let service = service_storage + .inner + .insert(ServiceInner::new(init_params.driver, init_params.config)); + Ok(( + Self { + inner: service, + _phantom: PhantomData, + }, + Runner { + service, + sensor: init_params.sensor_service, + event_senders: init_params.event_senders, + }, + )) } } diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index 55d816f99..dbcc178a4 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -1,101 +1,74 @@ //! Thermal service #![no_std] -#![allow(clippy::todo)] -#![allow(clippy::unwrap_used)] -use embedded_sensors_hal_async::temperature::DegreesCelsius; -use thermal_service_messages::{ThermalRequest, ThermalResult}; +use thermal_service_interface::{fan::FanService, sensor::SensorService}; -mod context; pub mod fan; #[cfg(feature = "mock")] pub mod mock; -pub mod mptf; pub mod sensor; -pub mod utils; +mod utils; -/// Thermal error -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Error; - -/// Thermal event -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event { - /// Sensor sampled temperature exceeding a threshold - ThresholdExceeded(sensor::DeviceId, sensor::ThresholdType, DegreesCelsius), - /// Sensor is no longer exceeding a threshold - ThresholdCleared(sensor::DeviceId, sensor::ThresholdType), - /// Sensor encountered hardware failure - SensorFailure(sensor::DeviceId, sensor::Error), - /// Fan encountered hardware failure - FanFailure(fan::DeviceId, fan::Error), +struct ServiceInner<'hw, S: SensorService, F: FanService> { + sensors: &'hw [S], + fans: &'hw [F], } -pub struct Service<'hw> { - context: context::Context<'hw>, +/// Thermal service handle. +/// +/// This maintains a list of registered temperature sensors and fans, which can be accessed by instance ID. +/// +/// To allow for a collection of sensors and fans of different underlying driver types, +/// type erasure will need to be handled by the user, likely via enum dispatch, +/// since async traits are not currently dyn compatible. +#[derive(Clone, Copy)] +pub struct Service<'hw, S: SensorService, F: FanService> { + inner: &'hw ServiceInner<'hw, S, F>, } -impl<'hw> Service<'hw> { - pub async fn init( - service_storage: &'hw embassy_sync::once_lock::OnceLock>, - sensors: &'hw [&'hw sensor::Device], - fans: &'hw [&'hw fan::Device], - ) -> &'hw Self { - service_storage.get_or_init(|| Self { - context: context::Context::new(sensors, fans), - }) - } - - /// Send a thermal event - pub async fn send_event(&self, event: Event) { - self.context.send_event(event).await - } - - /// Wait for a thermal event - pub async fn wait_event(&self) -> Event { - self.context.wait_event().await - } - - /// Provides access to the sensors list - pub fn sensors(&self) -> &[&sensor::Device] { - self.context.sensors() - } +/// Parameters required to initialize the thermal service. +pub struct InitParams<'hw, S: SensorService, F: FanService> { + /// Registered temperature sensors. + pub sensors: &'hw [S], + /// Registered fans. + pub fans: &'hw [F], +} - /// Find a sensor by its ID - pub fn get_sensor(&self, id: sensor::DeviceId) -> Option<&sensor::Device> { - self.context.get_sensor(id) - } +/// The memory resources required by the thermal service. +pub struct Resources<'hw, S: SensorService, F: FanService> { + inner: Option>, +} - /// Send a request to a sensor through the thermal service instead of directly. - pub async fn execute_sensor_request(&self, id: sensor::DeviceId, request: sensor::Request) -> sensor::Response { - self.context.execute_sensor_request(id, request).await +// Note: We can't derive Default because the compiler requires S: Default + F: Default bounds, +// but we don't need that since the default is just the None case +impl Default for Resources<'_, S, F> { + fn default() -> Self { + Self { inner: None } } +} - /// Provides access to the fans list - pub fn fans(&self) -> &[&fan::Device] { - self.context.fans() +impl<'hw, S: SensorService, F: FanService> Service<'hw, S, F> { + /// Initializes the thermal service with the provided sensors and fans. + pub fn init(resources: &'hw mut Resources<'hw, S, F>, init_params: InitParams<'hw, S, F>) -> Self { + let inner = resources.inner.insert(ServiceInner { + sensors: init_params.sensors, + fans: init_params.fans, + }); + Self { inner } } +} - /// Find a fan by its ID - pub fn get_fan(&self, id: fan::DeviceId) -> Option<&fan::Device> { - self.context.get_fan(id) - } +impl<'hw, S: SensorService + Copy, F: FanService + Copy> thermal_service_interface::ThermalService + for Service<'hw, S, F> +{ + type Sensor = S; + type Fan = F; - /// Send a request to a fan through the thermal service instead of directly. - pub async fn execute_fan_request(&self, id: fan::DeviceId, request: fan::Request) -> fan::Response { - self.context.execute_fan_request(id, request).await + fn sensor(&self, id: u8) -> Option { + self.inner.sensors.get(id as usize).copied() } -} - -impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { - type RequestType = ThermalRequest; - type ResultType = ThermalResult; -} -impl<'hw> embedded_services::relay::mctp::RelayServiceHandler for Service<'hw> { - async fn process_request(&self, request: Self::RequestType) -> Self::ResultType { - mptf::process_request(&request, self).await + fn fan(&self, id: u8) -> Option { + self.inner.fans.get(id as usize).copied() } } diff --git a/thermal-service/src/mock/fan.rs b/thermal-service/src/mock/fan.rs index c3fd5401e..c86256dd9 100644 --- a/thermal-service/src/mock/fan.rs +++ b/thermal-service/src/mock/fan.rs @@ -1,5 +1,6 @@ -use crate::fan; +use crate::fan::Config; use embedded_fans_async::{Error, ErrorKind, ErrorType, Fan, RpmSense}; +use thermal_service_interface::fan as fan_interface; /// `MockFan` error. #[derive(Clone, Copy, Debug)] @@ -21,6 +22,16 @@ impl MockFan { pub fn new() -> Self { Self::default() } + + /// Returns a suitable `Config` for a mock fan service. + pub fn config() -> Config { + Config { + min_temp: super::MIN_TEMP + super::TEMP_RANGE / 4.0, + ramp_temp: super::MIN_TEMP + super::TEMP_RANGE / 2.0, + max_temp: super::MAX_TEMP - super::TEMP_RANGE / 4.0, + ..Default::default() + } + } } impl ErrorType for MockFan { @@ -54,6 +65,4 @@ impl RpmSense for MockFan { } } -impl fan::CustomRequestHandler for MockFan {} -impl fan::RampResponseHandler for MockFan {} -impl fan::Controller for MockFan {} +impl fan_interface::Driver for MockFan {} diff --git a/thermal-service/src/mock/mod.rs b/thermal-service/src/mock/mod.rs index df78b3412..f93912d19 100644 --- a/thermal-service/src/mock/mod.rs +++ b/thermal-service/src/mock/mod.rs @@ -1,57 +1,7 @@ pub mod fan; pub mod sensor; -const SAMPLE_BUF_LEN: usize = 16; - // Represents the temperature ranges the mock thermal service will move through pub(crate) const MIN_TEMP: f32 = 20.0; pub(crate) const MAX_TEMP: f32 = 40.0; pub(crate) const TEMP_RANGE: f32 = MAX_TEMP - MIN_TEMP; - -/// Default mock sensor ID. -pub const MOCK_SENSOR_ID: crate::sensor::DeviceId = crate::sensor::DeviceId(0); - -/// Default mock fan ID. -pub const MOCK_FAN_ID: crate::fan::DeviceId = crate::fan::DeviceId(0); - -/// A thermal-service wrapped [`sensor::MockSensor`]. -pub type TsMockSensor = crate::sensor::Sensor; - -/// A thermal-service wrapped [`fan::MockFan`]. -pub type TsMockFan = crate::fan::Fan; - -/// Creates a new mock sensor ready for use with the thermal service. -/// -/// This is a convenience wrapper, but for finer control a [`sensor::MockSensor`] can still be -/// constructed manually. -/// -/// This still needs to be wrapped in a static and registered with the thermal service, -/// and then a respective task spawned. -pub fn new_sensor() -> TsMockSensor { - let sensor = sensor::MockSensor::new(); - crate::sensor::Sensor::new(MOCK_SENSOR_ID, sensor, crate::sensor::Profile::default()) -} - -/// Creates a new mock fan ready for use with the thermal service. -/// -/// This is a convenience wrapper, but for finer control a [`fan::MockFan`] can still be -/// constructed manually. -/// -/// This still needs to be wrapped in a static and registered with the thermal service, -/// and then a respective task spawned. -pub fn new_fan() -> TsMockFan { - let fan = fan::MockFan::new(); - - // Attaches the mock sensor to the mock fan and set the fan state temps - // so that they are in range with the mock sensor - let profile = crate::fan::Profile { - sensor_id: MOCK_SENSOR_ID, - auto_control: true, - on_temp: MIN_TEMP + TEMP_RANGE / 4.0, - ramp_temp: MIN_TEMP + TEMP_RANGE / 2.0, - max_temp: MAX_TEMP - TEMP_RANGE / 4.0, - ..Default::default() - }; - - crate::fan::Fan::new(MOCK_FAN_ID, fan, profile) -} diff --git a/thermal-service/src/mock/sensor.rs b/thermal-service/src/mock/sensor.rs index 1dd2028b3..6c587cfb1 100644 --- a/thermal-service/src/mock/sensor.rs +++ b/thermal-service/src/mock/sensor.rs @@ -1,6 +1,7 @@ -use crate::sensor; +use crate::sensor::Config; use embedded_sensors_hal_async::sensor as sensor_traits; use embedded_sensors_hal_async::temperature::{DegreesCelsius, TemperatureSensor, TemperatureThresholdSet}; +use thermal_service_interface::sensor; /// `MockSensor` error. #[derive(Clone, Copy, Debug)] @@ -30,6 +31,16 @@ impl MockSensor { falling: false, } } + + /// Returns a suitable `Config` for a mock sensor service. + pub fn config() -> Config { + Config { + warn_high_threshold: super::MIN_TEMP + super::TEMP_RANGE / 4.0, + prochot_threshold: super::MIN_TEMP + super::TEMP_RANGE / 2.0, + critical_threshold: super::MAX_TEMP - super::TEMP_RANGE / 4.0, + ..Default::default() + } + } } impl TemperatureSensor for MockSensor { @@ -66,5 +77,4 @@ impl TemperatureThresholdSet for MockSensor { } } -impl sensor::CustomRequestHandler for MockSensor {} -impl sensor::Controller for MockSensor {} +impl sensor::Driver for MockSensor {} diff --git a/thermal-service/src/mptf.rs b/thermal-service/src/mptf.rs deleted file mode 100644 index 25e62c49f..000000000 --- a/thermal-service/src/mptf.rs +++ /dev/null @@ -1,311 +0,0 @@ -//! Definitions for standard MPTF messages the generic Thermal service can expect -//! -//! Transport services such as eSPI and SSH would need to ensure messages are sent to the Thermal service in this format. -//! -//! This interface is subject to change as the eSPI OOB service is developed -use crate::{self as ts, fan, sensor, utils}; -use thermal_service_messages::{DeciKelvin, Milliseconds}; - -use embedded_services::error; - -/// MPTF Standard UUIDs which the thermal service understands -pub mod uuid_standard { - pub const CRT_TEMP: uuid::Bytes = uuid::uuid!("218246e7-baf6-45f1-aa13-07e4845256b8").to_bytes_le(); - pub const PROC_HOT_TEMP: uuid::Bytes = uuid::uuid!("22dc52d2-fd0b-47ab-95b8-26552f9831a5").to_bytes_le(); - pub const PROFILE_TYPE: uuid::Bytes = uuid::uuid!("23b4a025-cdfd-4af9-a411-37a24c574615").to_bytes_le(); - pub const FAN_ON_TEMP: uuid::Bytes = uuid::uuid!("ba17b567-c368-48d5-bc6f-a312a41583c1").to_bytes_le(); - pub const FAN_RAMP_TEMP: uuid::Bytes = uuid::uuid!("3a62688c-d95b-4d2d-bacc-90d7a5816bcd").to_bytes_le(); - pub const FAN_MAX_TEMP: uuid::Bytes = uuid::uuid!("dcb758b1-f0fd-4ec7-b2c0-ef1e2a547b76").to_bytes_le(); - pub const FAN_MIN_RPM: uuid::Bytes = uuid::uuid!("db261c77-934b-45e2-9742-256c62badb7a").to_bytes_le(); - pub const FAN_MAX_RPM: uuid::Bytes = uuid::uuid!("5cf839df-8be7-42b9-9ac5-3403ca2c8a6a").to_bytes_le(); - pub const FAN_CURRENT_RPM: uuid::Bytes = uuid::uuid!("adf95492-0776-4ffc-84f3-b6c8b5269683").to_bytes_le(); -} - -/// Notifications to Host -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Notify { - /// Warn threshold was exceeded - Warn, - /// Prochot threshold was exceeded - ProcHot, - /// Critical threshold was exceeded - Critical, -} - -async fn sensor_get_tmp<'hw>( - instance_id: u8, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - if let Ok(ts::sensor::ResponseData::Temp(temp)) = thermal_service - .execute_sensor_request(sensor::DeviceId(instance_id), sensor::Request::GetTemp) - .await - { - Ok(thermal_service_messages::ThermalResponse::ThermalGetTmpResponse { - temperature: utils::c_to_dk(temp), - }) - } else { - Err(thermal_service_messages::ThermalError::InvalidParameter) - } -} - -async fn get_var_handler<'hw>( - instance_id: u8, - var_uuid: uuid::Bytes, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match var_uuid { - uuid_standard::CRT_TEMP => sensor_get_thrs(instance_id, sensor::ThresholdType::Critical, thermal_service).await, - uuid_standard::PROC_HOT_TEMP => { - sensor_get_thrs(instance_id, sensor::ThresholdType::Prochot, thermal_service).await - } - // TODO: Add a SetProfileId request type? But for sensor or fan? - uuid_standard::PROFILE_TYPE => { - todo!() - } - uuid_standard::FAN_ON_TEMP => fan_get_temp(instance_id, fan::Request::GetOnTemp, thermal_service).await, - - uuid_standard::FAN_RAMP_TEMP => fan_get_temp(instance_id, fan::Request::GetRampTemp, thermal_service).await, - uuid_standard::FAN_MAX_TEMP => fan_get_temp(instance_id, fan::Request::GetMaxTemp, thermal_service).await, - uuid_standard::FAN_MIN_RPM => fan_get_rpm(instance_id, fan::Request::GetMinRpm, thermal_service).await, - uuid_standard::FAN_MAX_RPM => fan_get_rpm(instance_id, fan::Request::GetMaxRpm, thermal_service).await, - uuid_standard::FAN_CURRENT_RPM => fan_get_rpm(instance_id, fan::Request::GetRpm, thermal_service).await, - // TODO: Allow OEM to handle these? - uuid => { - error!("Received GetVar for unrecognized UUID: {:?}", uuid); - Err(thermal_service_messages::ThermalError::InvalidParameter) - } - } -} - -async fn set_var_handler<'hw>( - instance_id: u8, - var_uuid: uuid::Bytes, - set_var: u32, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match var_uuid { - uuid_standard::CRT_TEMP => { - sensor_set_thrs(instance_id, sensor::ThresholdType::Critical, set_var, thermal_service).await - } - uuid_standard::PROC_HOT_TEMP => { - sensor_set_thrs(instance_id, sensor::ThresholdType::Prochot, set_var, thermal_service).await - } - // TODO: Add a SetProfileId request type? But for sensor or fan? - uuid_standard::PROFILE_TYPE => { - todo!() - } - uuid_standard::FAN_ON_TEMP => { - fan_set_var( - instance_id, - fan::Request::SetOnTemp(utils::dk_to_c(set_var)), - thermal_service, - ) - .await - } - uuid_standard::FAN_RAMP_TEMP => { - fan_set_var( - instance_id, - fan::Request::SetRampTemp(utils::dk_to_c(set_var)), - thermal_service, - ) - .await - } - uuid_standard::FAN_MAX_TEMP => { - fan_set_var( - instance_id, - fan::Request::SetMaxTemp(utils::dk_to_c(set_var)), - thermal_service, - ) - .await - } - // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? - uuid_standard::FAN_MIN_RPM => { - todo!() - } - // TODO: What does it mean to set the min/max RPM? Aren't these hardware defined? - uuid_standard::FAN_MAX_RPM => { - todo!() - } - uuid_standard::FAN_CURRENT_RPM => { - fan_set_var(instance_id, fan::Request::SetRpm(set_var as u16), thermal_service).await - } - // TODO: Allow OEM to handle these? - uuid => { - error!("Received SetVar for unrecognized UUID: {:?}", uuid); - Err(thermal_service_messages::ThermalError::InvalidParameter) - } - } -} - -async fn sensor_get_warn_thrs<'hw>( - instance_id: u8, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - let low = thermal_service - .execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnLow), - ) - .await; - let high = thermal_service - .execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::GetThreshold(sensor::ThresholdType::WarnHigh), - ) - .await; - - match (low, high) { - (Ok(sensor::ResponseData::Threshold(low)), Ok(sensor::ResponseData::Threshold(high))) => { - Ok(thermal_service_messages::ThermalResponse::ThermalGetThrsResponse { - timeout: 0, - low: utils::c_to_dk(low), - high: utils::c_to_dk(high), - }) - } - _ => Err(thermal_service_messages::ThermalError::InvalidParameter), - } -} - -async fn sensor_set_warn_thrs<'hw>( - instance_id: u8, - _timeout: Milliseconds, - low: DeciKelvin, - high: DeciKelvin, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - let low_res = thermal_service - .execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnLow, utils::dk_to_c(low)), - ) - .await; - let high_res = thermal_service - .execute_sensor_request( - sensor::DeviceId(instance_id), - sensor::Request::SetThreshold(sensor::ThresholdType::WarnHigh, utils::dk_to_c(high)), - ) - .await; - - if low_res.is_ok() && high_res.is_ok() { - Ok(thermal_service_messages::ThermalResponse::ThermalSetThrsResponse) - } else { - Err(thermal_service_messages::ThermalError::InvalidParameter) - } -} - -async fn sensor_get_thrs<'hw>( - instance: u8, - threshold_type: sensor::ThresholdType, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match thermal_service - .execute_sensor_request( - sensor::DeviceId(instance), - sensor::Request::GetThreshold(threshold_type), - ) - .await - { - Ok(sensor::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { - val: utils::c_to_dk(temp), - }), - _ => Err(thermal_service_messages::ThermalError::HardwareError), - } -} - -async fn fan_get_temp<'hw>( - instance: u8, - fan_request: fan::Request, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match thermal_service - .execute_fan_request(fan::DeviceId(instance), fan_request) - .await - { - Ok(fan::ResponseData::Temp(temp)) => Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { - val: utils::c_to_dk(temp), - }), - _ => Err(thermal_service_messages::ThermalError::HardwareError), - } -} - -async fn fan_get_rpm<'hw>( - instance: u8, - fan_request: fan::Request, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match thermal_service - .execute_fan_request(fan::DeviceId(instance), fan_request) - .await - { - Ok(fan::ResponseData::Rpm(rpm)) => { - Ok(thermal_service_messages::ThermalResponse::ThermalGetVarResponse { val: rpm.into() }) - } - _ => Err(thermal_service_messages::ThermalError::HardwareError), - } -} - -async fn sensor_set_thrs<'hw>( - instance: u8, - threshold_type: sensor::ThresholdType, - threshold_dk: u32, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match thermal_service - .execute_sensor_request( - sensor::DeviceId(instance), - sensor::Request::SetThreshold(threshold_type, utils::dk_to_c(threshold_dk)), - ) - .await - { - Ok(sensor::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), - _ => Err(thermal_service_messages::ThermalError::HardwareError), - } -} - -async fn fan_set_var<'hw>( - instance: u8, - fan_request: fan::Request, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match thermal_service - .execute_fan_request(fan::DeviceId(instance), fan_request) - .await - { - Ok(fan::ResponseData::Success) => Ok(thermal_service_messages::ThermalResponse::ThermalSetVarResponse), - _ => Err(thermal_service_messages::ThermalError::HardwareError), - } -} - -pub(crate) async fn process_request<'hw>( - request: &thermal_service_messages::ThermalRequest, - thermal_service: &crate::Service<'hw>, -) -> thermal_service_messages::ThermalResult { - match request { - thermal_service_messages::ThermalRequest::ThermalGetTmpRequest { instance_id } => { - sensor_get_tmp(*instance_id, thermal_service).await - } - thermal_service_messages::ThermalRequest::ThermalSetThrsRequest { - instance_id, - timeout, - low, - high, - } => sensor_set_warn_thrs(*instance_id, *timeout, *low, *high, thermal_service).await, - thermal_service_messages::ThermalRequest::ThermalGetThrsRequest { instance_id } => { - sensor_get_warn_thrs(*instance_id, thermal_service).await - } - // TODO: How do we handle this generically? - thermal_service_messages::ThermalRequest::ThermalSetScpRequest { .. } => todo!(), - thermal_service_messages::ThermalRequest::ThermalGetVarRequest { - instance_id, - len: _len, - var_uuid, - } => get_var_handler(*instance_id, *var_uuid, thermal_service).await, - thermal_service_messages::ThermalRequest::ThermalSetVarRequest { - instance_id, - len: _len, - var_uuid, - set_var, - } => set_var_handler(*instance_id, *var_uuid, *set_var, thermal_service).await, - } -} diff --git a/thermal-service/src/sensor.rs b/thermal-service/src/sensor.rs index 1771d14de..9c50e0717 100644 --- a/thermal-service/src/sensor.rs +++ b/thermal-service/src/sensor.rs @@ -1,31 +1,14 @@ -//! Sensor Device -use crate::Event; use crate::utils::SampleBuf; -use embassy_sync::mutex::Mutex; -use embassy_sync::signal::Signal; -use embassy_time::Timer; -use embedded_sensors_hal_async::temperature::{DegreesCelsius, TemperatureSensor, TemperatureThresholdSet}; -use embedded_services::GlobalRawMutex; -use embedded_services::error; -use embedded_services::ipc::deferred as ipc; - -// Timeout period (in ms) for physical bus access -const BUS_TIMEOUT: u64 = 200; - -/// Convenience type for Sensor response result -pub type Response = Result; - -/// Allows OEM to implement custom requests -/// -/// The default response is to return an error on unrecognized requests -pub trait CustomRequestHandler { - fn handle_custom_request(&self, _request: Request) -> impl core::future::Future { - async { Err(Error::InvalidRequest) } - } -} +use core::marker::PhantomData; +use embassy_sync::{mutex::Mutex, signal::Signal}; +use embassy_time::{Duration, Timer, with_timeout}; +use embedded_sensors_hal_async::temperature::DegreesCelsius; +use embedded_services::event::Sender; +use embedded_services::{GlobalRawMutex, error}; +use thermal_service_interface::sensor; -/// Ensures all necessary traits are implemented for the controlling driver -pub trait Controller: TemperatureSensor + TemperatureThresholdSet + CustomRequestHandler {} +// Timeout period for physical bus access +const BUS_TIMEOUT: Duration = Duration::from_millis(200); /* Helper macro for calling a bus function with automatic retry after timeout or failure. * @@ -37,14 +20,14 @@ macro_rules! with_retry { $self:expr, $bus_method:expr ) => {{ - let mut retry_attempts = $self.profile.lock().await.retry_attempts; + let mut retry_attempts = $self.config.lock().await.retry_attempts; loop { if retry_attempts == 0 { - break Err(Error::Hardware); + break Err(sensor::Error::RetryExhausted); } - match embassy_time::with_timeout(embassy_time::Duration::from_millis(BUS_TIMEOUT), $bus_method).await { + match with_timeout(BUS_TIMEOUT, $bus_method).await { Ok(Ok(val)) => break Ok(val), _ => { retry_attempts -= 1; @@ -54,520 +37,303 @@ macro_rules! with_retry { }}; } -/// Sensor threshold type -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ThresholdType { - /// Threshold below which host is notified - WarnLow, - /// Threshold above which host is notified - WarnHigh, - /// Threshold above which PROCHOT is asserted - Prochot, - /// Threshold above which critical temperature is reached and system should be shutdown - /// Some systems may tie sensor alert pin directly to reset controller, in which case - /// SetHardAlert should be used. - Critical, -} - -/// Sensor error type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Sensor service configuration parameters. +#[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// Invalid request - InvalidRequest, - /// Device encountered a hardware failure - Hardware, -} - -/// Sensor request -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Request { - /// Most recent cached temperature measurement - GetTemp, - /// Average temperature measurement (over BUFFER_SIZE * SAMPLING_PERIOD) - GetAvgTemp, - /// Instructs sensor to immediately sample temperature (not cached) - GetTmpNow, - /// Low threshold below which sensor will set the alert pin active (in degrees Celsius) - SetHardAlertLow(DegreesCelsius), - /// High threshold above which sensor will set the alert pin active (in degrees Celsius) - SetHardAlertHigh(DegreesCelsius), - /// Get a threshold - GetThreshold(ThresholdType), - /// Set a threshold - SetThreshold(ThresholdType, DegreesCelsius), - /// Threshold in which sensor begins fast sampling - SetFastSamplingThreshold(DegreesCelsius), - /// Set temperature sampling period (in ms) - SetSamplingPeriod(u64), - /// Set fast temperature sampling period (in ms) - SetFastSamplingPeriod(u64), - /// An offset that is applied to all physical temperature samples (in degrees Celsius) - SetOffset(DegreesCelsius), - /// Enable sensor sampling - EnableSampling, - /// Disable sensor sampling - DisableSampling, - /// Set the max number of times communication with physical sensor will be attempted until error is reported - SetRetryAttempts(u8), - /// Get the thermal profile associated with this sensor - GetProfile, - /// Set the thermal profile associated with this sensor - SetProfile(Profile), - /// Custom-implemented command - Custom(u8, &'static [u8]), -} - -/// Sensor response -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// Response for any request that is successful but does not require data - Success, - /// Temperature (in degrees Celsius) - Temp(DegreesCelsius), - /// Threshold (in degrees Celsius) - Threshold(DegreesCelsius), - /// Profile - Profile(Profile), - /// Custom-implemented response - Custom(&'static [u8]), -} - -/// Sensor device ID new type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DeviceId(pub u8); - -/// Sensor device struct -pub struct Device { - /// Device ID - id: DeviceId, - /// Channel for IPC requests and responses - ipc: ipc::Channel, - /// Signal for enable - enable: Signal, -} - -impl Device { - /// Create a new sensor device - pub fn new(id: DeviceId) -> Self { - Self { - id, - ipc: ipc::Channel::new(), - enable: Signal::new(), - } - } - - /// Get the device ID - pub fn id(&self) -> DeviceId { - self.id - } - - /// Execute request and wait for response - pub async fn execute_request(&self, request: Request) -> Response { - self.ipc.execute(request).await - } -} - -/// Sensor profile -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Profile { - /// Profile ID - pub id: usize, - /// Period (in ms) sensor will sample its temperature - pub sample_period: u64, - /// Period (in ms) sensor will sample its temperature when in fast sampling state - pub fast_sample_period: u64, - /// Whether or not automatic background sampling is enabled or not +pub struct Config { + /// Rate at which to sample the sensor when operating in normal conditions. + pub sample_period: Duration, + /// Rate at which to sample the sensor when operating in fast conditions. + pub fast_sample_period: Duration, + /// Whether periodic sampling is enabled. pub sampling_enabled: bool, - /// Hysteresis value (in degrees Celsius) preventing sensor from rapidly reporting threshold events + /// Hysteresis value to prevent rapid generation of threshold events when temperature is near a threshold. pub hysteresis: DegreesCelsius, - /// Threshold (in degrees Celsius) at which sensor will trigger a WARN LOW event + /// Temperature threshold below which a warning event will be generated. pub warn_low_threshold: DegreesCelsius, - /// Threshold (in degrees Celsius) at which sensor will trigger a WARN HIGH event + /// Temperature threshold above which a warning event will be generated. pub warn_high_threshold: DegreesCelsius, - /// Threshold (in degrees Celsius) at which sensor will trigger a PROCHOT event + /// Temperature threshold above which a prochot event will be generated. pub prochot_threshold: DegreesCelsius, - /// Threshold (in degrees Celsius) at which sensor will trigger a CRITICAL event - pub crt_threshold: DegreesCelsius, - /// Threshold (in degrees Celsius) at which sensor will enter the fast sampling state + /// Temperature threshold above which a critical event will be generated. + pub critical_threshold: DegreesCelsius, + /// Temperature threshold above which fast sampling is enabled. pub fast_sampling_threshold: DegreesCelsius, - /// Offset (in degrees Celsius) to be added to sampled temperature + /// Offset to be applied to the temperature readings. pub offset: DegreesCelsius, - /// Number of attempts sensor will make to communicate with the physical device over the bus + /// Number of retry attempts for bus operations. pub retry_attempts: u8, } -impl Default for Profile { +impl Default for Config { fn default() -> Self { Self { - id: 0, - sample_period: 1000, - fast_sample_period: 200, + sample_period: Duration::from_secs(1), + fast_sample_period: Duration::from_millis(200), sampling_enabled: true, + hysteresis: 2.0, warn_low_threshold: DegreesCelsius::MIN, warn_high_threshold: DegreesCelsius::MAX, prochot_threshold: DegreesCelsius::MAX, - crt_threshold: DegreesCelsius::MAX, + critical_threshold: DegreesCelsius::MAX, fast_sampling_threshold: DegreesCelsius::MAX, offset: 0.0, retry_attempts: 5, - hysteresis: 2.0, } } } -// Additional Sensor state -#[derive(Debug, Clone, Copy, PartialEq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -struct State { - is_warn_low: bool, - is_warn_high: bool, - is_prochot: bool, - is_critical: bool, -} - -/// Wrapper binding a communication device, hardware driver, and additional state. -pub struct Sensor { - /// Sensor communication device - device: Device, - /// Sensor controller - controller: Mutex, - /// Sensor profile - profile: Mutex, - /// Sensor state - state: Mutex, - /// Cached temperature samples +struct ServiceInner { + driver: Mutex, + en_signal: Signal, + config: Mutex, samples: Mutex>, } -impl Sensor { - /// New sensor - /// - /// Sample buffer length MUST be a power of two - pub fn new(id: DeviceId, controller: T, profile: Profile) -> Self { +impl ServiceInner { + fn new(driver: T, config: Config) -> Self { Self { - device: Device::new(id), - controller: Mutex::new(controller), - profile: Mutex::new(profile), - state: Mutex::new(State::default()), + driver: Mutex::new(driver), + en_signal: Signal::new(), + config: Mutex::new(config), samples: Mutex::new(SampleBuf::create()), } } +} - /// Retrieve a reference to underlying device for registration with services - pub fn device(&self) -> &Device { - &self.device +/// Sensor service control handle. +pub struct Service<'hw, T: sensor::Driver, E: Sender, const SAMPLE_BUF_LEN: usize> { + inner: &'hw ServiceInner, + _phantom: PhantomData, +} + +// Note: We can't derive these traits because the compiler thinks our generics then need to be Copy + Clone, +// but we only hold a reference and don't actually need to be that strict +impl, const SAMPLE_BUF_LEN: usize> Clone + for Service<'_, T, E, SAMPLE_BUF_LEN> +{ + fn clone(&self) -> Self { + *self } +} + +impl, const SAMPLE_BUF_LEN: usize> Copy + for Service<'_, T, E, SAMPLE_BUF_LEN> +{ +} - /// Retrieve a Mutex wrapping the underlying controller - /// - /// Should only be used to update OEM specific state - pub fn controller(&self) -> &Mutex { - &self.controller +impl<'hw, T: sensor::Driver, E: Sender, const SAMPLE_BUF_LEN: usize> sensor::SensorService + for Service<'hw, T, E, SAMPLE_BUF_LEN> +{ + async fn temperature(&self) -> DegreesCelsius { + self.inner.samples.lock().await.recent() } - /// Wait for sensor to receive a request - pub async fn wait_request(&self) -> ipc::Request<'_, GlobalRawMutex, Request, Response> { - self.device.ipc.receive().await + async fn temperature_average(&self) -> DegreesCelsius { + self.inner.samples.lock().await.average() } - /// Process sensor request - pub async fn process_request(&self, request: Request) -> Response { - match request { - Request::GetTemp => { - let temp = self.samples.lock().await.recent(); - Ok(ResponseData::Temp(temp)) - } - Request::GetAvgTemp => { - let temp = self.samples.lock().await.average(); - Ok(ResponseData::Temp(temp)) - } - Request::GetTmpNow => { - let temp = with_retry!(self, self.controller.lock().await.temperature())?; - Ok(ResponseData::Temp(temp)) - } - Request::SetHardAlertLow(low) => { - with_retry!(self, self.controller.lock().await.set_temperature_threshold_low(low))?; - Ok(ResponseData::Success) - } - Request::SetHardAlertHigh(high) => { - with_retry!(self, self.controller.lock().await.set_temperature_threshold_high(high))?; - Ok(ResponseData::Success) - } - Request::GetThreshold(ThresholdType::WarnLow) => { - let threshold = self.profile.lock().await.warn_low_threshold; - Ok(ResponseData::Threshold(threshold)) - } - Request::GetThreshold(ThresholdType::WarnHigh) => { - let threshold = self.profile.lock().await.warn_high_threshold; - Ok(ResponseData::Threshold(threshold)) - } - Request::GetThreshold(ThresholdType::Prochot) => { - let threshold = self.profile.lock().await.prochot_threshold; - Ok(ResponseData::Threshold(threshold)) - } - Request::GetThreshold(ThresholdType::Critical) => { - let threshold = self.profile.lock().await.crt_threshold; - Ok(ResponseData::Threshold(threshold)) - } - Request::SetThreshold(ThresholdType::WarnLow, threshold) => { - self.profile.lock().await.warn_low_threshold = threshold; - Ok(ResponseData::Success) - } - Request::SetThreshold(ThresholdType::WarnHigh, threshold) => { - self.profile.lock().await.warn_high_threshold = threshold; - Ok(ResponseData::Success) - } - Request::SetThreshold(ThresholdType::Prochot, threshold) => { - self.profile.lock().await.prochot_threshold = threshold; - Ok(ResponseData::Success) - } - Request::SetThreshold(ThresholdType::Critical, threshold) => { - self.profile.lock().await.crt_threshold = threshold; - Ok(ResponseData::Success) - } - Request::SetFastSamplingThreshold(threshold) => { - self.profile.lock().await.fast_sampling_threshold = threshold; - Ok(ResponseData::Success) - } - Request::SetSamplingPeriod(period) => { - self.profile.lock().await.sample_period = period; - Ok(ResponseData::Success) - } - Request::SetFastSamplingPeriod(period) => { - self.profile.lock().await.fast_sample_period = period; - Ok(ResponseData::Success) - } - Request::SetOffset(offset) => { - self.profile.lock().await.offset = offset; - Ok(ResponseData::Success) - } - Request::EnableSampling => { - self.profile.lock().await.sampling_enabled = true; - self.device.enable.signal(()); - Ok(ResponseData::Success) - } - Request::DisableSampling => { - self.profile.lock().await.sampling_enabled = false; - Ok(ResponseData::Success) - } - Request::SetRetryAttempts(limit) => { - self.profile.lock().await.retry_attempts = limit; - Ok(ResponseData::Success) - } - Request::GetProfile => { - let profile = *self.profile.lock().await; - Ok(ResponseData::Profile(profile)) - } - Request::SetProfile(profile) => { - *self.profile.lock().await = profile; - Ok(ResponseData::Success) - } - Request::Custom(_, _) => self.controller.lock().await.handle_custom_request(request).await, + async fn temperature_immediate(&self) -> Result { + with_retry!(self.inner, self.inner.driver.lock().await.temperature()) + } + + async fn set_threshold(&self, threshold: sensor::Threshold, value: DegreesCelsius) { + let mut config = self.inner.config.lock().await; + match threshold { + sensor::Threshold::WarnLow => config.warn_low_threshold = value, + sensor::Threshold::WarnHigh => config.warn_high_threshold = value, + sensor::Threshold::Prochot => config.prochot_threshold = value, + sensor::Threshold::Critical => config.critical_threshold = value, } } - // Wait for sensor to receive a request, process it, and send a response - pub async fn wait_and_process(&self) { - let request = self.wait_request().await; - let response = self.process_request(request.command).await; - request.respond(response); + async fn threshold(&self, threshold: sensor::Threshold) -> DegreesCelsius { + let config = self.inner.config.lock().await; + match threshold { + sensor::Threshold::WarnLow => config.warn_low_threshold, + sensor::Threshold::WarnHigh => config.warn_high_threshold, + sensor::Threshold::Prochot => config.prochot_threshold, + sensor::Threshold::Critical => config.critical_threshold, + } } - /// Waits for a request then processes it and sends a response - pub async fn handle_rx(&self) { - loop { - self.wait_and_process().await; + async fn set_sample_period(&self, period: Duration) { + self.inner.config.lock().await.sample_period = period; + } + + async fn enable_sampling(&self) { + self.inner.config.lock().await.sampling_enabled = true; + self.inner.en_signal.signal(()); + } + + async fn disable_sampling(&self) { + self.inner.config.lock().await.sampling_enabled = false; + } +} + +/// Parameters required to initialize a sensor service. +pub struct InitParams<'hw, T: sensor::Driver, E: Sender> { + /// The underlying sensor driver this service will control. + pub driver: T, + /// Initial configuration for the sensor service. + pub config: Config, + /// Event senders for sensor events. + pub event_senders: &'hw mut [E], +} + +/// The memory resources required by the sensor. +pub struct Resources { + inner: Option>, +} + +// Note: We can't derive Default unless we trait bound T by Default, +// but we don't want that restriction since the default is just the None case +impl Default for Resources { + fn default() -> Self { + Self { inner: None } + } +} + +// Additional sensor runner state +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct State { + is_warn_low: bool, + is_warn_high: bool, + is_prochot: bool, + is_critical: bool, +} + +/// A task runner for a sensor. Users must run this in an embassy task or similar async execution context. +pub struct Runner<'hw, T: sensor::Driver, E: Sender, const SAMPLE_BUF_LEN: usize> { + service: &'hw ServiceInner, + event_senders: &'hw mut [E], + state: State, +} + +impl<'hw, T: sensor::Driver, E: Sender, const SAMPLE_BUF_LEN: usize> Runner<'hw, T, E, SAMPLE_BUF_LEN> { + async fn broadcast_event(&mut self, event: sensor::Event) { + for sender in self.event_senders.iter_mut() { + sender.send(event).await; } } - async fn check_thresholds<'hw>(&self, temp: DegreesCelsius, thermal_service: &crate::Service<'hw>) { - let profile = self.profile.lock().await; - let mut state = self.state.lock().await; + async fn check_thresholds(&mut self, temp: DegreesCelsius) { + let config = *self.service.config.lock().await; - if temp >= profile.warn_high_threshold && !state.is_warn_high { - thermal_service - .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::WarnHigh, temp)) + if temp >= config.warn_high_threshold && !self.state.is_warn_high { + self.state.is_warn_high = true; + self.broadcast_event(sensor::Event::ThresholdExceeded(sensor::Threshold::WarnHigh)) .await; - state.is_warn_high = true; - } else if temp < (profile.warn_high_threshold - profile.hysteresis) && state.is_warn_high { - thermal_service - .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::WarnHigh)) + } else if temp < (config.warn_high_threshold - config.hysteresis) && self.state.is_warn_high { + self.state.is_warn_high = false; + self.broadcast_event(sensor::Event::ThresholdCleared(sensor::Threshold::WarnHigh)) .await; - state.is_warn_high = false; } - if temp <= profile.warn_low_threshold && !state.is_warn_low { - thermal_service - .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::WarnLow, temp)) + if temp <= config.warn_low_threshold && !self.state.is_warn_low { + self.state.is_warn_low = true; + self.broadcast_event(sensor::Event::ThresholdExceeded(sensor::Threshold::WarnLow)) .await; - state.is_warn_low = true; - } else if temp > (profile.warn_low_threshold + profile.hysteresis) && state.is_warn_low { - thermal_service - .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::WarnLow)) + } else if temp > (config.warn_low_threshold + config.hysteresis) && self.state.is_warn_low { + self.state.is_warn_low = false; + self.broadcast_event(sensor::Event::ThresholdCleared(sensor::Threshold::WarnLow)) .await; - state.is_warn_low = false; } - if temp >= profile.prochot_threshold && !state.is_prochot { - thermal_service - .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::Prochot, temp)) + if temp >= config.prochot_threshold && !self.state.is_prochot { + self.state.is_prochot = true; + self.broadcast_event(sensor::Event::ThresholdExceeded(sensor::Threshold::Prochot)) .await; - state.is_prochot = true; - } else if temp < (profile.prochot_threshold - profile.hysteresis) && state.is_prochot { - thermal_service - .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::Prochot)) + } else if temp < (config.prochot_threshold - config.hysteresis) && self.state.is_prochot { + self.state.is_prochot = false; + self.broadcast_event(sensor::Event::ThresholdCleared(sensor::Threshold::Prochot)) .await; - state.is_prochot = false; } - if temp >= profile.crt_threshold && !state.is_critical { - thermal_service - .send_event(Event::ThresholdExceeded(self.device.id, ThresholdType::Critical, temp)) + if temp >= config.critical_threshold && !self.state.is_critical { + self.state.is_critical = true; + self.broadcast_event(sensor::Event::ThresholdExceeded(sensor::Threshold::Critical)) .await; - state.is_critical = true; - } else if temp < (profile.crt_threshold - profile.hysteresis) && state.is_critical { - thermal_service - .send_event(Event::ThresholdCleared(self.device.id, ThresholdType::Critical)) + } else if temp < (config.critical_threshold - config.hysteresis) && self.state.is_critical { + self.state.is_critical = false; + self.broadcast_event(sensor::Event::ThresholdCleared(sensor::Threshold::Critical)) .await; - state.is_critical = false; } } +} - /// Periodically samples temperature from physical sensor and caches it - pub async fn handle_sampling<'hw>(&self, thermal_service: &crate::Service<'hw>) { +impl<'hw, T: sensor::Driver, E: Sender, const SAMPLE_BUF_LEN: usize> + odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw, T, E, SAMPLE_BUF_LEN> +{ + async fn run(mut self) -> embedded_services::Never { loop { + let config = *self.service.config.lock().await; + // Only sample temperature if enabled - if self.profile.lock().await.sampling_enabled { - let temp = match with_retry!(self, self.controller.lock().await.temperature()) { + if config.sampling_enabled { + let temp = match with_retry!(self.service, self.service.driver.lock().await.temperature()) { Ok(temp) => temp, - _ => { - self.profile.lock().await.sampling_enabled = false; - thermal_service - .send_event(Event::SensorFailure(self.device.id, Error::Hardware)) - .await; - error!("Error sampling sensor {}, disabling sampling", self.device.id.0); + Err(e) => { + self.service.config.lock().await.sampling_enabled = false; + self.broadcast_event(sensor::Event::Failure(e)).await; + error!("Error sampling sensor, disabling sampling"); continue; } }; // Add offset to measured temperature - let temp = temp + self.profile.lock().await.offset; + let temp = temp + config.offset; // Cache in buffer for quick retrieval from other services - self.samples.lock().await.push(temp); + self.service.samples.lock().await.push(temp); // Check thresholds - self.check_thresholds(temp, thermal_service).await; + self.check_thresholds(temp).await; // Adjust sampling rate based on how hot we are getting - let profile = self.profile.lock().await; - let sleep_duration = if temp >= profile.fast_sampling_threshold { - profile.fast_sample_period + let sleep_duration = if temp >= config.fast_sampling_threshold { + config.fast_sample_period } else { - profile.sample_period + config.sample_period }; - drop(profile); // Sleep in-between sampling periods - Timer::after_millis(sleep_duration).await; + Timer::after(sleep_duration).await; // Otherwise sleep and wait to be re-enabled } else { - self.device.enable.wait().await; + self.service.en_signal.wait().await; } } } } -/// The memory resources required by the sensor. -pub struct Resources<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - inner: Option>, -} - -// Note: We can't derive Default unless we trait bound T by Default, -// but we don't want that restriction since the default is just the None case -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Default for Resources<'hw, T, SAMPLE_BUF_LEN> { - fn default() -> Self { - Self { inner: None } - } -} - -struct ServiceInner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - sensor: &'hw Sensor, - thermal_service: &'hw crate::Service<'hw>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> ServiceInner<'hw, T, SAMPLE_BUF_LEN> { - fn new(init_params: InitParams<'hw, T, SAMPLE_BUF_LEN>) -> Self { - Self { - sensor: init_params.sensor, - thermal_service: init_params.thermal_service, - } - } - - fn sensor(&self) -> &Sensor { - self.sensor - } -} - -/// A task runner for a sensor. Users must run this in an embassy task or similar async execution context. -pub struct Runner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - service: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::ServiceRunner<'hw> - for Runner<'hw, T, SAMPLE_BUF_LEN> -{ - async fn run(self) -> embedded_services::Never { - loop { - let _ = embassy_futures::join::join( - self.service.sensor.handle_rx(), - self.service.sensor.handle_sampling(self.service.thermal_service), - ) - .await; - } - } -} - -/// Sensor service control handle. -pub struct Service<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - inner: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Service<'hw, T, SAMPLE_BUF_LEN> { - /// Get a reference to the inner sensor. - pub fn sensor(&self) -> &Sensor { - self.inner.sensor() - } -} - -/// Parameters required to initialize a sensor service. -pub struct InitParams<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { - /// The underlying `Sensor` wrapper this service will control. - pub sensor: &'hw Sensor, - /// The thermal service handle for this sensor to communicate events to. - pub thermal_service: &'hw crate::Service<'hw>, -} - -impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::Service<'hw> - for Service<'hw, T, SAMPLE_BUF_LEN> +impl<'hw, T: sensor::Driver, E: Sender + 'hw, const SAMPLE_BUF_LEN: usize> + odp_service_common::runnable_service::Service<'hw> for Service<'hw, T, E, SAMPLE_BUF_LEN> { - type Runner = Runner<'hw, T, SAMPLE_BUF_LEN>; - type Resources = Resources<'hw, T, SAMPLE_BUF_LEN>; - type ErrorType = Error; - type InitParams = InitParams<'hw, T, SAMPLE_BUF_LEN>; + type Runner = Runner<'hw, T, E, SAMPLE_BUF_LEN>; + type Resources = Resources; + type ErrorType = sensor::Error; + type InitParams = InitParams<'hw, T, E>; async fn new( service_storage: &'hw mut Self::Resources, init_params: Self::InitParams, ) -> Result<(Self, Self::Runner), Self::ErrorType> { - let service = service_storage.inner.insert(ServiceInner::new(init_params)); - Ok((Self { inner: service }, Runner { service })) + let service = service_storage + .inner + .insert(ServiceInner::new(init_params.driver, init_params.config)); + Ok(( + Self { + inner: service, + _phantom: PhantomData, + }, + Runner { + service, + event_senders: init_params.event_senders, + state: State::default(), + }, + )) } } diff --git a/thermal-service/src/utils.rs b/thermal-service/src/utils.rs index 58c70c127..f8598c130 100644 --- a/thermal-service/src/utils.rs +++ b/thermal-service/src/utils.rs @@ -1,4 +1,4 @@ -//! Helpful utilities for the thermal service +//! Helpful utilities for the thermal service. use heapless::Deque; /// Buffer for storing samples @@ -29,23 +29,20 @@ impl SampleBuf { } impl SampleBuf { + /// Returns the average of the samples in the buffer, or 0.0 if the buffer is empty. pub fn average(&self) -> f32 { - self.deque.iter().copied().sum::() / (self.deque.len() as f32) + let len = self.deque.len(); + if len == 0 { + return 0.0; + } + self.deque.iter().copied().sum::() / len as f32 } } impl SampleBuf { + /// Returns the average of the samples in the buffer, or 0 if the buffer is empty. pub fn average(&self) -> u16 { - self.deque.iter().copied().sum::() / (self.deque.len() as u16) + let sum: u32 = self.deque.iter().copied().map(u32::from).sum(); + sum.checked_div(self.deque.len() as u32).unwrap_or(0) as u16 } } - -/// Convert deciKelvin to degrees Celsius -pub const fn dk_to_c(dk: thermal_service_messages::DeciKelvin) -> f32 { - (dk as f32 / 10.0) - 273.15 -} - -/// Convert degrees Celsius to deciKelvin -pub const fn c_to_dk(c: f32) -> thermal_service_messages::DeciKelvin { - ((c + 273.15) * 10.0) as thermal_service_messages::DeciKelvin -} From 29433d35c723a3d2981860aa9a60d921430a946f Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:08:54 -0700 Subject: [PATCH 60/79] power-policy-service/tests: Introduce dedicated Test trait (#772) The existing traits used to resolve lifetime issues with test closures are messy and difficult to understand. Introduce a dedicated `Test` trait to make things cleaner. This PR is smaller than it seems, but a lot of lines were touched due to the formatting changes required with moving the previously free functions into traits. --- power-policy-service/tests/common/mod.rs | 61 +- power-policy-service/tests/consumer.rs | 779 ++++++++++---------- power-policy-service/tests/provider.rs | 472 ++++++------ power-policy-service/tests/unconstrained.rs | 263 +++---- 4 files changed, 797 insertions(+), 778 deletions(-) diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 0351d8c2a..1c6b6e516 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -73,47 +73,23 @@ async fn power_policy_task<'device, 'sender, const N: usize>( } } -/// This trait is a workaround for Rust's current limitations on closures returning a generic future. +/// Trait for runnable tests. /// -/// The trait we want to express for `run_test` is something like: -/// ``` -/// for<'a> F: FnOnce( -/// &'a ServiceMutex<'a, 'a>, -/// &'a Mutex>>, -/// &'a Signal, -/// &'a Mutex>>, -/// &'a Signal -/// ) -> impl (Future + 'a) -/// ``` -/// However, `impl (Future + 'a)` is not real syntax. This could be done with the unstable feature type_alias_impl_trait, -/// but we use this helper trait so as to not require use of nightly. -pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, Arg5: 'a>: - FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) -> Self::Fut -{ - type Fut: Future; +/// This exists because there are lifetime issues with being generic over FnOnce or FnMut. +/// Those can be resolved, but having a dedicated trait is simpler. +pub trait Test { + fn run<'a>( + &mut self, + service: &'a ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &'a DeviceType<'a>, + device0_signal: &'a Signal, + device1: &'a DeviceType<'a>, + device1_signal: &'a Signal, + ) -> impl Future; } -impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, Arg5: 'a, F, Fut> - TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5> for F -where - F: FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) -> Fut, - Fut: Future, -{ - type Fut = Fut; -} - -pub async fn run_test(timeout: Duration, test: F, config: Config) -where - for<'a> F: TestArgsFnOnce< - 'a, - &'a ServiceMutex<'a, 'a>, - DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - &'a DeviceType<'a>, - &'a Signal, - &'a DeviceType<'a>, - &'a Signal, - >, -{ +pub async fn run_test(timeout: Duration, mut test: impl Test, config: Config) { // 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(())); @@ -138,11 +114,8 @@ where let service_context = power_policy_service::service::context::Context::new(); let completion_signal = Signal::new(); - // Ideally F would have two lifetime arguments: 'device and 'sender because the event type requires 'device: 'sender. - // But Rust doesn't currently support syntax like `for<'device, 'sender> ... where 'device: 'sender`. So we just - // use a single lifetime. However, the unified lifetime makes the drop-checker think that dropping the channel - // could be unsafe. We use ManuallyDrop to disable the drop and make the drop-checker happy. None of the types - // here do any clean-up in their Drop impls so we don't have to worry about any sort of leaks. + // For simplicity, Test::run is only generic over a single lifetime. But this causes issues with the drop checker because + // the device lifetime doesn't outlive the channel lifetime from its perspective. Use ManuallyDrop to work around this. let service_event_channel: ManuallyDrop< Channel>, EVENT_CHANNEL_SIZE>, > = ManuallyDrop::new(Channel::new()); @@ -168,7 +141,7 @@ where ArrayEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { - test( + test.run( &power_policy, service_receiver, &device0, diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 9e67877bc..62c4bccc8 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -14,6 +14,7 @@ use power_policy_service::service::config::Config; use crate::common::DeviceType; use crate::common::MINIMAL_POWER; +use crate::common::Test; use crate::common::assert_no_event; use crate::common::{ DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, run_test, @@ -24,449 +25,473 @@ const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); const MIN_CONSUMER_THRESHOLD_MW: u32 = 7500; /// Test the basic consumer flow with a single device. -async fn test_single<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - _device1: &DeviceType<'a>, - _device1_signal: &Signal, -) { - info!("Running test_single"); - // Test initial connection - { - device0 - .lock() - .await - .simulate_consumer_connection(LOW_POWER.into()) - .await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { +struct TestSingle; + +impl Test for TestSingle { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, + ) { + info!("Running test_single"); + // Test initial connection + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } - // Test detach - { - device0.lock().await.simulate_detach().await; - - // Power policy shouldn't call any functions on detach so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, - Err(TimeoutError) - ); - device0_signal.reset(); - - assert_consumer_disconnected(service_receiver, device0).await; - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } + .await; - assert_no_event(service_receiver); + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Test detach + { + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_consumer_disconnected(service_receiver, device0).await; + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + assert_no_event(service_receiver); + } } /// Test swapping to a higher powered device. -async fn test_swap_higher<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - device1: &DeviceType<'a>, - device1_signal: &Signal, -) { - info!("Running test_swap_higher"); - // Device0 connection at low power - { - device0 - .lock() - .await - .simulate_consumer_connection(LOW_POWER.into()) - .await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { +struct TestSwapHigher; + +impl Test for TestSwapHigher { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_swap_higher"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } - // Device1 connection at high power - { - device1 - .lock() - .await - .simulate_consumer_connection(HIGH_POWER.into()) .await; - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - (1, FnCall::Disconnect) - ); - device0_signal.reset(); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Device1 connection at high power + { + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { capability: HIGH_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device1_signal.reset(); - - // Should receive a disconnect event from device0 first - assert_consumer_disconnected(service_receiver, device0).await; - - assert_consumer_connected( - service_receiver, - device1, - ConsumerPowerCapability { - capability: HIGH_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } - // Test detach device1, should reconnect device0 - { - device1.lock().await.simulate_detach().await; - - // Power policy shouldn't call any functions on detach so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, - Err(TimeoutError) - ); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { + .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Test detach device1, should reconnect device0 + { + device1.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - // Should receive a disconnect event from device1 first - assert_consumer_disconnected(service_receiver, device1).await; - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } + .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } - assert_no_event(service_receiver); + assert_no_event(service_receiver); + } } /// Test a disconnect initiated by the current consumer. -async fn test_disconnect<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - device1: &DeviceType<'a>, - device1_signal: &Signal, -) { - info!("Running test_disconnect"); - // Device0 connection at low power - { - device0 - .lock() - .await - .simulate_consumer_connection(LOW_POWER.into()) - .await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { +struct TestDisconnect; + +impl Test for TestDisconnect { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_disconnect"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } - // Device1 connection at high power - { - device1 - .lock() - .await - .simulate_consumer_connection(HIGH_POWER.into()) .await; - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - (1, FnCall::Disconnect) - ); - device0_signal.reset(); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Device1 connection at high power + { + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { capability: HIGH_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device1_signal.reset(); - - // Should receive a disconnect event from device0 first - assert_consumer_disconnected(service_receiver, device0).await; - - assert_consumer_connected( - service_receiver, - device1, - ConsumerPowerCapability { - capability: HIGH_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } + .await; - // Test disconnect device1, should reconnect device0 - { - device1.lock().await.simulate_disconnect().await; - - // Power policy shouldn't call any functions on disconnect so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, - Err(TimeoutError) - ); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + // Test disconnect device1, should reconnect device0 + { + device1.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Consume the disconnect event generated by `simulate_disconnect` + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - // Consume the disconnect event generated by `simulate_disconnect` - assert_consumer_disconnected(service_receiver, device1).await; - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } + .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } - assert_no_event(service_receiver); + assert_no_event(service_receiver); + } } /// Test minimum consumer power logic. /// /// Config for this test uses [`MIN_CONSUMER_THRESHOLD_MW`]. -async fn test_min_consumer_power<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - _device1: &DeviceType<'a>, - _device1_signal: &Signal, -) { - info!("Running test_min_consumer_power"); - // Connect with power below the minimum threshold. - { - device0 - .lock() - .await - .simulate_consumer_connection(MINIMAL_POWER.into()) - .await; - - // Power policy shouldn't connect, so this call should timeout. - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, - Err(TimeoutError) - ); - device0_signal.reset(); - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); +struct TestMinConsumerPower; + +impl Test for TestMinConsumerPower { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, + ) { + info!("Running test_min_consumer_power"); + // Connect with power below the minimum threshold. + { + device0 + .lock() + .await + .simulate_consumer_connection(MINIMAL_POWER.into()) + .await; + + // Power policy shouldn't connect, so this call should timeout. + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + // Service shouldn't broadcast any events in this case. + assert_no_event(service_receiver); } - - // Service shouldn't broadcast any events in this case. - assert_no_event(service_receiver); } /// Test that we won't swap if the capabilities are the same -async fn test_no_swap<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - device1: &DeviceType<'a>, - device1_signal: &Signal, -) { - info!("Running test_no_swap"); - // Device0 connection at low power - { - device0 - .lock() - .await - .simulate_consumer_connection(LOW_POWER.into()) - .await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { +struct TestNoSwap; + +impl Test for TestNoSwap { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_no_swap"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Ensure consumer change doesn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); - } - // Device1 connection at low power, should not cause a swap since capabilities are the same - { - device1 - .lock() - .await - .simulate_consumer_connection(LOW_POWER.into()) .await; - // These should timeout since we shouldn't swap - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, - Err(TimeoutError) - ); - device0_signal.reset(); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, - Err(TimeoutError) - ); - device1_signal.reset(); - - // Shouldn't affect provider power computation - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Device1 connection at low power, should not cause a swap since capabilities are the same + { + device1 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + // These should timeout since we shouldn't swap + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + device1_signal.reset(); + + // Shouldn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + // Service shouldn't broadcast any events in this case since we shouldn't swap. + assert_no_event(service_receiver); } - - // Service shouldn't broadcast any events in this case since we shouldn't swap. - assert_no_event(service_receiver); } - #[tokio::test] async fn run_test_swap_higher() { - run_test(DEFAULT_TIMEOUT, test_swap_higher, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestSwapHigher, Default::default()).await; } #[tokio::test] async fn run_test_single() { - run_test(DEFAULT_TIMEOUT, test_single, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestSingle, Default::default()).await; } #[tokio::test] async fn run_test_disconnect() { - run_test(DEFAULT_TIMEOUT, test_disconnect, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestDisconnect, Default::default()).await; } #[tokio::test] async fn run_test_min_consumer_power() { run_test( DEFAULT_TIMEOUT, - test_min_consumer_power, + TestMinConsumerPower, Config { min_consumer_threshold_mw: Some(MIN_CONSUMER_THRESHOLD_MW), ..Default::default() @@ -477,5 +502,5 @@ async fn run_test_min_consumer_power() { #[tokio::test] async fn run_test_no_swap() { - run_test(DEFAULT_TIMEOUT, test_no_swap, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestNoSwap, Default::default()).await; } diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs index fa6a392ec..2a4a7f2b7 100644 --- a/power-policy-service/tests/provider.rs +++ b/power-policy-service/tests/provider.rs @@ -14,282 +14,298 @@ use power_policy_interface::service::event::Event as ServiceEvent; use crate::common::DeviceType; use crate::common::HIGH_POWER; +use crate::common::Test; use crate::common::assert_no_event; use crate::common::{DEFAULT_TIMEOUT, assert_provider_connected, assert_provider_disconnected, mock::FnCall, run_test}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic provider flow with a single device. -async fn test_single<'a>( - _service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - _device1: &DeviceType<'a>, - _device1_signal: &Signal, -) { - info!("Running test_single"); - // Test initial connection - { - device0.lock().await.simulate_provider_connection(LOW_POWER).await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectProvider(ProviderPowerCapability { +struct TestSingle; + +impl Test for TestSingle { + async fn run<'a>( + &mut self, + _service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, + ) { + info!("Running test_single"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_provider_connected( - service_receiver, - device0, - ProviderPowerCapability { - capability: LOW_POWER, - flags: ProviderFlags::none(), - }, - ) - .await; - } - // Test detach - { - device0.lock().await.simulate_detach().await; - - // Power policy shouldn't call any functions on detach so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, - Err(TimeoutError) - ); - device0_signal.reset(); - - assert_provider_disconnected(service_receiver, device0).await; + .await; + } + // Test detach + { + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + assert_no_event(service_receiver); } - - assert_no_event(service_receiver); } /// Test provider flow involving multiple devices and upgrading a provider's power capability. -async fn test_upgrade<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - device1: &DeviceType<'a>, - device1_signal: &Signal, -) { - info!("Running test_upgrade"); - { - // Connect device0 at high power, default service config should allow this - device0.lock().await.simulate_provider_connection(HIGH_POWER).await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectProvider(ProviderPowerCapability { +struct TestUpgrade; + +impl Test for TestUpgrade { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_upgrade"); + { + // Connect device0 at high power, default service config should allow this + device0.lock().await.simulate_provider_connection(HIGH_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { capability: HIGH_POWER, flags: ProviderFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_provider_connected( - service_receiver, - device0, - ProviderPowerCapability { - capability: HIGH_POWER, - flags: ProviderFlags::none(), - }, - ) - .await; - - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 15000); - } - - { - // Connect device1 at low power, default service config should allow this - device1.lock().await.simulate_provider_connection(LOW_POWER).await; + .await; - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectProvider(ProviderPowerCapability { + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 15000); + } + + { + // Connect device1 at low power, default service config should allow this + device1.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - }) + }, ) - ); - device1_signal.reset(); - - assert_provider_connected( - service_receiver, - device1, - ProviderPowerCapability { - capability: LOW_POWER, - flags: ProviderFlags::none(), - }, - ) - .await; - - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 22500); - } - - { - // Attempt to upgrade device1 to high power, power policy should reject this since device0 is already connected at high power - // Power policy will instead allow us to connect at low power - device1 - .lock() - .await - .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) .await; - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectProvider(ProviderPowerCapability { + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 22500); + } + + { + // Attempt to upgrade device1 to high power, power policy should reject this since device0 is already connected at high power + // Power policy will instead allow us to connect at low power + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - }) + }, ) - ); - device1_signal.reset(); - - assert_provider_connected( - service_receiver, - device1, - ProviderPowerCapability { - capability: LOW_POWER, - flags: ProviderFlags::none(), - }, - ) - .await; - - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 22500); - } - - { - // Detach device0, this should allow us to upgrade device1 to high power - device0.lock().await.simulate_detach().await; - - // Power policy shouldn't call any functions on detach so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, - Err(TimeoutError) - ); - device0_signal.reset(); - - assert_provider_disconnected(service_receiver, device0).await; - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); - } - - { - // Attempt to upgrade device1 to high power should now succeed - device1 - .lock() - .await - .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) .await; - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectProvider(ProviderPowerCapability { + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 22500); + } + + { + // Detach device0, this should allow us to upgrade device1 to high power + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); + } + + { + // Attempt to upgrade device1 to high power should now succeed + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { capability: HIGH_POWER, flags: ProviderFlags::none(), - }) + }, ) - ); - device1_signal.reset(); - - assert_provider_connected( - service_receiver, - device1, - ProviderPowerCapability { - capability: HIGH_POWER, - flags: ProviderFlags::none(), - }, - ) - .await; - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 15000); - } + .await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 15000); + } - assert_no_event(service_receiver); + assert_no_event(service_receiver); + } } /// Test the provider disconnect flow -async fn test_disconnect<'a>( - service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - _device1: &DeviceType<'a>, - _device1_signal: &Signal, -) { - info!("Running test_disconnect"); - // Test initial connection - { - device0.lock().await.simulate_provider_connection(LOW_POWER).await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectProvider(ProviderPowerCapability { +struct TestDisconnect; + +impl Test for TestDisconnect { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, + ) { + info!("Running test_disconnect"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { capability: LOW_POWER, flags: ProviderFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_provider_connected( - service_receiver, - device0, - ProviderPowerCapability { - capability: LOW_POWER, - flags: ProviderFlags::none(), - }, - ) - .await; - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); - } - // Test disconnect - { - device0.lock().await.simulate_disconnect().await; - - // Power policy shouldn't call any functions on disconnect so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, - Err(TimeoutError) - ); - device0_signal.reset(); - - assert_provider_disconnected(service_receiver, device0).await; - assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + .await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); + } + // Test disconnect + { + device0.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + assert_no_event(service_receiver); } - - assert_no_event(service_receiver); } #[tokio::test] async fn run_test_single() { - run_test(DEFAULT_TIMEOUT, test_single, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestSingle, Default::default()).await; } #[tokio::test] async fn run_test_upgrade() { - run_test(DEFAULT_TIMEOUT, test_upgrade, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestUpgrade, Default::default()).await; } #[tokio::test] async fn run_test_disconnect() { - run_test(DEFAULT_TIMEOUT, test_disconnect, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestDisconnect, Default::default()).await; } diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs index 551505fb0..673eadfc6 100644 --- a/power-policy-service/tests/unconstrained.rs +++ b/power-policy-service/tests/unconstrained.rs @@ -18,155 +18,160 @@ use crate::common::{ DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_no_event, assert_unconstrained, mock::FnCall, run_test, }; -use crate::common::{DeviceType, ServiceMutex}; +use crate::common::{DeviceType, ServiceMutex, Test}; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test unconstrained consumer flow with multiple devices. -async fn test_unconstrained<'a>( - _service: &ServiceMutex<'a, 'a>, - service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, - device0: &DeviceType<'a>, - device0_signal: &Signal, - device1: &DeviceType<'a>, - device1_signal: &Signal, -) { - info!("Running test_unconstrained"); - { - // Connect device0, without unconstrained, - device0 - .lock() - .await - .simulate_consumer_connection(LOW_POWER.into()) - .await; - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { +struct TestUnconstrained; + +impl Test for TestUnconstrained { + async fn run<'a>( + &mut self, + _service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_unconstrained"); + { + // Connect device0, without unconstrained, + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - // Should not have any unconstrained events - assert!(service_receiver.try_receive().is_err()); - } - - { - // Connect device1 with unconstrained at HIGH_POWER to force power policy to select this consumer. - device1 - .lock() - .await - .simulate_consumer_connection(ConsumerPowerCapability { - capability: HIGH_POWER, - flags: ConsumerFlags::none().with_unconstrained_power(), - }) .await; - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - (1, FnCall::Disconnect) - ); - device0_signal.reset(); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { + // Should not have any unconstrained events + assert!(service_receiver.try_receive().is_err()); + } + + { + // Connect device1 with unconstrained at HIGH_POWER to force power policy to select this consumer. + device1 + .lock() + .await + .simulate_consumer_connection(ConsumerPowerCapability { capability: HIGH_POWER, flags: ConsumerFlags::none().with_unconstrained_power(), }) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }, ) - ); - device1_signal.reset(); - - // Should receive a disconnect event from device0 first - assert_consumer_disconnected(service_receiver, device0).await; - - assert_consumer_connected( - service_receiver, - device1, - ConsumerPowerCapability { - capability: HIGH_POWER, - flags: ConsumerFlags::none().with_unconstrained_power(), - }, - ) - .await; - - assert_unconstrained( - service_receiver, - UnconstrainedState { - unconstrained: true, - available: 1, - }, - ) - .await; - } + .await; - { - // Test detach device1, unconstrained state should change - device1.lock().await.simulate_detach().await; - - // Power policy shouldn't call any functions on detach so we'll timeout - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, - Err(TimeoutError) - ); - - assert_eq!( - with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), - ( - 1, - FnCall::ConnectConsumer(ConsumerPowerCapability { + assert_unconstrained( + service_receiver, + UnconstrainedState { + unconstrained: true, + available: 1, + }, + ) + .await; + } + + { + // Test detach device1, unconstrained state should change + device1.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { capability: LOW_POWER, flags: ConsumerFlags::none(), - }) + }, ) - ); - device0_signal.reset(); - - // Should receive a disconnect event from device1 first - assert_consumer_disconnected(service_receiver, device1).await; - - assert_consumer_connected( - service_receiver, - device0, - ConsumerPowerCapability { - capability: LOW_POWER, - flags: ConsumerFlags::none(), - }, - ) - .await; - - assert_unconstrained( - service_receiver, - UnconstrainedState { - unconstrained: false, - available: 0, - }, - ) - .await; - } + .await; - assert_no_event(service_receiver); + assert_unconstrained( + service_receiver, + UnconstrainedState { + unconstrained: false, + available: 0, + }, + ) + .await; + } + + assert_no_event(service_receiver); + } } #[tokio::test] async fn run_test_unconstrained() { - run_test(DEFAULT_TIMEOUT, test_unconstrained, Default::default()).await; + run_test(DEFAULT_TIMEOUT, TestUnconstrained, Default::default()).await; } From a11dfb50704e1d8c74475b791713ff54f808c36c Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:39:26 -0700 Subject: [PATCH 61/79] type-c-service: Migrate to per-port events (#775) Currently, events are signaled to the service through a bitvec representing which ports have pending events. The service then reads pending events from each port. Refactor to a design where each port pushes events to the service instead. Also do some renaming and clean-up of event types. --- Cargo.lock | 1 - examples/rt685s-evk/Cargo.lock | 1 - examples/rt685s-evk/src/bin/type_c.rs | 19 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 21 +- examples/std/Cargo.lock | 1 - examples/std/src/bin/type_c/basic.rs | 59 ++-- examples/std/src/bin/type_c/service.rs | 12 +- examples/std/src/bin/type_c/ucsi.rs | 28 +- examples/std/src/bin/type_c/unconstrained.rs | 40 ++- .../std/src/lib/type_c/mock_controller.rs | 18 +- type-c-interface/Cargo.toml | 1 - type-c-interface/src/port/event.rs | 310 +++++------------- type-c-interface/src/port/mod.rs | 49 +-- type-c-interface/src/service/context.rs | 94 ++---- type-c-interface/src/service/event.rs | 35 +- type-c-service/src/driver/tps6699x.rs | 10 +- type-c-service/src/lib.rs | 171 +++------- type-c-service/src/service/mod.rs | 100 +++--- type-c-service/src/service/pd.rs | 14 - type-c-service/src/service/ucsi.rs | 2 +- type-c-service/src/task.rs | 9 +- type-c-service/src/wrapper/backing.rs | 59 +--- type-c-service/src/wrapper/message.rs | 52 +-- type-c-service/src/wrapper/mod.rs | 154 ++++----- type-c-service/src/wrapper/pd.rs | 57 +--- type-c-service/src/wrapper/power.rs | 4 +- type-c-service/src/wrapper/vdm.rs | 47 +-- 27 files changed, 501 insertions(+), 867 deletions(-) delete mode 100644 type-c-service/src/service/pd.rs diff --git a/Cargo.lock b/Cargo.lock index d59e46237..dd3439e05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2544,7 +2544,6 @@ name = "type-c-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "defmt 0.3.100", "embassy-sync", "embassy-time", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 889bfe641..1f227317e 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1688,7 +1688,6 @@ name = "type-c-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "defmt 0.3.100", "embassy-sync", "embassy-time", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 3b94eec78..5c0bb0ced 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -25,6 +25,8 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; +use type_c_interface::port::PortRegistration; +use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; @@ -33,6 +35,8 @@ use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; +const CHANNEL_CAPACITY: usize = 4; + const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -163,12 +167,25 @@ async fn main(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); + static PORT0_CHANNEL: Channel = Channel::new(); + static PORT1_CHANNEL: Channel = Channel::new(); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, 0, // CFU component ID - [PORT0_ID, PORT1_ID], + [ + 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 POLICY_CHANNEL0: StaticCell> = StaticCell::new(); diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 9e4c9da28..a3c43cccf 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -28,6 +28,8 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; +use type_c_interface::port::PortRegistration; +use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; @@ -36,6 +38,8 @@ use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; +const CHANNEL_CAPACITY: usize = 4; + bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; }); @@ -247,12 +251,25 @@ async fn main(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); + static PORT0_CHANNEL: Channel = Channel::new(); + static PORT1_CHANNEL: Channel = Channel::new(); static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, - CONTROLLER0_CFU_ID, - [PORT0_ID, PORT1_ID], + 0, // CFU component ID + [ + 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 POLICY_CHANNEL0: StaticCell> = StaticCell::new(); diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index bf6cec719..2f007e166 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1866,7 +1866,6 @@ name = "type-c-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "embassy-sync", "embassy-time", "embedded-services", diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 57739b021..eedcb6629 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -1,20 +1,23 @@ use embassy_executor::{Executor, Spawner}; -use embassy_sync::once_lock::OnceLock; +use embassy_sync::channel::Channel; use embassy_time::Timer; +use embedded_services::GlobalRawMutex; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; -use type_c_interface::port::{self, Cached, ControllerId}; +use type_c_interface::port::{self, ControllerId, 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 embedded_usb_pd::ucsi; - use type_c_interface::port::{ControllerStatus, PortStatus}; + use type_c_interface::port::{ControllerStatus, PortRegistration}; use super::*; @@ -29,7 +32,7 @@ mod test_controller { } impl<'a> Controller<'a> { - pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { + pub fn new(id: ControllerId, ports: &'a [PortRegistration]) -> Self { Self { controller: port::Device::new(id, ports), } @@ -80,16 +83,8 @@ mod test_controller { } async fn process_port_command(&self, command: port::PortCommand) -> Result { - Ok(match command.data { - port::PortCommandData::PortStatus(Cached(true)) => { - info!("Port status for port {}", command.port.0); - port::PortResponseData::PortStatus(PortStatus::new()) - } - _ => { - info!("Port command for port {}", command.port.0); - port::PortResponseData::Complete - } - }) + info!("Port command for port {}", command.port.0); + Ok(port::PortResponseData::Complete) } pub async fn process(&self) { @@ -109,11 +104,25 @@ mod test_controller { #[embassy_executor::task] async fn controller_task(controller_context: &'static Context) { - static CONTROLLER: OnceLock = OnceLock::new(); - - static PORTS: [GlobalPortId; 2] = [PORT0_ID, PORT1_ID]; - - let controller = CONTROLLER.get_or_init(|| test_controller::Controller::new(CONTROLLER0_ID, &PORTS)); + 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 { @@ -137,18 +146,6 @@ async fn task(spawner: Spawner) { let status = controller_context.get_controller_status(CONTROLLER0_ID).await.unwrap(); info!("Controller 0 status: {status:#?}"); - - let status = controller_context - .get_port_status(PORT0_ID, Cached(true)) - .await - .unwrap(); - info!("Port 0 status: {status:#?}"); - - let status = controller_context - .get_port_status(PORT1_ID, Cached(true)) - .await - .unwrap(); - info!("Port 1 status: {status:#?}"); } fn main() { diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 346ee26a5..0cb956ac8 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -17,8 +17,9 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; -use type_c_interface::port::ControllerId; +use type_c_interface::port::{ControllerId, PortRegistration}; use type_c_interface::service::context::Context; +use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::service::config::Config; use type_c_service::service::{EventReceiver, Service}; use type_c_service::util::power_capability_from_current; @@ -27,6 +28,7 @@ use type_c_service::wrapper::message::*; use type_c_service::wrapper::proxy::PowerProxyDevice; const NUM_PD_CONTROLLERS: usize = 1; +const CHANNEL_CAPACITY: usize = 4; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const DELAY_MS: u64 = 1000; @@ -193,12 +195,18 @@ fn create_wrapper( static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); + static PORT0_CHANNEL: Channel = Channel::new(); + static STORAGE: StaticCell> = StaticCell::new(); let storage = STORAGE.init(Storage::new( context, CONTROLLER0_ID, 0, // CFU component ID (unused) - [PORT0_ID], + [PortRegistration { + id: PORT0_ID, + sender: PORT0_CHANNEL.dyn_sender(), + receiver: PORT0_CHANNEL.dyn_receiver(), + }], )); static POLICY_CHANNEL: StaticCell> = diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 42c166038..01d35c304 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -22,13 +22,15 @@ use power_policy_service::psu::ArrayEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; -use type_c_interface::port::ControllerId; +use type_c_interface::port::{ControllerId, PortRegistration}; use type_c_interface::service::context::Context; +use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::service::config::Config; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::Storage; use type_c_service::wrapper::proxy::PowerProxyDevice; +const CHANNEL_CAPACITY: usize = 4; const NUM_PD_CONTROLLERS: usize = 2; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); @@ -229,8 +231,18 @@ async fn task(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(Context::new()); + static PORT0_CHANNEL: Channel = Channel::new(); static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + let storage0 = STORAGE0.init(Storage::new( + controller_context, + CONTROLLER0_ID, + CFU0_ID, + [PortRegistration { + id: PORT0_ID, + sender: PORT0_CHANNEL.dyn_sender(), + receiver: PORT0_CHANNEL.dyn_receiver(), + }], + )); static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); @@ -280,8 +292,18 @@ async fn task(spawner: Spawner) { let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); + static PORT1_CHANNEL: Channel = Channel::new(); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(controller_context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + let storage1 = STORAGE1.init(Storage::new( + controller_context, + CONTROLLER1_ID, + CFU1_ID, + [PortRegistration { + id: PORT1_ID, + sender: PORT1_CHANNEL.dyn_sender(), + receiver: PORT1_CHANNEL.dyn_receiver(), + }], + )); static INTERMEDIATE1: StaticCell< type_c_service::wrapper::backing::IntermediateStorage< 1, diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 4ce1ddb69..0052f730e 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -19,10 +19,14 @@ use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_interface::port::ControllerId; +use type_c_interface::port::PortRegistration; +use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::proxy::PowerProxyDevice; +const CHANNEL_CAPACITY: usize = 4; + const NUM_PD_CONTROLLERS: usize = 3; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -87,8 +91,18 @@ async fn task(spawner: Spawner) { let policy_sender0 = policy_channel0.dyn_sender(); let policy_receiver0 = policy_channel0.dyn_receiver(); + static PORT0_CHANNEL: Channel = Channel::new(); static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new(controller_context, CONTROLLER0_ID, CFU0_ID, [PORT0_ID])); + let storage0 = STORAGE0.init(Storage::new( + controller_context, + CONTROLLER0_ID, + CFU0_ID, + [PortRegistration { + id: PORT0_ID, + sender: PORT0_CHANNEL.dyn_sender(), + receiver: PORT0_CHANNEL.dyn_receiver(), + }], + )); static INTERMEDIATE0: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); @@ -123,8 +137,18 @@ async fn task(spawner: Spawner) { let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); + static PORT1_CHANNEL: Channel = Channel::new(); static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new(controller_context, CONTROLLER1_ID, CFU1_ID, [PORT1_ID])); + let storage1 = STORAGE1.init(Storage::new( + controller_context, + CONTROLLER1_ID, + CFU1_ID, + [PortRegistration { + id: PORT1_ID, + sender: PORT1_CHANNEL.dyn_sender(), + receiver: PORT1_CHANNEL.dyn_receiver(), + }], + )); static INTERMEDIATE1: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); @@ -159,8 +183,18 @@ async fn task(spawner: Spawner) { let policy_sender2 = policy_channel2.dyn_sender(); let policy_receiver2 = policy_channel2.dyn_receiver(); + static PORT2_CHANNEL: Channel = Channel::new(); static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new(controller_context, CONTROLLER2_ID, CFU2_ID, [PORT2_ID])); + let storage2 = STORAGE2.init(Storage::new( + controller_context, + CONTROLLER2_ID, + CFU2_ID, + [PortRegistration { + id: PORT2_ID, + sender: PORT2_CHANNEL.dyn_sender(), + receiver: PORT2_CHANNEL.dyn_receiver(), + }], + )); static INTERMEDIATE2: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 5f4d4e962..f97830111 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -10,12 +10,12 @@ use log::{debug, info, trace}; use power_policy_interface::capability::PowerCapability; use type_c_interface::port::{ AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, - RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, event::PortEvent, + RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, event::PortEventBitfield, }; use type_c_service::util::power_capability_from_current; pub struct ControllerState { - events: Signal, + events: Signal, status: Mutex, pd_alert: Mutex>, } @@ -38,7 +38,7 @@ impl ControllerState { ConnectionState::Attached }); - let mut events = PortEvent::none(); + let mut events = PortEventBitfield::none(); match role { PowerRole::Source => { status.available_source_contract = Some(capability); @@ -67,7 +67,7 @@ impl ControllerState { pub async fn disconnect(&self) { *self.status.lock().await = PortStatus::default(); - let mut events = PortEvent::none(); + let mut events = PortEventBitfield::none(); events.status.set_plug_inserted_or_removed(true); self.events.signal(events); } @@ -82,7 +82,7 @@ impl ControllerState { pub async fn send_pd_alert(&self, ado: Ado) { *self.pd_alert.lock().await = Some(ado); - let mut events = PortEvent::none(); + let mut events = PortEventBitfield::none(); events.notification.set_alert(true); self.events.signal(events); } @@ -96,14 +96,14 @@ impl Default for ControllerState { pub struct Controller<'a> { state: &'a ControllerState, - events: PortEvent, + events: PortEventBitfield, } impl<'a> Controller<'a> { pub fn new(state: &'a ControllerState) -> Self { Self { state, - events: PortEvent::none(), + events: PortEventBitfield::none(), } } @@ -123,10 +123,10 @@ impl type_c_interface::port::Controller for Controller<'_> { Ok(()) } - async fn clear_port_events(&mut self, _port: LocalPortId) -> Result> { + async fn clear_port_events(&mut self, _port: LocalPortId) -> Result> { let events = self.events; debug!("Clear port events: {events:#?}"); - self.events = PortEvent::none(); + self.events = PortEventBitfield::none(); Ok(events) } diff --git a/type-c-interface/Cargo.toml b/type-c-interface/Cargo.toml index b425a1fed..6d33373b3 100644 --- a/type-c-interface/Cargo.toml +++ b/type-c-interface/Cargo.toml @@ -7,7 +7,6 @@ repository.workspace = true [dependencies] bitfield.workspace = true -bitvec.workspace = true embassy-sync.workspace = true embassy-time.workspace = true log = { workspace = true, optional = true } diff --git a/type-c-interface/src/port/event.rs b/type-c-interface/src/port/event.rs index eb951079d..312680fdd 100644 --- a/type-c-interface/src/port/event.rs +++ b/type-c-interface/src/port/event.rs @@ -1,18 +1,18 @@ //! This module provides TCPM event types and bitfields. //! Hardware typically uses bitfields to store pending events/interrupts so we provide generic versions of these. -//! [`PortStatusChanged`] contains events related to the overall port state (plug state, power contract, etc). -//! Processing these events typically requires acessing similar registers so they are grouped together. -//! [`PortNotification`] contains events that are typically more message-like (PD alerts, VDMs, etc) and can be processed independently. -//! Consequently [`PortNotification`] implements iterator traits to allow for processing these events as a stream. +//! [`PortStatusEventBitfield`] contains events related to the overall port state (plug state, power contract, etc). +//! Processing these events typically requires accessing similar registers so they are grouped together. +//! [`PortNotificationEventBitfield`] contains events that are typically more message-like (PD alerts, VDMs, etc) and can be processed independently. +//! Consequently [`PortNotificationEventBitfield`] implements iterator traits to allow for processing these events as a stream. use bitfield::bitfield; -use bitvec::BitArr; -use embedded_services::error; + +use crate::port::{AttnVdm, OtherVdm}; bitfield! { /// Raw bitfield of possible port status events #[derive(Copy, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct PortStatusChangedRaw(u16); + struct PortStatusEventBitfieldRaw(u16); impl Debug; /// Plug inserted or removed pub u8, plug_inserted_or_removed, set_plug_inserted_or_removed: 0, 0; @@ -34,31 +34,23 @@ bitfield! { pub u8, pd_hard_reset, set_pd_hard_reset: 8, 8; } -/// Port pending errors -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortPendingError { - /// Invalid port - InvalidPort(usize), -} - /// Port status change events /// This is a type-safe wrapper around the raw bitfield /// These events are related to the overall port state and typically need to be considered together. #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortStatusChanged(PortStatusChangedRaw); +pub struct PortStatusEventBitfield(PortStatusEventBitfieldRaw); -impl PortStatusChanged { +impl PortStatusEventBitfield { /// Create a new PortEventKind with no pending events pub const fn none() -> Self { - Self(PortStatusChangedRaw(0)) + Self(PortStatusEventBitfieldRaw(0)) } /// Returns the union of self and other - pub fn union(self, other: PortStatusChanged) -> PortStatusChanged { + pub fn union(self, other: PortStatusEventBitfield) -> PortStatusEventBitfield { // This spacing is what rustfmt wants - PortStatusChanged(PortStatusChangedRaw(self.0.0 | other.0.0)) + PortStatusEventBitfield(PortStatusEventBitfieldRaw(self.0.0 | other.0.0)) } /// Returns true if a plug was inserted or removed @@ -156,7 +148,7 @@ bitfield! { /// Raw bitfield of possible port notification events #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] - struct PortNotificationRaw(u16); + struct PortNotificationEventBitfieldRaw(u16); impl Debug; /// PD alert pub u8, alert, set_alert: 0, 0; @@ -181,18 +173,18 @@ bitfield! { /// These events are unrelated to the overall port state and each other. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortNotification(PortNotificationRaw); +pub struct PortNotificationEventBitfield(PortNotificationEventBitfieldRaw); -impl PortNotification { +impl PortNotificationEventBitfield { /// Create a new PortNotification with no pending events pub const fn none() -> Self { - Self(PortNotificationRaw(0)) + Self(PortNotificationEventBitfieldRaw(0)) } /// Returns the union of self and other - pub fn union(self, other: PortNotification) -> PortNotification { + pub fn union(self, other: PortNotificationEventBitfield) -> PortNotificationEventBitfield { // This spacing is what rustfmt wants - PortNotification(PortNotificationRaw(self.0.0 | other.0.0)) + PortNotificationEventBitfield(PortNotificationEventBitfieldRaw(self.0.0 | other.0.0)) } /// Returns true if an alert was received @@ -290,10 +282,26 @@ pub enum VdmNotification { OtherReceived, } -/// Individual port notifications +/// VDM event data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum VdmData { + /// Entered custom mode + Entered(OtherVdm), + /// Exited custom mode + Exited(OtherVdm), + /// Received a vendor-defined other message + ReceivedOther(OtherVdm), + /// Received a vendor-defined attention message + ReceivedAttn(AttnVdm), +} + +/// Enum to contain all port event variants #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortNotificationSingle { +pub enum PortEvent { + /// Port status change events + StatusChanged(PortStatusEventBitfield), /// PD alert Alert, /// VDM @@ -306,34 +314,34 @@ pub enum PortNotificationSingle { DpStatusUpdate, } -impl Iterator for PortNotification { - type Item = PortNotificationSingle; +impl Iterator for PortNotificationEventBitfield { + type Item = PortEvent; fn next(&mut self) -> Option { if self.alert() { self.set_alert(false); - Some(PortNotificationSingle::Alert) + Some(PortEvent::Alert) } else if self.custom_mode_entered() { self.set_custom_mode_entered(false); - Some(PortNotificationSingle::Vdm(VdmNotification::Entered)) + Some(PortEvent::Vdm(VdmNotification::Entered)) } else if self.custom_mode_exited() { self.set_custom_mode_exited(false); - Some(PortNotificationSingle::Vdm(VdmNotification::Exited)) + Some(PortEvent::Vdm(VdmNotification::Exited)) } else if self.custom_mode_attention_received() { self.set_custom_mode_attention_received(false); - Some(PortNotificationSingle::Vdm(VdmNotification::AttentionReceived)) + Some(PortEvent::Vdm(VdmNotification::AttentionReceived)) } else if self.custom_mode_other_vdm_received() { self.set_custom_mode_other_vdm_received(false); - Some(PortNotificationSingle::Vdm(VdmNotification::OtherReceived)) + Some(PortEvent::Vdm(VdmNotification::OtherReceived)) } else if self.discover_mode_completed() { self.set_discover_mode_completed(false); - Some(PortNotificationSingle::DiscoverModeCompleted) + Some(PortEvent::DiscoverModeCompleted) } else if self.usb_mux_error_recovery() { self.set_usb_mux_error_recovery(false); - Some(PortNotificationSingle::UsbMuxErrorRecovery) + Some(PortEvent::UsbMuxErrorRecovery) } else if self.dp_status_update() { self.set_dp_status_update(false); - Some(PortNotificationSingle::DpStatusUpdate) + Some(PortEvent::DpStatusUpdate) } else { None } @@ -343,304 +351,146 @@ impl Iterator for PortNotification { /// Overall port event type #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortEvent { +pub struct PortEventBitfield { /// Port status change events - pub status: PortStatusChanged, + pub status: PortStatusEventBitfield, /// Port notification events - pub notification: PortNotification, + pub notification: PortNotificationEventBitfield, } -impl PortEvent { +impl PortEventBitfield { /// Creates a new PortEvent with no pending events pub const fn none() -> Self { Self { - status: PortStatusChanged::none(), - notification: PortNotification::none(), + status: PortStatusEventBitfield::none(), + notification: PortNotificationEventBitfield::none(), } } /// Returns the union of self and other - pub fn union(self, other: PortEvent) -> PortEvent { - PortEvent { + pub fn union(self, other: PortEventBitfield) -> PortEventBitfield { + PortEventBitfield { status: self.status.union(other.status), notification: self.notification.union(other.notification), } } } -impl Default for PortEvent { +impl Default for PortEventBitfield { fn default() -> Self { Self::none() } } -impl From for PortEvent { - fn from(status: PortStatusChanged) -> Self { +impl From for PortEventBitfield { + fn from(status: PortStatusEventBitfield) -> Self { Self { status, - notification: PortNotification::none(), + notification: PortNotificationEventBitfield::none(), } } } -impl From for PortEvent { - fn from(notification: PortNotification) -> Self { +impl From for PortEventBitfield { + fn from(notification: PortNotificationEventBitfield) -> Self { Self { - status: PortStatusChanged::none(), + status: PortStatusEventBitfield::none(), notification, } } } -/// Bit vector type to store pending port events -type PortPendingVec = BitArr!(for 32, in u32); - -/// Pending port events -/// -/// This type works using usize to allow use with both global and local port IDs. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(transparent)] -pub struct PortPending(PortPendingVec); - -impl PortPending { - /// Creates a new PortPending with no pending ports - pub const fn none() -> Self { - Self(PortPendingVec::ZERO) - } - - /// Returns true if there are no pending ports - pub fn is_none(&self) -> bool { - self.0 == PortPendingVec::ZERO - } - - /// Marks the given port as pending - pub fn pend_port(&mut self, port: usize) -> Result<(), PortPendingError> { - if port >= self.0.len() { - return Err(PortPendingError::InvalidPort(port)); - } - self.0.set(port, true); - - Ok(()) - } - - /// Marks the indexes given by the iterator as pending - pub fn pend_ports>(&mut self, iter: I) { - for port in iter { - if self.pend_port(port).is_err() { - error!("Error pending port {}", port); - } - } - } - - /// Clears the pending status of the given port - pub fn clear_port(&mut self, port: usize) -> Result<(), PortPendingError> { - if port >= self.0.len() { - return Err(PortPendingError::InvalidPort(port)); - } - - self.0.set(port, false); - Ok(()) - } - - /// Returns true if the given port is pending - pub fn is_pending(&self, port: usize) -> Result { - Ok(*self.0.get(port).ok_or(PortPendingError::InvalidPort(port))?) - } - - /// Returns a combination of the current pending ports and other - pub fn union(&self, other: PortPending) -> PortPending { - PortPending(self.0 | other.0) - } - - /// Returns the number of bits in Self - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.0.len() - } -} - -impl From for u32 { - fn from(flags: PortPending) -> Self { - flags.0.data[0] - } -} - -impl Default for PortPending { - fn default() -> Self { - Self::none() - } -} - -impl FromIterator for PortPending { - fn from_iter>(iter: T) -> Self { - let mut flags = PortPending::none(); - flags.pend_ports(iter); - flags - } -} - -/// An iterator over the pending port event flags -#[derive(Copy, Clone)] -pub struct PortPendingIter { - /// The flags being iterated over - flags: PortPending, - /// The current index in the flags - index: usize, -} - -impl Iterator for PortPendingIter { - type Item = usize; - - fn next(&mut self) -> Option { - while self.index < self.flags.len() { - let port_index = self.index; - self.index += 1; - if self.flags.is_pending(port_index).unwrap_or(false) { - if self.flags.clear_port(port_index).is_ok() { - return Some(port_index); - } else { - continue; - } - } - } - None - } -} - -impl IntoIterator for PortPending { - type Item = usize; - type IntoIter = PortPendingIter; - - fn into_iter(self) -> PortPendingIter { - PortPendingIter { flags: self, index: 0 } - } -} - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use super::*; - #[test] - fn test_port_event_flags_iter() { - let mut pending = PortPending::none(); - - pending.pend_port(0).unwrap(); - pending.pend_port(1).unwrap(); - pending.pend_port(2).unwrap(); - pending.pend_port(10).unwrap(); - pending.pend_port(23).unwrap(); - pending.pend_port(31).unwrap(); - - let result = pending.pend_port(32); - let expected = Err(PortPendingError::InvalidPort(32)); - assert_eq!(expected, result); - - let mut iter = pending.into_iter(); - assert_eq!(iter.next(), Some(0)); - assert_eq!(iter.next(), Some(1)); - assert_eq!(iter.next(), Some(2)); - assert_eq!(iter.next(), Some(10)); - assert_eq!(iter.next(), Some(23)); - assert_eq!(iter.next(), Some(31)); - assert_eq!(iter.next(), None); - } - #[test] fn test_port_notification_iter_all() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_alert(true); notification.set_custom_mode_entered(true); - assert_eq!(notification.next(), Some(PortNotificationSingle::Alert)); - assert_eq!( - notification.next(), - Some(PortNotificationSingle::Vdm(VdmNotification::Entered)) - ); + assert_eq!(notification.next(), Some(PortEvent::Alert)); + assert_eq!(notification.next(), Some(PortEvent::Vdm(VdmNotification::Entered))); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_alert() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_alert(true); - assert_eq!(notification.next(), Some(PortNotificationSingle::Alert)); + assert_eq!(notification.next(), Some(PortEvent::Alert)); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_custom_mode_entered() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_custom_mode_entered(true); - assert_eq!( - notification.next(), - Some(PortNotificationSingle::Vdm(VdmNotification::Entered)) - ); + assert_eq!(notification.next(), Some(PortEvent::Vdm(VdmNotification::Entered))); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_custom_mode_exited() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_custom_mode_exited(true); - assert_eq!( - notification.next(), - Some(PortNotificationSingle::Vdm(VdmNotification::Exited)) - ); + assert_eq!(notification.next(), Some(PortEvent::Vdm(VdmNotification::Exited))); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_custom_mode_attention_received() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_custom_mode_attention_received(true); assert_eq!( notification.next(), - Some(PortNotificationSingle::Vdm(VdmNotification::AttentionReceived)) + Some(PortEvent::Vdm(VdmNotification::AttentionReceived)) ); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_custom_mode_other_vdm_received() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_custom_mode_other_vdm_received(true); assert_eq!( notification.next(), - Some(PortNotificationSingle::Vdm(VdmNotification::OtherReceived)) + Some(PortEvent::Vdm(VdmNotification::OtherReceived)) ); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_discover_mode_completed() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_discover_mode_completed(true); - assert_eq!(notification.next(), Some(PortNotificationSingle::DiscoverModeCompleted)); + assert_eq!(notification.next(), Some(PortEvent::DiscoverModeCompleted)); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_usb_mux_error_recovery() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_usb_mux_error_recovery(true); - assert_eq!(notification.next(), Some(PortNotificationSingle::UsbMuxErrorRecovery)); + assert_eq!(notification.next(), Some(PortEvent::UsbMuxErrorRecovery)); assert_eq!(notification.next(), None); } #[test] fn test_port_notification_iter_dp_status_update() { - let mut notification = PortNotification::none(); + let mut notification = PortNotificationEventBitfield::none(); notification.set_dp_status_update(true); - assert_eq!(notification.next(), Some(PortNotificationSingle::DpStatusUpdate)); + assert_eq!(notification.next(), Some(PortEvent::DpStatusUpdate)); assert_eq!(notification.next(), None); } } diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index f52836b6a..e7b19de8e 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -1,6 +1,7 @@ //! PD controller related code use core::future::Future; +use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use embedded_usb_pd::ucsi::{self, lpm}; use embedded_usb_pd::{ DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, @@ -14,7 +15,8 @@ use embedded_services::{GlobalRawMutex, intrusive_list}; pub mod event; -use event::{PortEvent, PortPending}; +use crate::port::event::PortEventBitfield; +use crate::service::event::PortEvent as ServicePortEvent; /// Length of the Other VDM data pub const OTHER_VDM_LEN: usize = 29; @@ -23,11 +25,6 @@ pub const ATTN_VDM_LEN: usize = 9; /// maximum number of data objects in a VDM pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each -/// Newtype to help clarify arguments to port status commands -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Cached(pub bool); - /// Controller ID #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -275,10 +272,6 @@ pub enum TypeCStateMachineState { #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PortCommandData { - /// Get port status - PortStatus(Cached), - /// Get and clear events - ClearEvents, /// Get retimer fw update state RetimerFwUpdateGetState, /// Set retimer fw update state @@ -289,8 +282,6 @@ pub enum PortCommandData { SetRetimerCompliance, /// Reconfigure retimer ReconfigureRetimer, - /// Get oldest unhandled PD alert - GetPdAlert, /// Set the maximum sink voltage in mV for the given port SetMaxSinkVoltage(Option), /// Set unconstrained power @@ -347,10 +338,6 @@ pub enum RetimerFwUpdateState { pub enum PortResponseData { /// Command completed with no error Complete, - /// Port status - PortStatus(PortStatus), - /// ClearEvents - ClearEvents(PortEvent), /// Retimer Fw Update status RtFwUpdateStatus(RetimerFwUpdateState), /// PD alert @@ -441,11 +428,21 @@ pub struct ControllerStatus<'a> { pub fw_version1: u32, } +/// 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, - ports: &'a [GlobalPortId], + pub ports: &'a [PortRegistration], num_ports: usize, command: deferred::Channel>, } @@ -458,7 +455,7 @@ impl intrusive_list::NodeContainer for Device<'static> { impl<'a> Device<'a> { /// Create a new PD controller struct - pub fn new(id: ControllerId, ports: &'a [GlobalPortId]) -> Self { + pub fn new(id: ControllerId, ports: &'a [PortRegistration]) -> Self { Self { node: intrusive_list::Node::uninit(), id, @@ -485,14 +482,14 @@ impl<'a> Device<'a> { /// 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)?) + 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(|p| *p == port) + .position(|descriptor| descriptor.id == port) .map(|p| LocalPortId(p as u8)) .ok_or(PdError::InvalidParams) } @@ -504,20 +501,10 @@ impl<'a> Device<'a> { self.command.receive().await } - /// Notify that there are pending events on one or more ports - pub fn notify_ports(&self, ctx: &crate::service::context::Context, pending: PortPending) { - ctx.notify_ports(pending); - } - /// Number of ports on this controller pub fn num_ports(&self) -> usize { self.num_ports } - - /// Slice of global ports on the Device - pub fn ports(&self) -> &'a [GlobalPortId] { - self.ports - } } /// PD controller trait that device drivers may use to integrate with internal messaging system @@ -537,7 +524,7 @@ pub trait Controller { fn clear_port_events( &mut self, port: LocalPortId, - ) -> impl Future>>; + ) -> impl Future>>; /// Returns the port status fn get_port_status(&mut self, port: LocalPortId) -> impl Future>>; diff --git a/type-c-interface/src/service/context.rs b/type-c-interface/src/service/context.rs index 5efa98652..f5e8b4bfd 100644 --- a/type-c-interface/src/service/context.rs +++ b/type-c-interface/src/service/context.rs @@ -1,20 +1,16 @@ -use embassy_sync::signal::Signal; use embassy_time::{Duration, with_timeout}; use embedded_usb_pd::ucsi::{self, lpm}; -use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; +use embedded_usb_pd::{GlobalPortId, PdError}; -use crate::port::event::{PortEvent, PortPending}; +use crate::port::ControllerId; use crate::port::{ AttnVdm, Command, ControllerStatus, Device, DpConfig, DpStatus, InternalCommandData, InternalResponseData, - OtherVdm, PdStateMachineConfig, PortCommand, PortCommandData, PortResponseData, PortStatus, Response, - RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, + OtherVdm, PdStateMachineConfig, PortCommand, PortCommandData, PortResponseData, Response, RetimerFwUpdateState, + SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, }; -use crate::port::{Cached, ControllerId}; use crate::service; -use crate::service::event::Event; -use embedded_services::{ - GlobalRawMutex, IntrusiveNode, broadcaster::immediate as broadcaster, error, intrusive_list, trace, -}; +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 @@ -36,11 +32,10 @@ impl DeviceContainer for Device<'_> { /// /// This struct is going to be merged into the service implementation and removed from here. pub struct Context { - port_events: Signal, /// Event broadcaster broadcaster: broadcaster::Immediate, /// Controller list - controllers: intrusive_list::IntrusiveList, + pub controllers: intrusive_list::IntrusiveList, } impl Default for Context { @@ -53,30 +48,11 @@ impl Context { /// Create new Context pub const fn new() -> Self { Self { - port_events: Signal::new(), broadcaster: broadcaster::Immediate::new(), controllers: intrusive_list::IntrusiveList::new(), } } - /// Notify that there are pending events on one or more ports - /// Each bit corresponds to a global port ID - pub fn notify_ports(&self, pending: PortPending) { - let raw_pending: u32 = pending.into(); - trace!("Notify ports: {:#x}", raw_pending); - // Early exit if no events - if pending.is_none() { - return; - } - - self.port_events - .signal(if let Some(flags) = self.port_events.try_take() { - flags.union(pending) - } else { - pending - }); - } - /// Send a command to the given controller with no timeout pub async fn send_controller_command_no_timeout( &self, @@ -222,47 +198,6 @@ impl Context { } } - /// Get the current port events - pub async fn get_unhandled_events(&self) -> PortPending { - self.port_events.wait().await - } - - /// Get the unhandled events for the given port - pub async fn get_port_event(&self, port: GlobalPortId) -> Result { - match self.send_port_command(port, PortCommandData::ClearEvents).await? { - PortResponseData::ClearEvents(event) => Ok(event), - r => { - error!("Invalid response: expected clear events, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the current port status - pub async fn get_port_status(&self, port: GlobalPortId, cached: Cached) -> Result { - match self - .send_port_command(port, PortCommandData::PortStatus(cached)) - .await? - { - PortResponseData::PortStatus(status) => Ok(status), - r => { - error!("Invalid response: expected port status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Get the oldest unhandled PD alert for the given port - pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { - match self.send_port_command(port, PortCommandData::GetPdAlert).await? { - PortResponseData::PdAlert(alert) => Ok(alert), - r => { - error!("Invalid response: expected PD alert, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - /// Get the retimer fw update status pub async fn get_rt_fw_update_status(&self, port: GlobalPortId) -> Result { match self @@ -534,4 +469,19 @@ impl Context { .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 800d15558..08ac63cae 100644 --- a/type-c-interface/src/service/event.rs +++ b/type-c-interface/src/service/event.rs @@ -1,8 +1,39 @@ //! Comms service message definitions -use embedded_usb_pd::GlobalPortId; +use embedded_usb_pd::{GlobalPortId, ado::Ado}; -/// Message generated when a debug acessory is connected or disconnected +use crate::port::{ + PortStatus, + event::{PortStatusEventBitfield, VdmData}, +}; + +/// Enum to contain all port event variants +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PortEventData { + /// Port status change events + StatusChanged(PortStatusEventBitfield, PortStatus), + /// PD alert + Alert(Ado), + /// VDM + Vdm(VdmData), + /// Discover mode completed + DiscoverModeCompleted, + /// USB mux error recovery + UsbMuxErrorRecovery, + /// DP status update + DpStatusUpdate, +} + +/// Struct containing a complete port event +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PortEvent { + pub port: GlobalPortId, + pub event: PortEventData, +} + +/// Message generated when a debug accessory is connected or disconnected #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DebugAccessory { diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index f801a6e85..ab7f89eb1 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -28,7 +28,7 @@ use tps6699x::command::{ }; use tps6699x::fw_update::UpdateConfig as FwUpdateConfig; use tps6699x::registers::port_config::TypeCStateMachine; -use type_c_interface::port::event::PortEvent; +use type_c_interface::port::event::PortEventBitfield; use type_c_interface::port::{ATTN_VDM_LEN, DpConfig, DpStatus, PdStateMachineConfig, RetimerFwUpdateState}; use type_c_interface::port::{ AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, @@ -52,7 +52,7 @@ struct FwUpdateState<'a, M: RawMutex, B: I2c> { } pub struct Tps6699x<'a, M: RawMutex, B: I2c> { - port_events: heapless::Vec, + port_events: heapless::Vec, tps6699x: tps6699x_drv::Tps6699x<'a, M, B>, update_state: Option>, /// Firmware update configuration @@ -73,7 +73,7 @@ impl<'a, M: RawMutex, B: I2c> Tps6699x<'a, M, B> { } else { Some(Self { // num_ports validated by branch - port_events: heapless::Vec::from_iter((0..num_ports).map(|_| PortEvent::none())), + port_events: heapless::Vec::from_iter((0..num_ports).map(|_| PortEventBitfield::none())), tps6699x, update_state: None, fw_update_config, @@ -274,9 +274,7 @@ impl Controller for Tps6699x<'_, M, B> { } /// Returns and clears current events for the given port - /// - /// Drop safety: All state changes happen after await point - async fn clear_port_events(&mut self, port: LocalPortId) -> Result> { + async fn clear_port_events(&mut self, port: LocalPortId) -> Result> { Ok(core::mem::take( self.port_events.get_mut(port.0 as usize).ok_or(PdError::InvalidPort)?, )) diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index e15a4513e..7fa30c01f 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -8,53 +8,43 @@ pub mod wrapper; use core::future::Future; use type_c_interface::port::event::{ - PortEvent, PortNotification, PortNotificationSingle, PortPendingIter, PortStatusChanged, + PortEvent, PortEventBitfield, PortNotificationEventBitfield, PortStatusEventBitfield, }; -/// Enum to contain all port event variants -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PortEventVariant { - /// Port status change events - StatusChanged(PortStatusChanged), - /// Port notification events - Notification(PortNotificationSingle), -} - /// Struct to convert port events into a stream of events -#[derive(Clone, Copy)] -pub struct PortEventStreamer { +#[derive(Clone)] +pub struct PortEventStreamer> { /// Current port index being processed port_index: Option, /// Iterator over pending ports - pending_iter: PortPendingIter, + port_iter: Iter, /// Notification to be streamed - pending_notifications: Option, + pending_notifications: Option, } -impl PortEventStreamer { +impl> PortEventStreamer { /// Create new PortEventStreamer /// /// Returns none if there are no pending ports to process. - pub fn new(pending_iter: PortPendingIter) -> Self { + pub fn new(port_iter: Iter) -> Self { Self { port_index: None, - pending_iter, + port_iter, pending_notifications: None, } } } -impl PortEventStreamer { +impl> PortEventStreamer { /// Get the next port event, calls the closure if it needs to get pending events for the current port. - pub async fn next>, F: FnMut(usize) -> Fut>( + pub async fn next>, F: FnMut(usize) -> Fut>( &mut self, mut f: F, - ) -> Result, E> { + ) -> Result, E> { loop { let port_index = if let Some(index) = self.port_index { index - } else if let Some(next_port) = self.pending_iter.next() { + } else if let Some(next_port) = self.port_iter.next() { // First time this function is called, get our starting port index self.port_index = Some(next_port); next_port @@ -70,7 +60,7 @@ impl PortEventStreamer { if let Some(port_event) = pending.next() { // Return a single notification self.pending_notifications = Some(pending); - ret = Some((port_index, PortEventVariant::Notification(port_event))); + ret = Some((port_index, port_event)); } else { // Done with pending notifications, continue to the next port advance_port = true; @@ -80,7 +70,7 @@ impl PortEventStreamer { // Haven't read port events yet let event = f(port_index).await?; - if event.notification != PortNotification::none() { + if event.notification != PortNotificationEventBitfield::none() { // Have pending notifications to stream as events, store those for the next loop/call to this function self.pending_notifications = Some(event.notification); } else { @@ -89,14 +79,14 @@ impl PortEventStreamer { self.pending_notifications = None; } - if event.status != PortStatusChanged::none() { + if event.status != PortStatusEventBitfield::none() { // Return the port status changed event first if there is one - ret = Some((port_index, PortEventVariant::StatusChanged(event.status))); + ret = Some((port_index, PortEvent::StatusChanged(event.status))); } } if advance_port { - if let Some(next_port) = self.pending_iter.next() { + if let Some(next_port) = self.port_iter.next() { // Move to the next port self.port_index = Some(next_port); } else if ret.is_none() { @@ -122,15 +112,12 @@ impl PortEventStreamer { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use core::sync::atomic::AtomicBool; - - use type_c_interface::port::event::PortPending; - use super::*; + use core::sync::atomic::AtomicBool; - /// Utitily function to create a PortStatusChanged event - fn status_changed(plug_event: bool, power_contract: bool, sink_ready: bool) -> PortStatusChanged { - let mut status_changed = PortStatusChanged::none(); + /// Utility function to create a PortStatusChanged event + fn status_changed(plug_event: bool, power_contract: bool, sink_ready: bool) -> PortStatusEventBitfield { + let mut status_changed = PortStatusEventBitfield::none(); status_changed.set_plug_inserted_or_removed(plug_event); status_changed.set_new_power_contract_as_consumer(power_contract); status_changed.set_sink_ready(sink_ready); @@ -138,8 +125,8 @@ mod tests { } /// Utility function to create a PortNotification event - fn notification(alert: bool, discover_mode_completed: bool) -> PortNotification { - let mut notification = PortNotification::none(); + fn notification(alert: bool, discover_mode_completed: bool) -> PortNotificationEventBitfield { + let mut notification = PortNotificationEventBitfield::none(); notification.set_alert(alert); notification.set_discover_mode_completed(discover_mode_completed); notification @@ -148,22 +135,14 @@ mod tests { /// Test iterating over port status changed events #[tokio::test] async fn test_port_status_changed() { - let mut pending_ports = PortPending::none(); - pending_ports.pend_port(0).unwrap(); - pending_ports.pend_port(2).unwrap(); - pending_ports.pend_port(3).unwrap(); - - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); + let mut streamer = PortEventStreamer::new(0..3); let event = streamer .next::<(), _, _>(async |_| Ok(status_changed(true, true, true).into())) .await; assert_eq!( event, - Ok(Some(( - 0, - PortEventVariant::StatusChanged(status_changed(true, true, true)) - ))) + Ok(Some((0, PortEvent::StatusChanged(status_changed(true, true, true))))) ); let event = streamer @@ -171,10 +150,7 @@ mod tests { .await; assert_eq!( event, - Ok(Some(( - 2, - PortEventVariant::StatusChanged(status_changed(true, false, true)) - ))) + Ok(Some((1, PortEvent::StatusChanged(status_changed(true, false, true))))) ); let event = streamer @@ -182,10 +158,7 @@ mod tests { .await; assert_eq!( event, - Ok(Some(( - 3, - PortEventVariant::StatusChanged(status_changed(false, false, true)) - ))) + Ok(Some((2, PortEvent::StatusChanged(status_changed(false, false, true))))) ); let event = streamer @@ -197,28 +170,16 @@ mod tests { /// Test iterating over port notifications #[tokio::test] async fn test_port_notification() { - let mut pending_ports = PortPending::none(); - pending_ports.pend_port(0).unwrap(); - - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); + let mut streamer = PortEventStreamer::new(0..1); let event = streamer .next::<(), _, _>(async |_| Ok(notification(true, true).into())) .await; - assert_eq!( - event, - Ok(Some((0, PortEventVariant::Notification(PortNotificationSingle::Alert)))) - ); + assert_eq!(event, Ok(Some((0, PortEvent::Alert)))); let event = streamer .next::<(), _, _>(async |_| Ok(notification(true, true).into())) .await; - assert_eq!( - event, - Ok(Some(( - 0, - PortEventVariant::Notification(PortNotificationSingle::DiscoverModeCompleted) - ))) - ); + assert_eq!(event, Ok(Some((0, PortEvent::DiscoverModeCompleted)))); let event = streamer .next::<(), _, _>(async |_| Ok(notification(true, true).into())) @@ -229,20 +190,14 @@ mod tests { /// Test the the final port with no pending notifications #[tokio::test] async fn test_last_notifications() { - let mut pending_ports = PortPending::none(); - pending_ports.pend_port(0).unwrap(); - - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); + let mut streamer = PortEventStreamer::new(0..1); // Test p0 events let p0_event = status_changed(true, true, true).into(); let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; assert_eq!( event, - Ok(Some(( - 0, - PortEventVariant::StatusChanged(status_changed(true, true, true)) - ))) + Ok(Some((0, PortEvent::StatusChanged(status_changed(true, true, true))))) ); let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; @@ -252,14 +207,10 @@ mod tests { /// Test iterating over both status and notification events #[tokio::test] async fn test_port_event() { - let mut pending_ports = PortPending::none(); - pending_ports.pend_port(0).unwrap(); - pending_ports.pend_port(6).unwrap(); - - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); + let mut streamer = PortEventStreamer::new(0..2); // Test p0 events - let p0_event = PortEvent { + let p0_event = PortEventBitfield { status: status_changed(true, true, true), notification: notification(true, false), }; @@ -267,51 +218,35 @@ mod tests { let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; assert_eq!( event, - Ok(Some(( - 0, - PortEventVariant::StatusChanged(status_changed(true, true, true)) - ))) + Ok(Some((0, PortEvent::StatusChanged(status_changed(true, true, true))))) ); let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; - assert_eq!( - event, - Ok(Some((0, PortEventVariant::Notification(PortNotificationSingle::Alert)))) - ); + assert_eq!(event, Ok(Some((0, PortEvent::Alert)))); - // Test p6 events - let p6_event = PortEvent { + // Test p1 events + let p1_event = PortEventBitfield { status: status_changed(false, true, false), notification: notification(false, true), }; - let event = streamer.next::<(), _, _>(async |_| Ok(p6_event)).await; + let event = streamer.next::<(), _, _>(async |_| Ok(p1_event)).await; assert_eq!( event, - Ok(Some(( - 6, - PortEventVariant::StatusChanged(status_changed(false, true, false)) - ))) + Ok(Some((1, PortEvent::StatusChanged(status_changed(false, true, false))))) ); - let event = streamer.next::<(), _, _>(async |_| Ok(p6_event)).await; - assert_eq!( - event, - Ok(Some(( - 6, - PortEventVariant::Notification(PortNotificationSingle::DiscoverModeCompleted) - ))) - ); + let event = streamer.next::<(), _, _>(async |_| Ok(p1_event)).await; + assert_eq!(event, Ok(Some((1, PortEvent::DiscoverModeCompleted)))); - let event = streamer.next::<(), _, _>(async |_| Ok(p6_event)).await; + let event = streamer.next::<(), _, _>(async |_| Ok(p1_event)).await; assert_eq!(event, Ok(None)); } /// Test no pending ports #[tokio::test] async fn test_no_pending_ports() { - let pending_ports = PortPending::none(); - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); + let mut streamer = PortEventStreamer::new(0..0); let event = streamer .next::<(), _, _>(async |_| Ok(status_changed(true, true, true).into())) .await; @@ -321,22 +256,15 @@ mod tests { /// Test a port with a pending event with no actual event #[tokio::test] async fn test_empty_event() { - let mut pending_ports = PortPending::none(); - pending_ports.pend_port(0).unwrap(); - - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); - let event = streamer.next::<(), _, _>(async |_| Ok(PortEvent::none())).await; + let mut streamer = PortEventStreamer::new(0..1); + let event = streamer.next::<(), _, _>(async |_| Ok(PortEventBitfield::none())).await; assert_eq!(event, Ok(None)); } /// Test advancing to the next port when there are no events #[tokio::test] async fn test_skip_no_pending() { - let mut pending_ports = PortPending::none(); - pending_ports.pend_port(0).unwrap(); - pending_ports.pend_port(1).unwrap(); - - let mut streamer = PortEventStreamer::new(pending_ports.into_iter()); + let mut streamer = PortEventStreamer::new(0..2); let event = streamer .next::<(), _, _>(async |_| { static HAVE_EVENTS: AtomicBool = AtomicBool::new(false); @@ -348,10 +276,7 @@ mod tests { .await; assert_eq!( event, - Ok(Some(( - 1, - PortEventVariant::StatusChanged(status_changed(true, true, true)) - ))) + Ok(Some((1, PortEvent::StatusChanged(status_changed(true, true, true))))) ); let event = streamer diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index d2e4b5f61..1e1db4b18 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -1,19 +1,20 @@ use core::cell::RefCell; use core::future::pending; +use core::pin::pin; +use embassy_futures::select::select_slice; use embassy_futures::select::{Either, select}; use embedded_services::{debug, error, event::Receiver, 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::service::event::{PortEvent, PortEventData}; -use crate::{PortEventStreamer, PortEventVariant}; -use type_c_interface::port::event::{PortNotificationSingle, PortStatusChanged}; -use type_c_interface::port::{Cached, PortStatus}; +use type_c_interface::port::event::PortStatusEventBitfield; +use type_c_interface::port::{Device, PortStatus}; use type_c_interface::service::event; pub mod config; -pub mod pd; mod power; mod ucsi; pub mod vdm; @@ -60,9 +61,7 @@ pub enum PowerPolicyEvent { #[derive(Copy, Clone)] pub enum Event { /// Port event - PortStatusChanged(GlobalPortId, PortStatusChanged, PortStatus), - /// A controller notified of an event that occurred. - PortNotification(GlobalPortId, PortNotificationSingle), + PortEvent(PortEvent), /// Power policy event PowerPolicy(PowerPolicyEvent), } @@ -97,10 +96,10 @@ impl<'a> Service<'a> { } /// Process events for a specific port - async fn process_port_event( + async fn process_port_status_event( &mut self, port_id: GlobalPortId, - event: PortStatusChanged, + event: PortStatusEventBitfield, status: PortStatus, ) -> Result<(), Error> { let old_status = self.get_cached_port_status(port_id)?; @@ -132,17 +131,25 @@ impl<'a> Service<'a> { Ok(()) } + async fn process_port_event(&mut self, event: &PortEvent) -> Result<(), Error> { + match &event.event { + PortEventData::StatusChanged(status_event, status) => { + self.process_port_status_event(event.port, *status_event, *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); + Ok(()) + } + } + } + /// Process the given event pub async fn process_event(&mut self, event: Event) -> Result<(), Error> { match event { - Event::PortStatusChanged(port, event_kind, status) => { - trace!("Port{}: Processing port status changed", port.0); - self.process_port_event(port, event_kind, status).await - } - Event::PortNotification(port, notification) => { - // Other port notifications - info!("Port{}: Got port notification: {:?}", port.0, notification); - Ok(()) + Event::PortEvent(event) => { + trace!("Port{}: Processing port event", event.port.0); + self.process_port_event(&event).await } Event::PowerPolicy(event) => { trace!("Processing power policy event"); @@ -156,8 +163,6 @@ impl<'a> Service<'a> { pub struct EventReceiver<'a, PowerReceiver: Receiver> { /// Type-C context pub(crate) context: &'a type_c_interface::service::context::Context, - /// Next port to check, this is used to round-robin through ports - port_event_streaming_state: Option, /// Power policy event subscriber /// /// Used to allow partial borrows of Self for the call to select @@ -172,54 +177,31 @@ impl<'a, PowerReceiver: Receiver> EventReceiver<'a, PowerR ) -> Self { Self { context, - port_event_streaming_state: None, power_policy_event_subscriber: RefCell::new(power_policy_event_subscriber), } } /// Wait for the next event - pub async fn wait_next(&mut self) -> Result { - loop { - match select(self.wait_port_flags(), self.wait_power_policy_event()).await { - Either::First(mut stream) => { - if let Some((port_id, event)) = stream - .next(|port_id| self.context.get_port_event(GlobalPortId(port_id as u8))) - .await? - { - let port_id = GlobalPortId(port_id as u8); - self.port_event_streaming_state = Some(stream); - match event { - PortEventVariant::StatusChanged(status_event) => { - // Return a port status changed event - let status = self.context.get_port_status(port_id, Cached(true)).await?; - return Ok(Event::PortStatusChanged(port_id, status_event, status)); - } - PortEventVariant::Notification(notification) => { - // Other notifications - trace!("Port notification: {:?}", notification); - return Ok(Event::PortNotification(port_id, notification)); - } - } - } else { - self.port_event_streaming_state = None; - } - } - Either::Second(event) => return Ok(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 port flags - async fn wait_port_flags(&self) -> PortEventStreamer { - if let Some(ref streamer) = self.port_event_streaming_state { - // If we have an existing iterator, return it - // Yield first to prevent starving other tasks - embassy_futures::yield_now().await; - *streamer - } else { - // Wait for the next port event and create a streamer - PortEventStreamer::new(self.context.get_unhandled_events().await.into_iter()) - } + /// 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 diff --git a/type-c-service/src/service/pd.rs b/type-c-service/src/service/pd.rs deleted file mode 100644 index 21934fa76..000000000 --- a/type-c-service/src/service/pd.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Power Delivery (PD) related functionality. - -use embedded_usb_pd::{GlobalPortId, PdError, ado::Ado}; - -use super::Service; - -impl Service<'_> { - /// Get the oldest unhandled PD alert for the given port. - /// - /// Returns [`None`] if no alerts are pending. - pub async fn get_pd_alert(&self, port: GlobalPortId) -> Result, PdError> { - self.context.get_pd_alert(port).await - } -} diff --git a/type-c-service/src/service/ucsi.rs b/type-c-service/src/service/ucsi.rs index 62749cc89..9b75db63e 100644 --- a/type-c-service/src/service/ucsi.rs +++ b/type-c-service/src/service/ucsi.rs @@ -284,7 +284,7 @@ impl Service<'_> { pub(super) async fn handle_ucsi_port_event( &mut self, port_id: GlobalPortId, - port_event: PortStatusChanged, + port_event: PortStatusEventBitfield, port_status: &PortStatus, ) { let mut ucsi_event = ConnectorStatusChange::default(); diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index ce446aaa9..a1ad9330a 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -34,14 +34,7 @@ pub async fn task, con } loop { - let event = match event_receiver.wait_next().await { - Ok(event) => event, - Err(e) => { - error!("Error waiting for event: {:#?}", e); - continue; - } - }; - + let event = event_receiver.wait_next().await; if let Err(e) = service.lock().await.process_event(event).await { error!("Type-C service processing error: {:#?}", e); } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 92e182d34..7ee663fba 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -2,22 +2,16 @@ //! //! TODO: update this documentation when the type-C service is refactored //! -use core::array::from_fn; +use core::{array::from_fn, ops::Range}; use cfu_service::component::CfuDevice; -use embassy_sync::{ - blocking_mutex::raw::RawMutex, - mutex::Mutex, - pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}, -}; +use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::event; -use embedded_usb_pd::{GlobalPortId, ado::Ado}; -use type_c_interface::port::event::{PortEvent, PortStatusChanged}; -use type_c_interface::port::{ControllerId, PortStatus}; +use type_c_interface::port::{ControllerId, PortRegistration, PortStatus, event::PortStatusEventBitfield}; use crate::{ PortEventStreamer, @@ -28,12 +22,12 @@ use crate::{ }; /// Internal per-controller state -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct ControllerState { /// If we're currently doing a firmware update pub(crate) fw_update_state: cfu::FwUpdateState, /// State used to keep track of where we are as we turn the event bitfields into a stream of events - pub(crate) port_event_streaming_state: Option, + pub(crate) port_event_streaming_state: Option>>, } impl Default for ControllerState { @@ -59,20 +53,14 @@ impl<'a, M: RawMutex> Registration<'a, M> { } } -/// PD alerts should be fairly uncommon, four seems like a reasonable number to start with. -const MAX_BUFFERED_PD_ALERTS: usize = 4; - /// Base storage pub struct Storage<'a, const N: usize, M: RawMutex> { // Registration-related context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, - pd_ports: [GlobalPortId; N], + pd_ports: [PortRegistration; N], cfu_device: CfuDevice, power_proxy_channels: [PowerProxyChannel; N], - - // State-related - pd_alerts: [PubSubChannel; N], } impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { @@ -80,7 +68,7 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, cfu_id: ComponentId, - pd_ports: [GlobalPortId; N], + pd_ports: [PortRegistration; N], ) -> Self { Self { context, @@ -88,7 +76,6 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pd_ports, cfu_device: CfuDevice::new(cfu_id), power_proxy_channels: from_fn(|_| PowerProxyChannel::new()), - pd_alerts: [const { PubSubChannel::new() }; N], } } @@ -103,34 +90,26 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub struct Port<'a, M: RawMutex, S: event::Sender> { pub proxy: Mutex>, - pub state: Mutex>, + pub state: Mutex>, } -pub struct PortState<'a, S: event::Sender> { +pub struct PortState> { /// Cached port status pub(crate) status: PortStatus, /// Software status event - pub(crate) sw_status_event: PortStatusChanged, + pub(crate) sw_status_event: PortStatusEventBitfield, /// Sink ready deadline instant pub(crate) sink_ready_deadline: Option, - /// Pending events for the type-C service - pub(crate) pending_events: PortEvent, - /// PD alert channel for this port - // There's no direct immediate equivalent of a channel. PubSubChannel has immediate publisher behavior - // so we use that, but this requires us to keep separate publisher and subscriber objects. - pub(crate) pd_alerts: (DynImmediatePublisher<'a, Ado>, DynSubscriber<'a, Ado>), /// Sender to send events to the power policy service pub(crate) power_policy_sender: S, } -impl<'a, S: event::Sender> PortState<'a, S> { - pub fn new(pd_alerts: (DynImmediatePublisher<'a, Ado>, DynSubscriber<'a, Ado>), power_policy_sender: S) -> Self { +impl> PortState { + pub fn new(power_policy_sender: S) -> Self { Self { status: PortStatus::default(), - sw_status_event: PortStatusChanged::default(), + sw_status_event: PortStatusEventBitfield::default(), sink_ready_deadline: None, - pending_events: PortEvent::default(), - pd_alerts, power_policy_sender, } } @@ -155,21 +134,15 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender::new(); let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); - for ((power_proxy_channel, pd_alert), (name, policy_sender)) in storage - .power_proxy_channels - .iter() - .zip(storage.pd_alerts.iter()) - .zip(power_policy_init.into_iter()) + for (power_proxy_channel, (name, policy_sender)) in + storage.power_proxy_channels.iter().zip(power_policy_init.into_iter()) { let (device_sender, device_receiver) = power_proxy_channel.get_device_components(); ports .push(Port { proxy: Mutex::new(PowerProxyDevice::new(name, device_sender, device_receiver)), - state: Mutex::new(PortState::new( - (pd_alert.dyn_immediate_publisher(), pd_alert.dyn_subscriber().ok()?), - policy_sender, - )), + state: Mutex::new(PortState::new(policy_sender)), }) .ok()?; power_proxy_receivers diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index aa4c5c03a..b3bc32c51 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -3,32 +3,22 @@ use embedded_services::{GlobalRawMutex, ipc::deferred}; use embedded_usb_pd::{LocalPortId, ado::Ado}; use type_c_interface::{ - port::event::{PortNotificationSingle, PortStatusChanged}, + port::event::PortStatusEventBitfield, port::{self, DpStatus, PortStatus}, }; -/// Port status changed event data +/// Port event #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EventPortStatusChanged { +pub struct LocalPortEvent { /// Port ID pub port: LocalPortId, - /// Status changed event - pub status_event: PortStatusChanged, -} - -/// Port notification event data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EventPortNotification { - /// Port ID - pub port: LocalPortId, - /// Notification event - pub notification: PortNotificationSingle, + /// Port event + pub event: type_c_interface::port::event::PortEvent, } /// Power policy command event data -pub struct EventPowerPolicyCommand { +pub struct PowerPolicyCommand { /// Port ID pub port: LocalPortId, /// Power policy request @@ -50,11 +40,9 @@ pub enum EventCfu { /// Wrapper events pub enum Event<'a> { /// Port status changed - PortStatusChanged(EventPortStatusChanged), - /// Port notification - PortNotification(EventPortNotification), + PortEvent(LocalPortEvent), /// Power policy command received - PowerPolicyCommand(EventPowerPolicyCommand), + PowerPolicyCommand(PowerPolicyCommand), /// Command from TCPM ControllerCommand(deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>), /// Cfu event @@ -68,7 +56,7 @@ pub struct OutputPortStatusChanged { /// Port ID pub port: LocalPortId, /// Status changed event - pub status_event: PortStatusChanged, + pub status_event: PortStatusEventBitfield, /// Port status pub status: PortStatus, } @@ -101,22 +89,9 @@ pub struct OutputControllerCommand<'a> { pub mod vdm { //! Events and output for vendor-defined messaging. - use super::LocalPortId; - use type_c_interface::port::{AttnVdm, OtherVdm}; + use type_c_interface::port::event::VdmData; - /// The kind of output from processing a vendor-defined message. - #[derive(Copy, Clone, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum OutputKind { - /// Entered custom mode - Entered(OtherVdm), - /// Exited custom mode - Exited(OtherVdm), - /// Received a vendor-defined other message - ReceivedOther(OtherVdm), - /// Received a vendor-defined attention message - ReceivedAttn(AttnVdm), - } + use super::LocalPortId; /// Output from processing a vendor-defined message. #[derive(Copy, Clone, Debug)] @@ -124,9 +99,8 @@ pub mod vdm { pub struct Output { /// The port that the VDM message is associated with. pub port: LocalPortId, - - /// The kind of VDM output. - pub kind: OutputKind, + /// VDM data + pub vdm_data: VdmData, } } diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 91506384a..95ee565d7 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -17,7 +17,7 @@ //! any caching/buffering of data, and notifies the type-C service implementation of the event if needed. use core::array::from_fn; use core::future::pending; -use core::ops::DerefMut; +use core::ops::{DerefMut, Range}; use crate::wrapper::backing::{ControllerState, PortState}; use cfu_service::CfuClient; @@ -29,13 +29,15 @@ use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::event; use embedded_services::sync::Lockable; -use embedded_services::{debug, error, info, trace, warn}; +use embedded_services::{debug, error, info, trace}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; +use type_c_interface::port::event::PortEvent as InterfacePortEvent; +use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventData as ServicePortEventData}; +use crate::PortEventStreamer; use crate::wrapper::message::*; use crate::wrapper::proxy::PowerProxyReceiver; -use crate::{PortEventStreamer, PortEventVariant}; pub mod backing; mod cfu; @@ -47,7 +49,7 @@ mod power; pub mod proxy; mod vdm; -use type_c_interface::port::event::{PortEvent, PortNotificationSingle, PortPending, PortStatusChanged}; +use type_c_interface::port::event::PortStatusEventBitfield; use type_c_interface::port::{Controller, PortStatus}; /// Base interval for checking for FW update timeouts and recovery attempts @@ -174,7 +176,7 @@ where } port_state.sw_status_event = status_changed; - if port_state.sw_status_event != PortStatusChanged::none() { + if port_state.sw_status_event != PortStatusEventBitfield::none() { // Have a status changed event, notify trace!("Port{} status changed: {:#?}", i, status); self.sw_status_event.signal(()); @@ -186,7 +188,7 @@ where /// Handle a plug event async fn process_plug_event( &self, - port_state: &mut PortState<'_, S>, + port_state: &mut PortState, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Plug event"); @@ -212,7 +214,7 @@ where &self, controller: &mut D::Inner, local_port_id: LocalPortId, - status_event: PortStatusChanged, + status_event: PortStatusEventBitfield, ) -> Result, Error<::BusError>> { let global_port_id = self .registration @@ -264,7 +266,7 @@ where async fn finalize_port_status_change( &self, local_port: LocalPortId, - status_event: PortStatusChanged, + status_event: PortStatusEventBitfield, status: PortStatus, ) -> Result<(), Error<::BusError>> { let global_port_id = self @@ -273,31 +275,22 @@ where .lookup_global_port(local_port) .map_err(Error::Pd)?; - let mut port_state = self - .ports + self.ports .get(local_port.0 as usize) .ok_or(Error::Pd(PdError::InvalidPort))? .state .lock() - .await; - - let mut events = port_state.pending_events; - events.status = events.status.union(status_event); - port_state.pending_events = events; - port_state.status = status; - - if events != PortEvent::none() { - let mut pending = PortPending::none(); - pending - .pend_port(global_port_id.0 as usize) - .map_err(|_| PdError::InvalidPort)?; - self.registration - .pd_controller - .notify_ports(self.registration.context, pending); - trace!("P{}: Notified service for events: {:#?}", global_port_id.0, events); - } + .await + .status = status; - Ok(()) + self.registration + .context + .send_port_event(ServicePortEvent { + port: global_port_id, + event: ServicePortEventData::StatusChanged(status_event, status), + }) + .await + .map_err(Error::Pd) } /// Finalize a PD alert output @@ -312,29 +305,14 @@ where .lookup_global_port(local_port) .map_err(Error::Pd)?; - let mut port_state = self - .ports - .get(local_port.0 as usize) - .ok_or(Error::Pd(PdError::InvalidPort))? - .state - .lock() - .await; - - // Buffer the alert - port_state.pd_alerts.0.publish_immediate(alert); - - // Pend the alert - port_state.pending_events.notification.set_alert(true); - - // Pend this port - let mut pending = PortPending::none(); - pending - .pend_port(global_port_id.0 as usize) - .map_err(|_| PdError::InvalidPort)?; self.registration - .pd_controller - .notify_ports(self.registration.context, pending); - Ok(()) + .context + .send_port_event(ServicePortEvent { + port: global_port_id, + event: ServicePortEventData::Alert(alert), + }) + .await + .map_err(Error::Pd) } /// Wait for a pending port event @@ -344,14 +322,14 @@ where &self, controller_state: &ControllerState, controller: &mut D::Inner, - ) -> Result::BusError>> { + ) -> Result>, Error<::BusError>> { if controller_state.fw_update_state.in_progress() { // Don't process events while firmware update is in progress debug!("Firmware update in progress, ignoring port events"); return pending().await; } - let streaming_state = controller_state.port_event_streaming_state; + let streaming_state = controller_state.port_event_streaming_state.clone(); if let Some(streamer) = streaming_state { // If we're converting the bitfields into an event stream yield first to prevent starving other tasks embassy_futures::yield_now().await; @@ -368,8 +346,7 @@ where Either::First(r) => r?, Either::Second(_) => (), }; - let pending: PortPending = FromIterator::from_iter(0..self.registration.num_ports()); - Ok(PortEventStreamer::new(pending.into_iter())) + Ok(PortEventStreamer::new(0..self.registration.num_ports())) } } @@ -410,33 +387,20 @@ where // No more awaits, modify state here for drop safety let sw_event = - core::mem::replace(&mut port_state.sw_status_event, PortStatusChanged::none()); + core::mem::replace(&mut port_state.sw_status_event, PortStatusEventBitfield::none()); Ok(hw_event.union(sw_event.into())) }) .await? { let port_id = LocalPortId(port_index as u8); self.controller_state.lock().await.port_event_streaming_state = Some(stream); - match event { - PortEventVariant::StatusChanged(status_event) => { - return Ok(Event::PortStatusChanged(EventPortStatusChanged { - port: port_id, - status_event, - })); - } - PortEventVariant::Notification(notification) => { - return Ok(Event::PortNotification(EventPortNotification { - port: port_id, - notification, - })); - } - } + return Ok(Event::PortEvent(LocalPortEvent { port: port_id, event })); } else { self.controller_state.lock().await.port_event_streaming_state = None; } } Either5::Second((port, request)) => { - return Ok(Event::PowerPolicyCommand(EventPowerPolicyCommand { port, request })); + return Ok(Event::PowerPolicyCommand(PowerPolicyCommand { port, request })); } Either5::Third(request) => return Ok(Event::ControllerCommand(request)), Either5::Fourth(event) => return Ok(Event::CfuEvent(event)), @@ -450,42 +414,49 @@ where .lock() .await .sink_ready_deadline = None; - let mut status_event = PortStatusChanged::none(); + let mut status_event = PortStatusEventBitfield::none(); status_event.set_sink_ready(true); - return Ok(Event::PortStatusChanged(EventPortStatusChanged { port, status_event })); + return Ok(Event::PortEvent(LocalPortEvent { + port, + event: type_c_interface::port::event::PortEvent::StatusChanged(status_event), + })); } } } } /// Process a port notification - async fn process_port_notification<'b>( + async fn process_port_event<'b>( &self, controller: &mut D::Inner, - port: LocalPortId, - notification: PortNotificationSingle, + event: LocalPortEvent, ) -> Result, Error<::BusError>> { - match notification { - PortNotificationSingle::Alert => { - let ado = controller.get_pd_alert(port).await?; - trace!("Port{}: PD alert: {:#?}", port.0, ado); + match event.event { + InterfacePortEvent::StatusChanged(status_event) => { + self.process_port_status_changed(controller, event.port, status_event) + .await + } + InterfacePortEvent::Alert => { + let ado = controller.get_pd_alert(event.port).await?; + trace!("Port{}: PD alert: {:#?}", event.port.0, ado); if let Some(ado) = ado { - Ok(Output::PdAlert(OutputPdAlert { port, ado })) + Ok(Output::PdAlert(OutputPdAlert { port: event.port, ado })) } else { // For some reason we didn't read an alert, nothing to do Ok(Output::Nop) } } - PortNotificationSingle::Vdm(event) => { - self.process_vdm_event(controller, port, event).await.map(Output::Vdm) - } - PortNotificationSingle::DpStatusUpdate => self - .process_dp_status_update(controller, port) + InterfacePortEvent::Vdm(vdm_event) => self + .process_vdm_event(controller, event.port, vdm_event) + .await + .map(Output::Vdm), + InterfacePortEvent::DpStatusUpdate => self + .process_dp_status_update(controller, event.port) .await .map(Output::DpStatusUpdate), rest => { // Nothing currently implemented for these - trace!("Port{}: Notification: {:#?}", port.0, rest); + trace!("Port{}: Notification: {:#?}", event.port.0, rest); Ok(Output::Nop) } } @@ -500,11 +471,8 @@ where let mut controller = self.controller.lock().await; let mut controller_state = self.controller_state.lock().await; match event { - Event::PortStatusChanged(EventPortStatusChanged { port, status_event }) => { - self.process_port_status_changed(&mut controller, port, status_event) - .await - } - Event::PowerPolicyCommand(EventPowerPolicyCommand { port, request }) => { + Event::PortEvent(port_event) => self.process_port_event(&mut controller, port_event).await, + Event::PowerPolicyCommand(PowerPolicyCommand { port, request }) => { let response = self .process_power_command(&mut controller_state, &mut controller, port, &request) .await; @@ -529,10 +497,6 @@ where Ok(Output::CfuRecovery) } }, - Event::PortNotification(EventPortNotification { port, notification }) => { - self.process_port_notification(&mut controller, port, notification) - .await - } } } diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index a789f3b38..41a938d73 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,13 +1,10 @@ use crate::wrapper::backing::ControllerState; -use embassy_futures::yield_now; -use embassy_sync::pubsub::WaitResult; use embassy_time::{Duration, Timer}; use embedded_services::debug; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; use embedded_usb_pd::ucsi::{self, lpm}; use power_policy_interface::psu::{self, PsuState}; use type_c_interface::port; -use type_c_interface::port::Cached; use type_c_interface::port::{InternalResponseData, Response}; use super::*; @@ -22,24 +19,6 @@ impl< where D::Inner: Controller, { - async fn process_get_pd_alert( - &self, - port_state: &mut PortState<'_, S>, - local_port: LocalPortId, - ) -> Result, PdError> { - loop { - match port_state.pd_alerts.1.try_next_message() { - Some(WaitResult::Message(alert)) => return Ok(Some(alert)), - None => return Ok(None), - Some(WaitResult::Lagged(count)) => { - warn!("Port{}: Lagged PD alert channel: {}", local_port.0, count); - // Yield to avoid starving other tasks since we're in a loop and try_next_message isn't async - yield_now().await; - } - } - } - } - /// Check the sink ready timeout /// /// After accepting a sink contract (new contract as consumer), the PD spec guarantees that the @@ -47,7 +26,7 @@ where /// even for controllers that might not always broadcast sink ready events. pub(super) fn check_sink_ready_timeout( &self, - port_state: &mut PortState<'_, S>, + port_state: &mut PortState, status: &PortStatus, port: LocalPortId, new_contract: bool, @@ -107,7 +86,7 @@ where async fn process_set_max_sink_voltage( &self, controller: &mut D::Inner, - port_state: &mut PortState<'_, S>, + port_state: &mut PortState, state: &psu::State, local_port: LocalPortId, voltage_mv: Option, @@ -139,26 +118,6 @@ where } } - async fn process_get_port_status( - &self, - controller: &mut D::Inner, - port_state: &mut PortState<'_, S>, - local_port: LocalPortId, - cached: Cached, - ) -> Result { - if cached.0 { - Ok(port::PortResponseData::PortStatus(port_state.status)) - } else { - match controller.get_port_status(local_port).await { - Ok(status) => Ok(port::PortResponseData::PortStatus(status)), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - } - /// Handle a port command async fn process_port_command( &self, @@ -185,14 +144,6 @@ where let mut port_state = port.state.lock().await; port::Response::Port(match command.data { - port::PortCommandData::PortStatus(cached) => { - self.process_get_port_status(controller, &mut port_state, local_port, cached) - .await - } - port::PortCommandData::ClearEvents => { - let event = core::mem::take(&mut port_state.pending_events); - Ok(port::PortResponseData::ClearEvents(event)) - } port::PortCommandData::RetimerFwUpdateGetState => { match controller.get_rt_fw_update_status(local_port).await { Ok(status) => Ok(port::PortResponseData::RtFwUpdateStatus(status)), @@ -234,10 +185,6 @@ where Error::Pd(e) => Err(e), }, }, - port::PortCommandData::GetPdAlert => match self.process_get_pd_alert(&mut port_state, local_port).await { - Ok(alert) => Ok(port::PortResponseData::PdAlert(alert)), - Err(e) => Err(e), - }, port::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { match self.registration.pd_controller.lookup_local_port(command.port) { Ok(local_port) => { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index fa9c33d34..bc683ab06 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -27,7 +27,7 @@ where /// Handle a new contract as consumer pub(super) async fn process_new_consumer_contract( &self, - port_state: &mut PortState<'_, S>, + port_state: &mut PortState, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process new consumer contract"); @@ -53,7 +53,7 @@ where /// Handle a new contract as provider pub(super) async fn process_new_provider_contract( &self, - port_state: &mut PortState<'_, S>, + port_state: &mut PortState, status: &PortStatus, ) -> Result<(), Error<::BusError>> { info!("Process New provider contract"); diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 896c2b2a8..5cedbf044 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -2,10 +2,9 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, sync::Lockable, trace}; use embedded_usb_pd::{Error, LocalPortId, PdError}; -use crate::wrapper::message::vdm::OutputKind; - -use type_c_interface::port::Controller; -use type_c_interface::port::event::{PortPending, VdmNotification}; +use type_c_interface::port::event::VdmNotification; +use type_c_interface::port::{Controller, event::VdmData}; +use type_c_interface::service::event::{PortEvent, PortEventData}; use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; @@ -28,42 +27,26 @@ where ) -> Result::BusError>> { trace!("Processing VDM event: {:?} on port {}", event, port.0); let kind = match event { - VdmNotification::Entered => OutputKind::Entered(controller.get_other_vdm(port).await?), - VdmNotification::Exited => OutputKind::Exited(controller.get_other_vdm(port).await?), - VdmNotification::OtherReceived => OutputKind::ReceivedOther(controller.get_other_vdm(port).await?), - VdmNotification::AttentionReceived => OutputKind::ReceivedAttn(controller.get_attn_vdm(port).await?), + VdmNotification::Entered => VdmData::Entered(controller.get_other_vdm(port).await?), + VdmNotification::Exited => VdmData::Exited(controller.get_other_vdm(port).await?), + VdmNotification::OtherReceived => VdmData::ReceivedOther(controller.get_other_vdm(port).await?), + VdmNotification::AttentionReceived => VdmData::ReceivedAttn(controller.get_attn_vdm(port).await?), }; - Ok(Output { port, kind }) + Ok(Output { port, vdm_data: kind }) } /// Finalize a VDM output by notifying the service. pub(super) async fn finalize_vdm(&self, output: Output) -> Result<(), PdError> { trace!("Finalizing VDM output: {:?}", output); - let Output { port, kind } = output; + let Output { port, vdm_data } = output; let global_port_id = self.registration.pd_controller.lookup_global_port(port)?; - let mut port_state = self - .ports - .get(port.0 as usize) - .ok_or(PdError::InvalidPort)? - .state - .lock() - .await; - let notification = &mut port_state.pending_events.notification; - match kind { - OutputKind::Entered(_) => notification.set_custom_mode_entered(true), - OutputKind::Exited(_) => notification.set_custom_mode_exited(true), - OutputKind::ReceivedOther(_) => notification.set_custom_mode_other_vdm_received(true), - OutputKind::ReceivedAttn(_) => notification.set_custom_mode_attention_received(true), - } - - let mut pending = PortPending::none(); - pending - .pend_port(global_port_id.0 as usize) - .map_err(|_| PdError::InvalidPort)?; self.registration - .pd_controller - .notify_ports(self.registration.context, pending); - Ok(()) + .context + .send_port_event(PortEvent { + port: global_port_id, + event: PortEventData::Vdm(vdm_data), + }) + .await } } From 01bc3655b3afe01f7d8c92ed855f7befad571e52 Mon Sep 17 00:00:00 2001 From: Billy Price <45800072+williampMSFT@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:14:35 -0700 Subject: [PATCH 62/79] Uptake latest embassy-imxrt (#788) This change uptakes the latest version of embassy-imxrt. This brings with it a couple updates to dependencies: - Bumped minimum Rust version to 1.90 - necessary because embedded-mcu-hal v0.2.0 requires 1.90 - imposed new clippy warnings around if-let chains that also needed to be fixed - embedded-mcu-hal updated to v0.2.0 - Included some breaking changes to the traits (moved namespaces, changed function names) - embassy-executor updated from v0.9 to 0.10 - `Spawner::must_spawn` removed in favor of having the spawn token function return a `Result` that you need to `unwrap` or similar - Platform feature named changed from `arch-` to `platform-` - embassy-sync updated to 0.8.0 --- Cargo.lock | 124 +++--- Cargo.toml | 6 +- embedded-service/src/comms.rs | 8 +- examples/pico-de-gallo/Cargo.lock | 39 +- examples/pico-de-gallo/src/bin/battery.rs | 5 +- examples/rt633/Cargo.lock | 353 +++++++++++++++-- examples/rt633/Cargo.toml | 8 +- examples/rt685s-evk/Cargo.lock | 359 ++++++++++++++++-- examples/rt685s-evk/Cargo.toml | 10 +- examples/rt685s-evk/src/bin/keyboard.rs | 4 +- .../rt685s-evk/src/bin/mock_espi_service.rs | 4 +- examples/rt685s-evk/src/bin/power_button.rs | 4 +- examples/rt685s-evk/src/bin/reset.rs | 2 +- examples/rt685s-evk/src/bin/time_alarm.rs | 6 +- examples/rt685s-evk/src/bin/transport.rs | 4 +- examples/rt685s-evk/src/bin/type_c.rs | 36 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 38 +- examples/std/Cargo.lock | 80 ++-- examples/std/Cargo.toml | 6 +- examples/std/src/bin/battery.rs | 11 +- examples/std/src/bin/buffer.rs | 2 +- examples/std/src/bin/cfu_buffer.rs | 8 +- examples/std/src/bin/cfu_client.rs | 8 +- examples/std/src/bin/cfu_splitter.rs | 10 +- examples/std/src/bin/debug.rs | 10 +- examples/std/src/bin/hid.rs | 4 +- examples/std/src/bin/init.rs | 4 +- examples/std/src/bin/keyboard.rs | 6 +- examples/std/src/bin/power_policy.rs | 25 +- examples/std/src/bin/thermal.rs | 8 +- examples/std/src/bin/type_c/basic.rs | 4 +- examples/std/src/bin/type_c/service.rs | 12 +- examples/std/src/bin/type_c/ucsi.rs | 16 +- examples/std/src/bin/type_c/unconstrained.rs | 16 +- odp-service-common/src/runnable_service.rs | 2 +- .../partition-manager/src/ext/bdd.rs | 2 +- .../partition-manager/src/test/mock.rs | 6 +- power-policy-service/src/service/consumer.rs | 8 +- rust-toolchain.toml | 2 +- supply-chain/audits.toml | 35 ++ supply-chain/config.toml | 16 +- supply-chain/imports.lock | 6 + .../src/acpi_timestamp.rs | 4 +- time-alarm-service/src/lib.rs | 18 +- time-alarm-service/src/mock.rs | 22 +- time-alarm-service/src/timer.rs | 39 +- time-alarm-service/tests/tad_test.rs | 12 +- 47 files changed, 1064 insertions(+), 348 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd3439e05..64b015e9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,7 +412,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", - "heapless", + "heapless 0.8.0", "log", ] @@ -633,8 +633,8 @@ checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ "defmt 0.3.100", "device-driver-macros", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", ] [[package]] @@ -683,12 +683,12 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-embedded-hal" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" dependencies = [ "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.4.0", "embassy-sync", "embassy-time", "embedded-hal 0.2.7", @@ -701,10 +701,11 @@ dependencies = [ [[package]] name = "embassy-executor" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" dependencies = [ + "cordyceps", "critical-section", "defmt 1.0.1", "document-features", @@ -714,9 +715,9 @@ dependencies = [ [[package]] name = "embassy-executor-macros" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" dependencies = [ "darling", "proc-macro2", @@ -748,10 +749,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "embassy-hal-internal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" +dependencies = [ + "num-traits", +] + [[package]] name = "embassy-imxrt" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#fbf7df43734625332214634188b21883af114115" +source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d95ffe560071e942d917b422bbdea7e73f6c4602" dependencies = [ "cfg-if", "cortex-m", @@ -761,7 +771,7 @@ dependencies = [ "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.3.0", "embassy-sync", "embassy-time", "embassy-time-driver", @@ -770,8 +780,8 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-hal-nb", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-mcu-hal", "embedded-storage", "fixed", @@ -787,25 +797,25 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", "defmt 1.0.1", - "embedded-io-async", + "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless", + "heapless 0.9.2", "log", ] [[package]] name = "embassy-time" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", @@ -822,9 +832,9 @@ dependencies = [ [[package]] name = "embassy-time-driver" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" dependencies = [ "document-features", ] @@ -836,7 +846,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -883,7 +893,7 @@ version = "0.2.0" source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" dependencies = [ "defmt 0.3.100", - "embedded-io-async", + "embedded-io-async 0.6.1", "log", ] @@ -956,19 +966,35 @@ dependencies = [ "defmt 0.3.100", ] +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ - "embedded-io", + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", ] [[package]] name = "embedded-mcu-hal" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" dependencies = [ "defmt 1.0.1", "num_enum", @@ -1013,10 +1039,10 @@ dependencies = [ "embassy-time-driver", "embedded-cfu-protocol", "embedded-hal-async", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "log", "mctp-rs", "num_enum", @@ -1047,7 +1073,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce99a6d91032d7c9792fd87d4b4f49b6f" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#1a8e79d3a2ac0d2837a34b045087cf0863146f7d" dependencies = [ "aquamarine", "bincode", @@ -1241,6 +1267,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -1389,7 +1425,7 @@ dependencies = [ "arraydeque", "either", "embedded-hal 1.0.0", - "heapless", + "heapless 0.8.0", "keyberon-macros", "usb-device", ] @@ -1822,7 +1858,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", - "heapless", + "heapless 0.8.0", "log", ] @@ -1862,7 +1898,7 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless", + "heapless 0.8.0", "log", "num_enum", ] @@ -1880,7 +1916,7 @@ dependencies = [ "embedded-batteries-async", "embedded-services", "env_logger", - "heapless", + "heapless 0.8.0", "log", "num_enum", "power-policy-interface", @@ -2216,7 +2252,7 @@ dependencies = [ [[package]] name = "storage_bus" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#528045934782abc704531aa423169c75016e7727" [[package]] name = "strsim" @@ -2275,7 +2311,7 @@ dependencies = [ "embedded-fans-async", "embedded-sensors-hal-async", "embedded-services", - "heapless", + "heapless 0.8.0", "log", "odp-service-common", "thermal-service-interface", @@ -2461,7 +2497,7 @@ dependencies = [ [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x#af2073995aff1e2e9302fc030ad26e10785008fb" +source = "git+https://github.com/OpenDevicePartnership/tps6699x#aa3425136216dccfb107bff6b172a49b8972bb70" dependencies = [ "bincode", "bitfield 0.19.2", @@ -2471,9 +2507,9 @@ dependencies = [ "embassy-time", "embedded-hal 1.0.0", "embedded-hal-async", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "itertools 0.14.0", "log", ] @@ -2569,10 +2605,10 @@ dependencies = [ "embedded-cfu-protocol", "embedded-hal 1.0.0", "embedded-hal-async", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-services", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "log", "power-policy-interface", "static_cell", @@ -2596,7 +2632,7 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-services", "log", "mctp-rs", @@ -2639,7 +2675,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" dependencies = [ - "heapless", + "heapless 0.8.0", "portable-atomic", ] diff --git a/Cargo.toml b/Cargo.toml index ffa634e5d..a495148ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,10 +71,10 @@ critical-section = "1.1" defmt = "0.3" document-features = "0.2.7" debug-service-messages = { path = "./debug-service-messages" } -embassy-executor = "0.9.1" +embassy-executor = "0.10.0" embassy-futures = "0.1.2" embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" } -embassy-sync = "0.7.2" +embassy-sync = "0.8.0" embassy-time = "0.5.0" embassy-time-driver = "0.2.1" embedded-batteries-async = "0.3.4" @@ -85,7 +85,7 @@ embedded-hal-async = "1.0" embedded-hal-nb = "1.0" embedded-io = "0.6.1" embedded-io-async = "0.6.1" -embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } +embedded-mcu-hal = "0.2.0" embedded-services = { path = "./embedded-service" } embedded-storage = "0.3" embedded-storage-async = "0.4.1" diff --git a/embedded-service/src/comms.rs b/embedded-service/src/comms.rs index b2e606771..dd0df20e8 100644 --- a/embedded-service/src/comms.rs +++ b/embedded-service/src/comms.rs @@ -313,10 +313,10 @@ async fn route(message: Message<'_>) -> Result<(), Infallible> { let list = get_list(message.to).get().await; for rxq in list { - if let Some(endpoint) = rxq.data::() { - if message.to == endpoint.id { - endpoint.process(&message); - } + if let Some(endpoint) = rxq.data::() + && message.to == endpoint.id + { + endpoint.process(&message); } } diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index e32f34de6..d062f4ae2 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -396,7 +396,7 @@ checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ "device-driver-macros", "embedded-io 0.6.1", - "embedded-io-async", + "embedded-io-async 0.6.1", ] [[package]] @@ -457,16 +457,16 @@ checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" [[package]] name = "embassy-sync" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "embedded-io-async", + "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless 0.8.0", + "heapless 0.9.2", "log", ] @@ -535,7 +535,7 @@ name = "embedded-cfu-protocol" version = "0.2.0" source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" dependencies = [ - "embedded-io-async", + "embedded-io-async 0.6.1", "log", ] @@ -582,6 +582,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" @@ -591,6 +597,15 @@ dependencies = [ "embedded-io 0.6.1", ] +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", +] + [[package]] name = "embedded-services" version = "0.1.0" @@ -608,7 +623,7 @@ dependencies = [ "embedded-cfu-protocol", "embedded-hal-async", "embedded-io 0.6.1", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-usb-pd", "heapless 0.8.0", "log", @@ -791,6 +806,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" diff --git a/examples/pico-de-gallo/src/bin/battery.rs b/examples/pico-de-gallo/src/bin/battery.rs index b4d165c0e..feaa0d0a7 100644 --- a/examples/pico-de-gallo/src/bin/battery.rs +++ b/examples/pico-de-gallo/src/bin/battery.rs @@ -227,8 +227,8 @@ pub async fn run_app(battery_service: battery_service::Service<'static, 1>) { let mut count: usize = 1; loop { tokio::time::sleep(std::time::Duration::from_secs(1)).await; - if count.is_multiple_of(const { 60 * 60 * 60 }) { - if let Err(e) = battery_service + if count.is_multiple_of(const { 60 * 60 * 60 }) + && let Err(e) = battery_service .execute_event(battery_service::context::BatteryEvent { event: battery_service::context::BatteryEventInner::PollStaticData, device_id: bs::device::DeviceId(0), @@ -238,7 +238,6 @@ pub async fn run_app(battery_service: battery_service::Service<'static, 1>) { failures += 1; embedded_services::error!("Fuel gauge static data error: {:#?}", e); } - } if let Err(e) = battery_service .execute_event(battery_service::context::BatteryEvent { event: battery_service::context::BatteryEventInner::PollDynamicData, diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 56cef1eb1..5c766bf10 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "aquamarine" version = "0.6.0" @@ -199,12 +208,32 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "cortex-m" version = "0.7.7" @@ -342,8 +371,8 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", ] [[package]] @@ -363,12 +392,12 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-embedded-hal" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" dependencies = [ "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.4.0", "embassy-sync", "embassy-time", "embedded-hal 0.2.7", @@ -381,10 +410,11 @@ dependencies = [ [[package]] name = "embassy-executor" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" dependencies = [ + "cordyceps", "cortex-m", "critical-section", "defmt 1.0.1", @@ -395,9 +425,9 @@ dependencies = [ [[package]] name = "embassy-executor-macros" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" dependencies = [ "darling", "proc-macro2", @@ -429,10 +459,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "embassy-hal-internal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" +dependencies = [ + "num-traits", +] + [[package]] name = "embassy-imxrt" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d13994d875c29342b86bef374b44b9f401b44917" +source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d95ffe560071e942d917b422bbdea7e73f6c4602" dependencies = [ "cfg-if", "cortex-m", @@ -442,7 +481,7 @@ dependencies = [ "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.3.0", "embassy-sync", "embassy-time", "embassy-time-driver", @@ -451,8 +490,8 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-hal-nb", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-mcu-hal", "embedded-storage", "fixed", @@ -468,24 +507,24 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", "defmt 1.0.1", - "embedded-io-async", + "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless", + "heapless 0.9.2", ] [[package]] name = "embassy-time" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", @@ -500,9 +539,9 @@ dependencies = [ [[package]] name = "embassy-time-driver" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" dependencies = [ "document-features", ] @@ -514,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -561,7 +600,7 @@ version = "0.2.0" source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" dependencies = [ "defmt 0.3.100", - "embedded-io-async", + "embedded-io-async 0.6.1", ] [[package]] @@ -611,19 +650,35 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ - "embedded-io", + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", ] [[package]] name = "embedded-mcu-hal" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" dependencies = [ "defmt 1.0.1", "num_enum", @@ -646,10 +701,10 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-hal-async", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "mctp-rs", "num_enum", "paste", @@ -716,6 +771,12 @@ dependencies = [ "odp-service-common", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fixed" version = "1.30.0" @@ -812,6 +873,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "half" version = "2.7.1" @@ -842,6 +918,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -891,12 +977,52 @@ dependencies = [ "either", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "mctp-rs" version = "0.1.0" @@ -911,6 +1037,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + [[package]] name = "mimxrt600-fcb" version = "0.2.2" @@ -961,6 +1093,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -999,6 +1140,12 @@ dependencies = [ "static_cell", ] +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "panic-probe" version = "0.3.2" @@ -1037,7 +1184,7 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless", + "heapless 0.8.0", "num_enum", ] @@ -1108,6 +1255,23 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "rt633-examples" version = "0.1.0" @@ -1152,6 +1316,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "semver" version = "0.9.0" @@ -1197,6 +1367,27 @@ dependencies = [ "syn", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "smbus-pec" version = "1.0.1" @@ -1230,7 +1421,7 @@ dependencies = [ [[package]] name = "storage_bus" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#528045934782abc704531aa423169c75016e7727" [[package]] name = "strsim" @@ -1287,6 +1478,76 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "typenum" version = "1.19.0" @@ -1311,6 +1572,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcell" version = "0.1.3" @@ -1332,6 +1599,30 @@ dependencies = [ "vcell", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/examples/rt633/Cargo.toml b/examples/rt633/Cargo.toml index cd9682e82..4f553eb5f 100644 --- a/examples/rt633/Cargo.toml +++ b/examples/rt633/Cargo.toml @@ -29,9 +29,9 @@ embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" "unstable-pac", ] } -embassy-sync = { version = "0.7.2", features = ["defmt"] } -embassy-executor = { version = "0.9.1", features = [ - "arch-cortex-m", +embassy-sync = { version = "0.8.0", features = ["defmt"] } +embassy-executor = { version = "0.10.0", features = [ + "platform-cortex-m", "executor-thread", "executor-interrupt", "defmt", @@ -58,7 +58,7 @@ bq25773 = { git = "https://github.com/OpenDevicePartnership/bq25773" } battery-service = { path = "../../battery-service", features = ["defmt"] } bq40z50-rx = { version = "0.8", features = ["r5"] } static_cell = "2.1.0" -embassy-embedded-hal = { version = "0.5.0", default-features = false } +embassy-embedded-hal = { version = "0.6.0", default-features = false } # Needed otherwise cargo will pull from git [patch."https://github.com/OpenDevicePartnership/embedded-services"] diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 1f227317e..96083761a 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -14,6 +14,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -221,6 +230,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -237,7 +256,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -249,6 +268,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "cortex-m" version = "0.7.7" @@ -412,8 +441,8 @@ checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ "defmt 0.3.100", "device-driver-macros", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", ] [[package]] @@ -462,13 +491,13 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-embedded-hal" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" dependencies = [ "defmt 1.0.1", "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.4.0", "embassy-sync", "embassy-time", "embedded-hal 0.2.7", @@ -481,10 +510,11 @@ dependencies = [ [[package]] name = "embassy-executor" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" dependencies = [ + "cordyceps", "cortex-m", "critical-section", "defmt 1.0.1", @@ -495,9 +525,9 @@ dependencies = [ [[package]] name = "embassy-executor-macros" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" dependencies = [ "darling", "proc-macro2", @@ -529,10 +559,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "embassy-hal-internal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" +dependencies = [ + "num-traits", +] + [[package]] name = "embassy-imxrt" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d13994d875c29342b86bef374b44b9f401b44917" +source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d95ffe560071e942d917b422bbdea7e73f6c4602" dependencies = [ "cfg-if", "cortex-m", @@ -542,7 +581,7 @@ dependencies = [ "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-hal-internal", + "embassy-hal-internal 0.3.0", "embassy-sync", "embassy-time", "embassy-time-driver", @@ -551,8 +590,8 @@ dependencies = [ "embedded-hal 1.0.0", "embedded-hal-async", "embedded-hal-nb", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-mcu-hal", "embedded-storage", "fixed", @@ -568,24 +607,24 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", "defmt 1.0.1", - "embedded-io-async", + "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless", + "heapless 0.9.2", ] [[package]] name = "embassy-time" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", @@ -600,9 +639,9 @@ dependencies = [ [[package]] name = "embassy-time-driver" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" dependencies = [ "document-features", ] @@ -614,7 +653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -659,7 +698,7 @@ version = "0.2.0" source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" dependencies = [ "defmt 0.3.100", - "embedded-io-async", + "embedded-io-async 0.6.1", ] [[package]] @@ -712,19 +751,35 @@ dependencies = [ "defmt 0.3.100", ] +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ - "embedded-io", + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", ] [[package]] name = "embedded-mcu-hal" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" dependencies = [ "defmt 1.0.1", "num_enum", @@ -747,10 +802,10 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-hal-async", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "mctp-rs", "num_enum", "paste", @@ -777,7 +832,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce99a6d91032d7c9792fd87d4b4f49b6f" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#1a8e79d3a2ac0d2837a34b045087cf0863146f7d" dependencies = [ "aquamarine", "bincode", @@ -808,6 +863,12 @@ dependencies = [ "subenum", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fixed" version = "1.29.0" @@ -905,6 +966,21 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "half" version = "2.6.0" @@ -952,6 +1028,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -1028,12 +1114,52 @@ dependencies = [ "winnow 0.6.24", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + [[package]] name = "litrs" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "mctp-rs" version = "0.1.0" @@ -1135,6 +1261,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "num" version = "0.4.3" @@ -1289,7 +1424,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -1318,7 +1453,7 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless", + "heapless 0.8.0", "num_enum", ] @@ -1333,7 +1468,7 @@ dependencies = [ "embassy-time", "embedded-batteries-async", "embedded-services", - "heapless", + "heapless 0.8.0", "num_enum", "power-policy-interface", ] @@ -1390,6 +1525,23 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "rt685s-evk-example" version = "0.1.0" @@ -1457,6 +1609,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "semver" version = "0.9.0" @@ -1504,6 +1662,27 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "smbus-pec" version = "1.0.1" @@ -1537,7 +1716,7 @@ dependencies = [ [[package]] name = "storage_bus" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#4dadf27b212ce91d884e9a33b3c2725d5d0511fb" +source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#528045934782abc704531aa423169c75016e7727" [[package]] name = "strsim" @@ -1625,6 +1804,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time-alarm-service" version = "0.1.0" @@ -1667,7 +1855,7 @@ dependencies = [ [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x#af2073995aff1e2e9302fc030ad26e10785008fb" +source = "git+https://github.com/OpenDevicePartnership/tps6699x#aa3425136216dccfb107bff6b172a49b8972bb70" dependencies = [ "bincode", "bitfield 0.19.2", @@ -1677,12 +1865,73 @@ dependencies = [ "embassy-time", "embedded-hal 1.0.0", "embedded-hal-async", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "itertools 0.14.0", ] +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "type-c-interface" version = "0.1.0" @@ -1710,10 +1959,10 @@ dependencies = [ "embedded-cfu-protocol", "embedded-hal 1.0.0", "embedded-hal-async", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-services", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "power-policy-interface", "tps6699x", "type-c-interface", @@ -1755,6 +2004,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcell" version = "0.1.3" @@ -1788,6 +2043,30 @@ dependencies = [ "vcell", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "winnow" version = "0.6.24" diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index e0277d38c..82b168f79 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -29,11 +29,11 @@ embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" "mimxrt685s", ] } -embassy-embedded-hal = { version = "0.5.0", features = ["defmt"] } +embassy-embedded-hal = { version = "0.6.0", features = ["defmt"] } -embassy-sync = { version = "0.7.2", features = ["defmt"] } -embassy-executor = { version = "0.9.1", features = [ - "arch-cortex-m", +embassy-sync = { version = "0.8.0", features = ["defmt"] } +embassy-executor = { version = "0.10.0", features = [ + "platform-cortex-m", "executor-thread", "executor-interrupt", "defmt", @@ -86,7 +86,7 @@ platform-service = { path = "../../platform-service", features = [ "defmt", "imxrt685", ] } -embedded-mcu-hal = { git = "https://github.com/OpenDevicePartnership/embedded-mcu" } +embedded-mcu-hal = "0.2.0" cfu-service = { path = "../../cfu-service", features = ["defmt"] } diff --git a/examples/rt685s-evk/src/bin/keyboard.rs b/examples/rt685s-evk/src/bin/keyboard.rs index 6271414a4..5363bb549 100644 --- a/examples/rt685s-evk/src/bin/keyboard.rs +++ b/examples/rt685s-evk/src/bin/keyboard.rs @@ -150,10 +150,10 @@ async fn main(spawner: Spawner) { info!("Service initialization complete..."); // create an activity service subscriber - spawner.spawn(activity_example::backlight::task()).unwrap(); + spawner.spawn(activity_example::backlight::task().expect("Failed to create backlight task")); // create an activity service publisher - spawner.spawn(activity_example::publisher::keyboard_task()).unwrap(); + spawner.spawn(activity_example::publisher::keyboard_task().expect("Failed to create keyboard task")); info!("Subsystem initialization complete..."); diff --git a/examples/rt685s-evk/src/bin/mock_espi_service.rs b/examples/rt685s-evk/src/bin/mock_espi_service.rs index 4b20e12e1..e52dfaa25 100644 --- a/examples/rt685s-evk/src/bin/mock_espi_service.rs +++ b/examples/rt685s-evk/src/bin/mock_espi_service.rs @@ -182,9 +182,9 @@ async fn main(spawner: Spawner) { info!("Service initialization complete..."); - spawner.spawn(espi_service::espi_service()).unwrap(); + spawner.spawn(espi_service::espi_service().expect("Failed to create espi service task")); - spawner.spawn(battery_service::battery_service_task()).unwrap(); + spawner.spawn(battery_service::battery_service_task().expect("Failed to create battery service task")); info!("Subsystem initialization complete..."); } diff --git a/examples/rt685s-evk/src/bin/power_button.rs b/examples/rt685s-evk/src/bin/power_button.rs index fe7a8c9a1..489b631df 100644 --- a/examples/rt685s-evk/src/bin/power_button.rs +++ b/examples/rt685s-evk/src/bin/power_button.rs @@ -129,8 +129,8 @@ async fn main(spawner: Spawner) { let config_b = ButtonConfig::default(); // Spawn the button tasks - spawner.must_spawn(button_task(button_a, config_a)); - spawner.must_spawn(button_task(button_b, config_b)); + spawner.spawn(button_task(button_a, config_a).expect("Failed to spawn button task")); + spawner.spawn(button_task(button_b, config_b).expect("Failed to spawn button task")); static RECEIVER: StaticCell = StaticCell::new(); let receiver = RECEIVER.init(receiver::Receiver::new()); diff --git a/examples/rt685s-evk/src/bin/reset.rs b/examples/rt685s-evk/src/bin/reset.rs index ea1569aa5..2ff3da3fb 100644 --- a/examples/rt685s-evk/src/bin/reset.rs +++ b/examples/rt685s-evk/src/bin/reset.rs @@ -50,7 +50,7 @@ async fn main(spawner: embassy_executor::Spawner) { // when immediately calling reset below blocker.register().expect("Infallible"); - spawner.must_spawn(reset_watcher(blocker)); + spawner.spawn(reset_watcher(blocker).expect("Failed to spawn reset watcher task")); } // perform reset diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 5f8c0343e..8673d9096 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -2,8 +2,8 @@ #![no_main] use embedded_mcu_hal::{ - Nvram, - time::{Datetime, Month, UncheckedDatetime}, + nvram::Nvram, + time::{Datetime, DatetimeFields, Month}, }; use embedded_services::info; use static_cell::StaticCell; @@ -58,7 +58,7 @@ async fn main(spawner: embassy_executor::Spawner) { // time_service .set_real_time(AcpiTimestamp { - datetime: Datetime::new(UncheckedDatetime { + datetime: Datetime::new(DatetimeFields { year: 2024, month: Month::January, day: 10, diff --git a/examples/rt685s-evk/src/bin/transport.rs b/examples/rt685s-evk/src/bin/transport.rs index cefeb5a7a..abd725ba3 100644 --- a/examples/rt685s-evk/src/bin/transport.rs +++ b/examples/rt685s-evk/src/bin/transport.rs @@ -156,8 +156,8 @@ async fn main(spawner: Spawner) { info!("Service initialization complete..."); - spawner.spawn(simple_example::receiver()).unwrap(); - spawner.spawn(simple_example::sender()).unwrap(); + spawner.spawn(simple_example::receiver().expect("Failed to create receiver task")); + spawner.spawn(simple_example::sender().expect("Failed to create sender task")); info!("Subsystem initialization complete..."); diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 5c0bb0ced..a81152a3e 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -148,7 +148,7 @@ async fn main(spawner: Spawner) { tps6699x.reset(&mut delay).await.unwrap(); info!("Spawining interrupt task"); - spawner.must_spawn(interrupt_task(int_in, interrupt)); + spawner.spawn(interrupt_task(int_in, interrupt).expect("Failed to spawn interrupt task")); // These aren't enabled by default tps6699x @@ -263,21 +263,27 @@ async fn main(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task( - type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper], - cfu_client, - )); + spawner.spawn( + type_c_service_task( + type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), + [wrapper], + cfu_client, + ) + .expect("Failed to create type-c service task"), + ); info!("Spawining power policy task"); - spawner.must_spawn(power_policy_task( - ArrayEventReceivers::new( - [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], - [policy_receiver0, policy_receiver1], - ), - power_service, - )); + spawner.spawn( + power_policy_task( + ArrayEventReceivers::new( + [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + [policy_receiver0, policy_receiver1], + ), + power_service, + ) + .expect("Failed to create power policy task"), + ); - spawner.must_spawn(pd_controller_task(wrapper)); + spawner.spawn(pd_controller_task(wrapper).expect("Failed to create pd controller task")); } diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index a3c43cccf..6a56ccab8 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -232,7 +232,7 @@ async fn main(spawner: Spawner) { tps6699x.reset(&mut delay).await.unwrap(); info!("Spawining interrupt task"); - spawner.must_spawn(interrupt_task(int_in, interrupt)); + spawner.spawn(interrupt_task(int_in, interrupt).expect("Failed to spawn interrupt task")); // These aren't enabled by default tps6699x @@ -351,23 +351,29 @@ async fn main(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; info!("Spawining type-c service task"); - spawner.must_spawn(type_c_service_task( - type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper], - cfu_client, - )); + spawner.spawn( + type_c_service_task( + type_c_service, + EventReceiver::new(controller_context, power_policy_subscriber), + [wrapper], + cfu_client, + ) + .expect("Failed to spawn type-c service task"), + ); info!("Spawining power policy task"); - spawner.must_spawn(power_policy_task( - ArrayEventReceivers::new( - [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], - [policy_receiver0, policy_receiver1], - ), - power_service, - )); + spawner.spawn( + power_policy_task( + ArrayEventReceivers::new( + [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + [policy_receiver0, policy_receiver1], + ), + power_service, + ) + .expect("Failed to create power policy task"), + ); - spawner.must_spawn(pd_controller_task(wrapper)); + spawner.spawn(pd_controller_task(wrapper).expect("Failed to create pd controller task")); - spawner.must_spawn(fw_update_task()); + spawner.spawn(fw_update_task().expect("Failed to create fw update task")); } diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 2f007e166..f3e4b7277 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -320,7 +320,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", - "heapless", + "heapless 0.8.0", "log", ] @@ -488,8 +488,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ "device-driver-macros", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", ] [[package]] @@ -538,10 +538,11 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-executor" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" dependencies = [ + "cordyceps", "critical-section", "document-features", "embassy-executor-macros", @@ -551,9 +552,9 @@ dependencies = [ [[package]] name = "embassy-executor-macros" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" dependencies = [ "darling", "proc-macro2", @@ -575,16 +576,16 @@ checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" [[package]] name = "embassy-sync" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "embedded-io-async", + "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless", + "heapless 0.9.2", "log", ] @@ -622,7 +623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -653,7 +654,7 @@ name = "embedded-cfu-protocol" version = "0.2.0" source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#a4cc8707842b878048447abbf2af4efa79fed368" dependencies = [ - "embedded-io-async", + "embedded-io-async 0.6.1", "log", ] @@ -734,13 +735,28 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ - "embedded-io", + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", ] [[package]] @@ -775,10 +791,10 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-hal-async", - "embedded-io", - "embedded-io-async", + "embedded-io 0.6.1", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "log", "mctp-rs", "num_enum", @@ -800,7 +816,7 @@ dependencies = [ [[package]] name = "embedded-usb-pd" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce99a6d91032d7c9792fd87d4b4f49b6f" +source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#1a8e79d3a2ac0d2837a34b045087cf0863146f7d" dependencies = [ "aquamarine", "bincode", @@ -934,6 +950,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -1377,7 +1403,7 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless", + "heapless 0.8.0", "log", "num_enum", ] @@ -1392,7 +1418,7 @@ dependencies = [ "embassy-time", "embedded-batteries-async", "embedded-services", - "heapless", + "heapless 0.8.0", "log", "num_enum", "power-policy-interface", @@ -1649,7 +1675,7 @@ dependencies = [ "embedded-services", "embedded-usb-pd", "env_logger", - "heapless", + "heapless 0.8.0", "log", "odp-service-common", "power-policy-interface", @@ -1717,7 +1743,7 @@ dependencies = [ "embedded-fans-async", "embedded-sensors-hal-async", "embedded-services", - "heapless", + "heapless 0.8.0", "log", "odp-service-common", "thermal-service-interface", @@ -1784,7 +1810,7 @@ dependencies = [ [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x#af2073995aff1e2e9302fc030ad26e10785008fb" +source = "git+https://github.com/OpenDevicePartnership/tps6699x#aa3425136216dccfb107bff6b172a49b8972bb70" dependencies = [ "bincode", "bitfield 0.19.2", @@ -1793,9 +1819,9 @@ dependencies = [ "embassy-time", "embedded-hal 1.0.0", "embedded-hal-async", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "itertools 0.14.0", "log", ] @@ -1887,10 +1913,10 @@ dependencies = [ "embedded-cfu-protocol", "embedded-hal 1.0.0", "embedded-hal-async", - "embedded-io-async", + "embedded-io-async 0.6.1", "embedded-services", "embedded-usb-pd", - "heapless", + "heapless 0.8.0", "log", "power-policy-interface", "tps6699x", diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index dd60ffb6c..f963cac75 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -12,12 +12,12 @@ warnings = "deny" workspace = true [dependencies] -embassy-sync = { version = "0.7.2", features = ["log", "std"] } +embassy-sync = { version = "0.8.0", features = ["log", "std"] } embassy-time-driver = { version = "0.2.1", optional = true } embassy-time = { version = "0.5.0", features = ["log", "std"] } embassy-futures = "0.1.2" -embassy-executor = { version = "0.9.1", features = [ - "arch-std", +embassy-executor = { version = "0.10.0", features = [ + "platform-std", "executor-thread", "log", ] } diff --git a/examples/std/src/bin/battery.rs b/examples/std/src/bin/battery.rs index 0e3ea6f29..938c1eaef 100644 --- a/examples/std/src/bin/battery.rs +++ b/examples/std/src/bin/battery.rs @@ -38,8 +38,8 @@ async fn embassy_main(spawner: Spawner) { battery_wrapper.process().await } - spawner.must_spawn(battery_wrapper_process(wrapper)); - spawner.must_spawn(run_app(battery_service)); + spawner.spawn(battery_wrapper_process(wrapper).expect("Failed to create battery wrapper task")); + spawner.spawn(run_app(battery_service).expect("Failed to create run_app task")); } #[embassy_executor::task] @@ -59,8 +59,8 @@ pub async fn run_app(battery_service: battery_service::Service<'static, 1>) { let mut count: usize = 1; loop { Timer::after(Duration::from_secs(1)).await; - if count.is_multiple_of(const { 60 * 60 * 60 }) { - if let Err(e) = battery_service + if count.is_multiple_of(const { 60 * 60 * 60 }) + && let Err(e) = battery_service .execute_event(battery_service::context::BatteryEvent { event: battery_service::context::BatteryEventInner::PollStaticData, device_id: bs::device::DeviceId(0), @@ -70,7 +70,6 @@ pub async fn run_app(battery_service: battery_service::Service<'static, 1>) { failures += 1; embedded_services::error!("Fuel gauge static data error: {:#?}", e); } - } if let Err(e) = battery_service .execute_event(battery_service::context::BatteryEvent { event: battery_service::context::BatteryEventInner::PollDynamicData, @@ -104,6 +103,6 @@ fn main() { let executor = EXECUTOR.init(Executor::new()); // Run battery service executor.run(|spawner| { - spawner.must_spawn(embassy_main(spawner)); + spawner.spawn(embassy_main(spawner).expect("Failed to create embassy_main task")); }); } diff --git a/examples/std/src/bin/buffer.rs b/examples/std/src/bin/buffer.rs index 1d1b14a9f..ae8ace7de 100644 --- a/examples/std/src/bin/buffer.rs +++ b/examples/std/src/bin/buffer.rs @@ -109,6 +109,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.spawn(task()).unwrap(); + spawner.spawn(task().expect("Failed to create task")); }); } diff --git a/examples/std/src/bin/cfu_buffer.rs b/examples/std/src/bin/cfu_buffer.rs index 4186ec84d..1769e3967 100644 --- a/examples/std/src/bin/cfu_buffer.rs +++ b/examples/std/src/bin/cfu_buffer.rs @@ -160,7 +160,7 @@ async fn run(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(cfu_service_task(cfu_client)); + spawner.spawn(cfu_service_task(cfu_client).expect("Failed to create cfu service task")); info!("Creating device 0"); static DEVICE0: OnceLock = OnceLock::new(); @@ -175,7 +175,7 @@ async fn run(spawner: Spawner) { ) }); cfu_client.register_device(device0).unwrap(); - spawner.must_spawn(device_task(device0)); + spawner.spawn(device_task(device0).expect("Failed to create device task")); info!("Creating buffer"); static BUFFER: OnceLock> = OnceLock::new(); @@ -192,7 +192,7 @@ async fn run(spawner: Spawner) { ) }); buffer.register(cfu_client).unwrap(); - spawner.must_spawn(buffer_task(buffer, cfu_client)); + spawner.spawn(buffer_task(buffer, cfu_client).expect("Failed to create buffer task")); info!("Getting FW version"); let response = cfu_client @@ -261,6 +261,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(run(spawner)); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/cfu_client.rs b/examples/std/src/bin/cfu_client.rs index 1c51f2b4f..b57a6d961 100644 --- a/examples/std/src/bin/cfu_client.rs +++ b/examples/std/src/bin/cfu_client.rs @@ -38,7 +38,7 @@ async fn run(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(cfu_service_task(cfu_client)); + spawner.spawn(cfu_service_task(cfu_client).expect("Failed to create cfu service task")); info!("Creating device 0"); static DEVICE0: OnceLock> = OnceLock::new(); @@ -46,14 +46,14 @@ async fn run(spawner: Spawner) { subs[0] = Some(2); let device0 = DEVICE0.get_or_init(|| CfuComponentDefault::new(1, true, subs, CfuWriterNop {})); cfu_client.register_device(device0).unwrap(); - spawner.must_spawn(device_task0(device0, cfu_client)); + spawner.spawn(device_task0(device0, cfu_client).expect("Failed to create device_task0")); info!("Creating device 1"); static DEVICE1: OnceLock> = OnceLock::new(); let device1 = DEVICE1.get_or_init(|| CfuComponentDefault::new(2, false, [None; MAX_SUBCMPT_COUNT], CfuWriterNop {})); cfu_client.register_device(device1).unwrap(); - spawner.must_spawn(device_task1(device1, cfu_client)); + spawner.spawn(device_task1(device1, cfu_client).expect("Failed to create device_task1")); let dummy_offer0 = FwUpdateOffer::new( HostToken::Driver, @@ -108,6 +108,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(run(spawner)); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/cfu_splitter.rs b/examples/std/src/bin/cfu_splitter.rs index 506e49426..f77502ea0 100644 --- a/examples/std/src/bin/cfu_splitter.rs +++ b/examples/std/src/bin/cfu_splitter.rs @@ -182,7 +182,7 @@ async fn run(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(cfu_service_task(cfu_client)); + spawner.spawn(cfu_service_task(cfu_client).expect("Failed to create cfu service task")); info!("Creating device 0"); static DEVICE0: OnceLock = OnceLock::new(); @@ -197,7 +197,7 @@ async fn run(spawner: Spawner) { ) }); cfu_client.register_device(device0).unwrap(); - spawner.must_spawn(device_task(device0)); + spawner.spawn(device_task(device0).expect("Failed to create device0 task")); info!("Creating device 1"); static DEVICE1: OnceLock = OnceLock::new(); @@ -212,7 +212,7 @@ async fn run(spawner: Spawner) { ) }); cfu_client.register_device(device1).unwrap(); - spawner.must_spawn(device_task(device1)); + spawner.spawn(device_task(device1).expect("Failed to create device1 task")); info!("Creating splitter"); static SPLITTER: OnceLock> = OnceLock::new(); @@ -220,7 +220,7 @@ async fn run(spawner: Spawner) { let customization = mock::Customization {}; let splitter = SPLITTER.get_or_init(|| splitter::Splitter::new(CFU_SPLITTER_ID, &DEVICES, customization).unwrap()); splitter.register(cfu_client).unwrap(); - spawner.must_spawn(splitter_task(splitter, cfu_client)); + spawner.spawn(splitter_task(splitter, cfu_client).expect("Failed to create splitter task")); info!("Getting FW version"); let response = cfu_client @@ -283,6 +283,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(run(spawner)); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/debug.rs b/examples/std/src/bin/debug.rs index 20e927c69..04e3277c3 100644 --- a/examples/std/src/bin/debug.rs +++ b/examples/std/src/bin/debug.rs @@ -167,15 +167,15 @@ async fn init_task(spawner: Spawner) { info!("init espi service"); espi_service::init().await; // Spawn eSPI request task to drive the OOB request/response flow - spawner.must_spawn(espi_service::request_task()); + spawner.spawn(espi_service::request_task().expect("Failed to create espi request task")); info!("spawn debug service"); - spawner.must_spawn(debug_service()); + spawner.spawn(debug_service().expect("Failed to create debug service task")); info!("spawn defmt_to_host_task"); - spawner.must_spawn(defmt_to_host_task()); + spawner.spawn(defmt_to_host_task().expect("Failed to create defmt_to_host task")); - spawner.must_spawn(defmt_frames_task()); + spawner.spawn(defmt_frames_task().expect("Failed to create defmt_frames task")); } #[embassy_executor::task] @@ -198,6 +198,6 @@ fn main() { executor.run(|spawner| { // Spawn debug-service tasks and mock eSPI service - spawner.must_spawn(init_task(spawner)); + spawner.spawn(init_task(spawner).expect("Failed to create init task")); }); } diff --git a/examples/std/src/bin/hid.rs b/examples/std/src/bin/hid.rs index 1f2060f4e..bba0ca9e2 100644 --- a/examples/std/src/bin/hid.rs +++ b/examples/std/src/bin/hid.rs @@ -78,13 +78,13 @@ async fn run(spawner: Spawner) { let dev1 = DEVICE1.get_or_init(|| Device::new(DEV1_ID)); comms::register_endpoint(dev1, &dev1.tp).await.unwrap(); info!("Spawning host task"); - spawner.spawn(host()).unwrap(); + spawner.spawn(host().expect("Failed to create host task")); } static EXECUTOR: StaticCell = StaticCell::new(); fn main() { env_logger::builder().filter_level(log::LevelFilter::Info).init(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.spawn(run(spawner)).unwrap(); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/init.rs b/examples/std/src/bin/init.rs index 882d316bb..97764e66d 100644 --- a/examples/std/src/bin/init.rs +++ b/examples/std/src/bin/init.rs @@ -25,7 +25,7 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(registration_waiter()); - spawner.must_spawn(registration_task()); + spawner.spawn(registration_waiter().expect("Failed to create registration_waiter task")); + spawner.spawn(registration_task().expect("Failed to create registration task")); }); } diff --git a/examples/std/src/bin/keyboard.rs b/examples/std/src/bin/keyboard.rs index 5f107096e..14e221c13 100644 --- a/examples/std/src/bin/keyboard.rs +++ b/examples/std/src/bin/keyboard.rs @@ -139,8 +139,8 @@ async fn run(spawner: Spawner) { keyboard::enable_broadcast_host().await; - spawner.must_spawn(host()); - spawner.must_spawn(device()); + spawner.spawn(host().expect("Failed to create host task")); + spawner.spawn(device().expect("Failed to create device task")); } fn main() { @@ -149,6 +149,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(run(spawner)); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 6cf7e51fe..410cddb15 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -150,16 +150,19 @@ async fn run(spawner: Spawner) { power_policy_service::service::config::Config::default(), ))); - spawner.must_spawn(power_policy_task( - ArrayEventReceivers::new( - [device0, device1], - [ - device0_event_channel.dyn_receiver(), - device1_event_channel.dyn_receiver(), - ], - ), - service, - )); + spawner.spawn( + power_policy_task( + ArrayEventReceivers::new( + [device0, device1], + [ + device0_event_channel.dyn_receiver(), + device1_event_channel.dyn_receiver(), + ], + ), + service, + ) + .expect("Failed to create power policy task"), + ); // Plug in device 0, should become current consumer info!("Connecting device 0"); @@ -301,6 +304,6 @@ fn main() { let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(run(spawner)); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index fa3075494..d17faffeb 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -76,8 +76,10 @@ async fn run(spawner: Spawner) { let resources = RESOURCES.init(ts::Resources::default()); let thermal_service = ts::Service::init(resources, ts::InitParams { sensors, fans }); - spawner.must_spawn(monitor(thermal_service)); - spawner.must_spawn(sensor_event_listener(sensor_event_channel.receiver())); + spawner.spawn(monitor(thermal_service).expect("Failed to create monitor task")); + spawner.spawn( + sensor_event_listener(sensor_event_channel.receiver()).expect("Failed to create sensor event listener task"), + ); } fn main() { @@ -86,7 +88,7 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(run(spawner)); + spawner.spawn(run(spawner).expect("Failed to create run task")); }); } diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index eedcb6629..c278f8ac6 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -138,7 +138,7 @@ async fn task(spawner: Spawner) { let controller_context = CONTROLLER_CONTEXT.init(Context::new()); info!("Starting controller task"); - spawner.must_spawn(controller_task(controller_context)); + spawner.spawn(controller_task(controller_context).expect("Failed to create controller task")); // Wait for controller to be registered Timer::after_secs(1).await; @@ -154,6 +154,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.spawn(task(spawner)).unwrap(); + 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 0cb956ac8..4f395a8e0 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -129,17 +129,17 @@ async fn task(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(power_policy_task( + spawner.spawn(power_policy_task( ArrayEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), power_service, - )); - spawner.must_spawn(type_c_service_task( + ).expect("Failed to create power policy task")); + spawner.spawn(type_c_service_task( type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper], cfu_client, - )); - spawner.must_spawn(controller_task(wrapper, controller)); + ).expect("Failed to create type-c service task")); + spawner.spawn(controller_task(wrapper, controller).expect("Failed to create controller task")); Timer::after_millis(1000).await; info!("Simulating connection"); @@ -266,6 +266,6 @@ fn main() { let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(task(spawner)); + spawner.spawn(task(spawner).expect("Failed to create task")); }); } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 01d35c304..04c3c9ec0 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -403,23 +403,23 @@ async fn task(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(power_policy_task( + spawner.spawn(power_policy_task( ArrayEventReceivers::new( [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], [policy_receiver0, policy_receiver1], ), power_service, - )); + ).expect("Failed to create power policy task")); - spawner.must_spawn(type_c_service_task( + spawner.spawn(type_c_service_task( type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper0, wrapper1], cfu_client, - )); - spawner.must_spawn(wrapper_task(wrapper0)); - spawner.must_spawn(wrapper_task(wrapper1)); - spawner.must_spawn(opm_task(controller_context, [state0, state1])); + ).expect("Failed to create type-c service task")); + spawner.spawn(wrapper_task(wrapper0).expect("Failed to create wrapper0 task")); + spawner.spawn(wrapper_task(wrapper1).expect("Failed to create wrapper1 task")); + spawner.spawn(opm_task(controller_context, [state0, state1]).expect("Failed to create opm task")); } fn main() { @@ -429,6 +429,6 @@ fn main() { let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(task(spawner)); + spawner.spawn(task(spawner).expect("Failed to create task")); }); } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 0052f730e..0e323904f 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -259,7 +259,7 @@ async fn task(spawner: Spawner) { static CFU_CLIENT: OnceLock = OnceLock::new(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.must_spawn(power_policy_task( + spawner.spawn(power_policy_task( ArrayEventReceivers::new( [ &wrapper0.ports[0].proxy, @@ -269,17 +269,17 @@ async fn task(spawner: Spawner) { [policy_receiver0, policy_receiver1, policy_receiver2], ), power_service, - )); - spawner.must_spawn(type_c_service_task( + ).expect("Failed to create power policy task")); + spawner.spawn(type_c_service_task( type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper0, wrapper1, wrapper2], cfu_client, - )); + ).expect("Failed to create type-c service task")); - spawner.must_spawn(controller_task(wrapper0)); - spawner.must_spawn(controller_task(wrapper1)); - spawner.must_spawn(controller_task(wrapper2)); + spawner.spawn(controller_task(wrapper0).expect("Failed to create controller0 task")); + spawner.spawn(controller_task(wrapper1).expect("Failed to create controller1 task")); + spawner.spawn(controller_task(wrapper2).expect("Failed to create controller2 task")); const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, @@ -355,6 +355,6 @@ fn main() { static EXECUTOR: StaticCell = StaticCell::new(); let executor = EXECUTOR.init(Executor::new()); executor.run(|spawner| { - spawner.must_spawn(task(spawner)); + spawner.spawn(task(spawner).expect("Failed to create task")); }); } diff --git a/odp-service-common/src/runnable_service.rs b/odp-service-common/src/runnable_service.rs index d65100249..149b2aed6 100644 --- a/odp-service-common/src/runnable_service.rs +++ b/odp-service-common/src/runnable_service.rs @@ -74,7 +74,7 @@ macro_rules! spawn_service { <$service_ty>::new(service_resources, $init_arg) .await .map(|(control_handle, runner)| { - $spawner.must_spawn(service_task_fn(runner)); + $spawner.spawn(service_task_fn(runner).expect("Failed to spawn service task")); control_handle }) }}; diff --git a/partition-manager/partition-manager/src/ext/bdd.rs b/partition-manager/partition-manager/src/ext/bdd.rs index 2ee3ce89d..f36cdc835 100644 --- a/partition-manager/partition-manager/src/ext/bdd.rs +++ b/partition-manager/partition-manager/src/ext/bdd.rs @@ -11,7 +11,7 @@ impl Partition<'_, F, MARKER, M> { /// /// Will not be able to return a value of the partition is not aligned to a single block. const fn start_block(&self, block_size: u32) -> Option { - if self.offset % block_size != 0 { + if !self.offset.is_multiple_of(block_size) { None } else { Some(self.offset / block_size) diff --git a/partition-manager/partition-manager/src/test/mock.rs b/partition-manager/partition-manager/src/test/mock.rs index 3c77273f8..ba1a5d99e 100644 --- a/partition-manager/partition-manager/src/test/mock.rs +++ b/partition-manager/partition-manager/src/test/mock.rs @@ -155,7 +155,7 @@ pub mod esa { const READ_SIZE: usize = 4; async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - if (offset as usize) % Self::READ_SIZE != 0 || bytes.len() % Self::READ_SIZE != 0 { + if !(offset as usize).is_multiple_of(Self::READ_SIZE) || !bytes.len().is_multiple_of(Self::READ_SIZE) { return Err(Error::NotAligned); } @@ -175,7 +175,7 @@ pub mod esa { async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { let len = to.checked_sub(from).unwrap(); - if (from as usize) % Self::ERASE_SIZE != 0 || len as usize % Self::ERASE_SIZE != 0 { + if !(from as usize).is_multiple_of(Self::ERASE_SIZE) || !(len as usize).is_multiple_of(Self::ERASE_SIZE) { return Err(Error::NotAligned); } @@ -184,7 +184,7 @@ pub mod esa { } async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - if (offset as usize) % Self::WRITE_SIZE != 0 || bytes.len() % Self::WRITE_SIZE != 0 { + if !(offset as usize).is_multiple_of(Self::WRITE_SIZE) || !bytes.len().is_multiple_of(Self::WRITE_SIZE) { return Err(Error::NotAligned); } diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index b52b0e2b7..f9fcf5118 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -102,10 +102,10 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); for psu in self.registration.psus() { - if let Some(capability) = psu.lock().await.state().consumer_capability { - if capability.flags.unconstrained_power() { - unconstrained_new.available += 1; - } + if let Some(capability) = psu.lock().await.state().consumer_capability + && capability.flags.unconstrained_power() + { + unconstrained_new.available += 1; } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6863a00f6..400d2c8cb 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.88" +channel = "1.90" targets = ["thumbv8m.main-none-eabihf"] components = ["rust-src", "rustfmt", "llvm-tools-preview", "clippy"] diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 8991824d9..0e128269e 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -72,6 +72,36 @@ who = "Jerry Xie " criteria = "safe-to-deploy" version = "1.0.0" +[[audits.embassy-embedded-hal]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.5.0 -> 0.6.0" + +[[audits.embassy-executor]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.9.1 -> 0.10.0" + +[[audits.embassy-executor-macros]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.7.0 -> 0.8.0" + +[[audits.embassy-hal-internal]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.3.0 -> 0.4.0" + +[[audits.embassy-time]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.5.0 -> 0.5.1" + +[[audits.embassy-time-driver]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.2.1 -> 0.2.2" + [[audits.embedded-batteries]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -165,6 +195,11 @@ criteria = "safe-to-deploy" version = "0.2.0" notes = "ODP crates are always trusted." +[[audits.embedded-mcu-hal]] +who = "Billy Price " +criteria = "safe-to-deploy" +version = "0.2.0" + [[audits.embedded-sensors-hal]] who = "Kurtis Dinelle " criteria = "safe-to-deploy" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index b76513c3d..82286d315 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -208,8 +208,8 @@ criteria = "safe-to-deploy" version = "0.7.2" criteria = "safe-to-deploy" -[[exemptions.embassy-time]] -version = "0.5.0" +[[exemptions.embassy-sync]] +version = "0.8.0" criteria = "safe-to-deploy" [[exemptions.embassy-time-driver]] @@ -236,10 +236,18 @@ criteria = "safe-to-deploy" version = "1.0.0" criteria = "safe-to-deploy" +[[exemptions.embedded-io]] +version = "0.7.1" +criteria = "safe-to-deploy" + [[exemptions.embedded-io-async]] version = "0.6.1" criteria = "safe-to-deploy" +[[exemptions.embedded-io-async]] +version = "0.7.0" +criteria = "safe-to-deploy" + [[exemptions.embedded-storage]] version = "0.3.1" criteria = "safe-to-deploy" @@ -276,6 +284,10 @@ criteria = "safe-to-deploy" version = "0.8.0" criteria = "safe-to-deploy" +[[exemptions.heapless]] +version = "0.9.2" +criteria = "safe-to-deploy" + [[exemptions.indexmap]] version = "2.11.0" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 94299bee0..82b08b650 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -300,6 +300,12 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" +[[audits.OpenDevicePartnership.audits.embassy-time]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.5.0" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/tps6699x/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.windows-sys]] who = "Felipe Balbi " criteria = "safe-to-run" diff --git a/time-alarm-service-interface/src/acpi_timestamp.rs b/time-alarm-service-interface/src/acpi_timestamp.rs index cc29abe70..d554bc4c8 100644 --- a/time-alarm-service-interface/src/acpi_timestamp.rs +++ b/time-alarm-service-interface/src/acpi_timestamp.rs @@ -1,4 +1,4 @@ -use embedded_mcu_hal::time::{Datetime, DatetimeClockError, Month, UncheckedDatetime}; +use embedded_mcu_hal::time::{Datetime, DatetimeClockError, DatetimeFields, Month}; use zerocopy::{FromBytes, I16, Immutable, IntoBytes, KnownLayout, LE, U16, Unaligned}; // Timestamp structure as specified in the ACPI spec. Must be exactly this layout. @@ -167,7 +167,7 @@ impl AcpiTimestamp { .map_err(|_| DatetimeClockError::Unknown)?; Ok(Self { - datetime: Datetime::new(UncheckedDatetime { + datetime: Datetime::new(DatetimeFields { year: raw.year.get(), month: Month::try_from(raw.month).map_err(|_| DatetimeClockError::Unknown)?, day: raw.day, diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 08afc019e..7fbbc51a9 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -3,7 +3,7 @@ use core::cell::RefCell; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::signal::Signal; -use embedded_mcu_hal::NvramStorage; +use embedded_mcu_hal::nvram::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; use embedded_services::GlobalRawMutex; use embedded_services::{info, warn}; @@ -167,7 +167,7 @@ impl<'hw> ServiceInner<'hw> { fn get_real_time(&self) -> Result { self.clock_state.lock(|clock_state| { let clock_state = clock_state.borrow(); - let datetime = clock_state.datetime_clock.get_current_datetime()?; + let datetime = clock_state.datetime_clock.now()?; let (time_zone, dst_status) = clock_state.tz_data.get_data(); Ok(AcpiTimestamp { datetime, @@ -181,7 +181,7 @@ impl<'hw> ServiceInner<'hw> { fn set_real_time(&self, timestamp: AcpiTimestamp) -> Result<(), DatetimeClockError> { self.clock_state.lock(|clock_state| { let mut clock_state = clock_state.borrow_mut(); - clock_state.datetime_clock.set_current_datetime(×tamp.datetime)?; + clock_state.datetime_clock.set(timestamp.datetime)?; clock_state.tz_data.set_data(timestamp.time_zone, timestamp.dst_status); Ok(()) }) @@ -221,10 +221,10 @@ impl<'hw> ServiceInner<'hw> { AlarmTimerSeconds(secs) => { let current_time = self .clock_state - .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; + .lock(|clock_state| clock_state.borrow().datetime_clock.now())?; - Some(Datetime::from_unix_time_seconds( - current_time.to_unix_time_seconds() + u64::from(secs), + Some(Datetime::from_unix_timestamp( + current_time.unix_timestamp() + u64::from(secs), )) } }; @@ -242,12 +242,12 @@ impl<'hw> ServiceInner<'hw> { Some(expiration_time) => { let current_time = self .clock_state - .lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime())?; + .lock(|clock_state| clock_state.borrow().datetime_clock.now())?; Ok(AlarmTimerSeconds( expiration_time - .to_unix_time_seconds() - .saturating_sub(current_time.to_unix_time_seconds()) as u32, + .unix_timestamp() + .saturating_sub(current_time.unix_timestamp()) as u32, )) } None => Ok(AlarmTimerSeconds::DISABLED), diff --git a/time-alarm-service/src/mock.rs b/time-alarm-service/src/mock.rs index 2bc9f3086..3bb2bd02d 100644 --- a/time-alarm-service/src/mock.rs +++ b/time-alarm-service/src/mock.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] // We have some functionality in these mocks that isn't used yet but will be in future tests. -use embedded_mcu_hal::NvramStorage; +use embedded_mcu_hal::nvram::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError}; // Used for `cargo test` runs in an std environment @@ -15,7 +15,7 @@ fn now_seconds() -> u64 { } // Allows us to use this mock in no_std contexts -// Note: `get_current_datetime` will always reflect time as starting from the beginning +// Note: `now` will always reflect time as starting from the beginning // of UNIX time (1970), and not current wall-clock time. This is sufficient for its use case // since it provides a consistent baseline and allows time to advance, but is something to be aware of. #[cfg(not(test))] @@ -37,7 +37,7 @@ impl MockDatetimeClock { /// New `MockDatetimeClock` in which time is paused. pub fn new_paused() -> Self { Self::Paused { - frozen_time: Datetime::from_unix_time_seconds(now_seconds()), + frozen_time: Datetime::from_unix_timestamp(now_seconds()), } } @@ -47,7 +47,7 @@ impl MockDatetimeClock { *self = MockDatetimeClock::Paused { // Panic safety: Mocks aren't used in production code, so panicking is acceptable here #[allow(clippy::unwrap_used)] - frozen_time: self.get_current_datetime().unwrap(), + frozen_time: self.now().unwrap(), }; } } @@ -55,7 +55,7 @@ impl MockDatetimeClock { /// Resume time advancing. pub fn resume(&mut self) { if let Self::Paused { frozen_time } = self { - let target_secs = frozen_time.to_unix_time_seconds() as i64; + let target_secs = frozen_time.unix_timestamp() as i64; *self = MockDatetimeClock::Running { seconds_offset: target_secs - now_seconds() as i64, }; @@ -64,24 +64,24 @@ impl MockDatetimeClock { } impl DatetimeClock for MockDatetimeClock { - fn get_current_datetime(&self) -> Result { + fn now(&self) -> Result { match self { MockDatetimeClock::Paused { frozen_time } => Ok(*frozen_time), MockDatetimeClock::Running { seconds_offset } => { let adjusted_seconds = now_seconds() as i64 + seconds_offset; - Ok(Datetime::from_unix_time_seconds(adjusted_seconds as u64)) + Ok(Datetime::from_unix_timestamp(adjusted_seconds as u64)) } } } - fn set_current_datetime(&mut self, datetime: &Datetime) -> Result<(), DatetimeClockError> { + fn set(&mut self, datetime: Datetime) -> Result<(), DatetimeClockError> { match self { MockDatetimeClock::Paused { .. } => { - *self = MockDatetimeClock::Paused { frozen_time: *datetime }; + *self = MockDatetimeClock::Paused { frozen_time: datetime }; Ok(()) } MockDatetimeClock::Running { .. } => { - let target_secs = datetime.to_unix_time_seconds() as i64; + let target_secs = datetime.unix_timestamp() as i64; *self = MockDatetimeClock::Running { seconds_offset: target_secs - now_seconds() as i64, }; @@ -90,7 +90,7 @@ impl DatetimeClock for MockDatetimeClock { } } - fn max_resolution_hz(&self) -> u32 { + fn resolution_hz(&self) -> u32 { 1 } } diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs index 05840d464..27034e5e5 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -2,7 +2,7 @@ use crate::{AlarmExpiredWakePolicy, ClockState, TimerStatus}; use core::cell::RefCell; use embassy_futures::select::{Either, select}; use embassy_sync::{blocking_mutex::Mutex, signal::Signal}; -use embedded_mcu_hal::NvramStorage; +use embedded_mcu_hal::nvram::NvramStorage; use embedded_mcu_hal::time::{Datetime, DatetimeClockError}; use embedded_services::{GlobalRawMutex, error}; @@ -24,8 +24,8 @@ enum WakeState { } mod persistent_storage { + use crate::NvramStorage; use crate::{AlarmExpiredWakePolicy, Datetime}; - use embedded_mcu_hal::NvramStorage; pub struct PersistentStorage<'hw> { /// When the timer is programmed to expire, or None if the timer is not set @@ -61,7 +61,7 @@ mod persistent_storage { pub fn get_expiration_time(&self) -> Option { match self.expiration_time_storage.read() { Self::NO_EXPIRATION_TIME => None, - secs => Some(Datetime::from_unix_time_seconds(secs.into())), + secs => Some(Datetime::from_unix_timestamp(secs.into())), } } @@ -69,7 +69,7 @@ mod persistent_storage { match expiration_time { Some(dt) => { // This won't overflow until 2106, which is acceptable for our use case. - self.expiration_time_storage.write(dt.to_unix_time_seconds() as u32); + self.expiration_time_storage.write(dt.unix_timestamp() as u32); } None => { self.expiration_time_storage.write(Self::NO_EXPIRATION_TIME); @@ -163,8 +163,7 @@ impl<'hw> Timer<'hw> { self.timer_state.lock(|timer_state| { let mut timer_state = timer_state.borrow_mut(); if let WakeState::ExpiredWaitingForPolicyDelay(_, _) = timer_state.wake_state { - timer_state.wake_state = - WakeState::ExpiredWaitingForPolicyDelay(Self::get_current_datetime(clock_state)?, 0); + timer_state.wake_state = WakeState::ExpiredWaitingForPolicyDelay(Self::now(clock_state)?, 0); self.timer_signal.signal(Some(wake_policy.0)); } @@ -189,9 +188,8 @@ impl<'hw> Timer<'hw> { Some(dt) => { // Note: If the expiration time was in the past, this will immediately trigger the timer to expire. self.timer_signal.signal(Some( - dt.to_unix_time_seconds() - .saturating_sub(Self::get_current_datetime(clock_state)?.to_unix_time_seconds()) - as u32, // The ACPI spec doesn't provide a facility to program a timer more than u32::MAX seconds in the future, so this cast is safe + dt.unix_timestamp() + .saturating_sub(Self::now(clock_state)?.unix_timestamp()) as u32, // The ACPI spec doesn't provide a facility to program a timer more than u32::MAX seconds in the future, so this cast is safe )); timer_state.persistent_storage.set_expiration_time(expiration_time); @@ -222,7 +220,7 @@ impl<'hw> Timer<'hw> { if !was_active { if let WakeState::ExpiredWaitingForPowerSource(seconds_already_elapsed) = timer_state.wake_state { - match Self::get_current_datetime(clock_state) { + match Self::now(clock_state) { Ok(now) => { timer_state.wake_state = WakeState::ExpiredWaitingForPolicyDelay(now, seconds_already_elapsed); @@ -250,12 +248,12 @@ impl<'hw> Timer<'hw> { } else if let WakeState::ExpiredWaitingForPolicyDelay(wait_start_time, seconds_elapsed_before_wait) = timer_state.wake_state { - let total_seconds_elapsed_on_policy_delay = match Self::get_current_datetime(clock_state) { + let total_seconds_elapsed_on_policy_delay = match Self::now(clock_state) { Ok(now) => { seconds_elapsed_before_wait + (now - .to_unix_time_seconds() - .saturating_sub(wait_start_time.to_unix_time_seconds()) + .unix_timestamp() + .saturating_sub(wait_start_time.unix_timestamp()) as u32) // The ACPI spec expresses timeouts in terms of u32s - it's impossible to schedule a timer u32::MAX seconds in the future } Err(_) => { @@ -332,16 +330,13 @@ impl<'hw> Timer<'hw> { } }; - match Self::get_current_datetime(clock_state) { + match Self::now(clock_state) { Ok(now) => { - if now.to_unix_time_seconds() < expiration_time.to_unix_time_seconds() { + if now.unix_timestamp() < expiration_time.unix_timestamp() { // Time hasn't actually passed the mark yet - this can happen if we were reprogrammed with a different time right as the old timer was expiring. Reset the timer. timer_state.wake_state = WakeState::Armed; self.timer_signal.signal(Some( - expiration_time - .to_unix_time_seconds() - .saturating_sub(now.to_unix_time_seconds()) - as u32, + expiration_time.unix_timestamp().saturating_sub(now.unix_timestamp()) as u32, )); return false; } @@ -392,9 +387,7 @@ impl<'hw> Timer<'hw> { self.timer_signal.signal(None); } - fn get_current_datetime( - clock_state: &Mutex>>, - ) -> Result { - clock_state.lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime()) + fn now(clock_state: &Mutex>>) -> Result { + clock_state.lock(|clock_state| clock_state.borrow().datetime_clock.now()) } } diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index 14359395e..a9d1bed35 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -53,8 +53,8 @@ mod test { Timer::after(embassy_time::Duration::from_millis(delay_secs * 1000)).await; let end = service.get_real_time().unwrap(); println!("Current time from service after delay: {end:?}"); - assert!(end.datetime.to_unix_time_seconds() - begin.datetime.to_unix_time_seconds() <= delay_secs + 1); - assert!(end.datetime.to_unix_time_seconds() - begin.datetime.to_unix_time_seconds() >= delay_secs - 1); + assert!(end.datetime.unix_timestamp() - begin.datetime.unix_timestamp() <= delay_secs + 1); + assert!(end.datetime.unix_timestamp() - begin.datetime.unix_timestamp() >= delay_secs - 1); } => {} } } @@ -69,9 +69,7 @@ mod test { let mut clock = MockDatetimeClock::new_paused(); const TEST_UNIX_TIME: u64 = 1_234_567_890; - clock - .set_current_datetime(&Datetime::from_unix_time_seconds(TEST_UNIX_TIME)) - .unwrap(); + clock.set(Datetime::from_unix_timestamp(TEST_UNIX_TIME)).unwrap(); let mut storage = Default::default(); @@ -94,10 +92,10 @@ mod test { _ = async { // Clock is paused, so time shouldn't advance unless we set it. let begin = service.get_real_time().unwrap(); - assert_eq!(begin.datetime.to_unix_time_seconds(), TEST_UNIX_TIME); + assert_eq!(begin.datetime.unix_timestamp(), TEST_UNIX_TIME); let target_timestamp = AcpiTimestamp { - datetime: Datetime::from_unix_time_seconds(TEST_UNIX_TIME), + datetime: Datetime::from_unix_timestamp(TEST_UNIX_TIME), time_zone: AcpiTimeZone::Unknown, dst_status: AcpiDaylightSavingsTimeStatus::Adjusted, }; From 83646b1194684dc6b1b550bb2d93bcc346b15cab Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:06:11 -0700 Subject: [PATCH 63/79] power-policy-service/tests: Add tests for disconnecting other PSU (#794) A bug was found in main where disconnecting any PSU would disconnect the current consumer instead. Add tests for this. --- power-policy-service/tests/consumer.rs | 198 +++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 62c4bccc8..4626349ff 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -4,6 +4,8 @@ use embassy_sync::signal::Signal; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::info; +use power_policy_interface::capability::ProviderFlags; +use power_policy_interface::capability::ProviderPowerCapability; use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability}; mod common; @@ -16,6 +18,8 @@ use crate::common::DeviceType; use crate::common::MINIMAL_POWER; use crate::common::Test; use crate::common::assert_no_event; +use crate::common::assert_provider_connected; +use crate::common::assert_provider_disconnected; use crate::common::{ DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, run_test, }; @@ -356,6 +360,190 @@ impl Test for TestDisconnect { } } +/// Test a disconnect initiated by a consumer other than the current consumer. +struct TestDisconnectOtherConsumer; + +impl Test for TestDisconnectOtherConsumer { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_disconnect_other_consumer"); + // Device0 connection at high power + { + device0 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Device1 connection at low power + { + device1 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + device1_signal.reset(); + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + // Test disconnect device1, should have no effect on device0 + { + device1.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + assert_no_event(service_receiver); + } +} + +/// Test a disconnect initiated by a provider other than the current consumer. +struct TestDisconnectOtherProvider; + +impl Test for TestDisconnectOtherProvider { + async fn run<'a>( + &mut self, + service: &ServiceMutex<'a, 'a>, + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, + ) { + info!("Running test_disconnect_other_provider"); + // Device0 connection at high power + { + device0 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + // Ensure consumer change doesn't affect provider power computation + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + // Device1 connect as provider + { + device1.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 7500); + } + + // Test disconnect device1, should have no effect on device0 + { + device1.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_provider_disconnected(service_receiver, device1).await; + assert_eq!(service.lock().await.compute_total_provider_power_mw().await, 0); + } + + assert_no_event(service_receiver); + } +} + /// Test minimum consumer power logic. /// /// Config for this test uses [`MIN_CONSUMER_THRESHOLD_MW`]. @@ -487,6 +675,16 @@ async fn run_test_disconnect() { run_test(DEFAULT_TIMEOUT, TestDisconnect, Default::default()).await; } +#[tokio::test] +async fn run_test_disconnect_other_consumer() { + run_test(DEFAULT_TIMEOUT, TestDisconnectOtherConsumer, Default::default()).await; +} + +#[tokio::test] +async fn run_test_disconnect_other_provider() { + run_test(DEFAULT_TIMEOUT, TestDisconnectOtherProvider, Default::default()).await; +} + #[tokio::test] async fn run_test_min_consumer_power() { run_test( From 4087e0edd3af19be23b89b00454eae7a99173fe3 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:09:28 -0700 Subject: [PATCH 64/79] .github: Pull-in shared copilot instructions (#810) --- .github/copilot-instructions.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..43d8860fe --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,9 @@ +# Rust PR Review Instructions +CI overview: +* CI will build the project and run `cargo test` and `cargo clippy`. +* Feature combinations are checked with `cargo hack`. +* Do not comment on compile errors, compiler warnings, or clippy warnings. + +Pay special attention to... +* code that uses async selection APIs such as `select`, `selectN`, `select_array`, `select_slice`, or is marked with a drop safety comment. These functions drop the futures that don't finish. Check that values are not lost when this happens. +* code that could possibly panic or is marked with a panic safety comment. \ No newline at end of file From 40d33a16fcb11557d470edf94c1dc0fb5c0edb1d Mon Sep 17 00:00:00 2001 From: matteotullo Date: Tue, 21 Apr 2026 12:09:40 -0700 Subject: [PATCH 65/79] Fixes on the mergeback --- Cargo.lock | 163 +- Cargo.toml | 2 +- battery-service-interface/Cargo.toml | 3 + battery-service-relay/Cargo.toml | 4 +- cfu-service/src/splitter.rs | 18 - embedded-service/Cargo.toml | 26 +- espi-service/Cargo.toml | 4 +- examples/pico-de-gallo/Cargo.lock | 136 +- examples/pico-de-gallo/Cargo.toml | 6 +- examples/rt633/Cargo.lock | 1376 +---------------- examples/rt633/Cargo.toml | 38 +- examples/rt685s-evk/Cargo.lock | 58 - examples/rt685s-evk/Cargo.toml | 6 +- examples/std/Cargo.lock | 150 -- examples/std/Cargo.toml | 7 - .../partition-manager/Cargo.toml | 3 + power-policy-interface/Cargo.toml | 4 +- power-policy-service/Cargo.toml | 10 +- supply-chain/audits.toml | 157 +- supply-chain/config.toml | 126 +- supply-chain/imports.lock | 327 ++-- time-alarm-service-interface/Cargo.toml | 3 + time-alarm-service-relay/Cargo.toml | 3 + time-alarm-service/Cargo.toml | 7 +- type-c-interface/Cargo.toml | 3 + type-c-service/Cargo.toml | 3 - uart-service/Cargo.toml | 11 +- 27 files changed, 411 insertions(+), 2243 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db3b33b06..286f96b51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version 0.2.3", + "rustc_version", ] [[package]] @@ -236,7 +236,6 @@ version = "0.1.0" dependencies = [ "battery-service-interface", "defmt 0.3.100", - "embedded-batteries-async", "embedded-services", "log", "num_enum", @@ -494,41 +493,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - [[package]] name = "dd-manifest-tree" version = "1.0.0" @@ -676,32 +640,6 @@ dependencies = [ "nb 1.1.0", ] -[[package]] -name = "embassy-executor" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" -dependencies = [ - "cordyceps", - "critical-section", - "defmt 1.0.1", - "document-features", - "embassy-executor-macros", - "embassy-executor-timer-queue", -] - -[[package]] -name = "embassy-executor-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "embassy-executor-timer-queue" version = "0.1.0" @@ -989,27 +927,18 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "cortex-m", "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", - "embassy-time", - "embassy-time-driver", - "embedded-cfu-protocol", - "embedded-usb-pd", - "heapless 0.8.0", "log", "mctp-rs", - "num_enum", "paste", "portable-atomic", - "rstest", "serde", "static_cell", "tokio", - "uuid", ] [[package]] @@ -1094,7 +1023,6 @@ dependencies = [ name = "espi-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "cortex-m", "cortex-m-rt", "defmt 0.3.100", @@ -1104,7 +1032,6 @@ dependencies = [ "embedded-services", "log", "mctp-rs", - "num_enum", "odp-service-common", ] @@ -1120,12 +1047,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "funty" version = "2.0.0" @@ -1164,12 +1085,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "half" version = "2.6.0" @@ -1252,12 +1167,6 @@ dependencies = [ "log", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "include_dir" version = "0.7.4" @@ -1853,7 +1762,6 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless 0.8.0", "log", "num_enum", ] @@ -1862,20 +1770,16 @@ dependencies = [ name = "power-policy-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", "embedded-services", "env_logger", "heapless 0.8.0", "log", - "num_enum", "power-policy-interface", - "static_cell", "tokio", ] @@ -1984,38 +1888,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" - -[[package]] -name = "rstest" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" -dependencies = [ - "rstest_macros", -] - -[[package]] -name = "rstest_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" -dependencies = [ - "cfg-if", - "glob", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version 0.4.1", - "syn 2.0.106", - "unicode-ident", -] - [[package]] name = "rtt-target" version = "0.6.1" @@ -2045,16 +1917,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.27", + "semver", ] [[package]] @@ -2084,12 +1947,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "semver-parser" version = "0.7.0" @@ -2209,12 +2066,6 @@ name = "storage_bus" version = "0.1.0" source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#528045934782abc704531aa423169c75016e7727" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subenum" version = "1.1.2" @@ -2346,14 +2197,11 @@ dependencies = [ name = "time-alarm-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "critical-section", "defmt 0.3.100", - "embassy-executor", "embassy-futures", "embassy-sync", "embassy-time", - "embassy-time-driver", "embedded-mcu-hal", "embedded-services", "log", @@ -2551,12 +2399,10 @@ dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", "cfu-service", - "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", - "embassy-time-driver", "embedded-cfu-protocol", "embedded-hal-async", "embedded-services", @@ -2564,7 +2410,6 @@ dependencies = [ "heapless 0.8.0", "log", "power-policy-interface", - "static_cell", "tokio", "tps6699x", "type-c-interface", @@ -2580,16 +2425,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" name = "uart-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "defmt 0.3.100", - "embassy-futures", "embassy-sync", - "embassy-time", "embedded-io-async 0.6.1", "embedded-services", "log", "mctp-rs", - "num_enum", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index be3b19d64..cdfe1aa48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ portable-atomic = { version = "1.11", default-features = false } power-policy-interface = { path = "./power-policy-interface" } paste = "1.0.15" power-policy-service = { path = "./power-policy-service" } -fixed = "=1.23.1" +fixed = "1.23.1" log = "0.4" proc-macro2 = "1.0" quote = "1.0" diff --git a/battery-service-interface/Cargo.toml b/battery-service-interface/Cargo.toml index f73417a88..e9c9538f5 100644 --- a/battery-service-interface/Cargo.toml +++ b/battery-service-interface/Cargo.toml @@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true repository.workspace = true +[package.metadata.cargo-machete] +ignored = ["log"] + [dependencies] defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } diff --git a/battery-service-relay/Cargo.toml b/battery-service-relay/Cargo.toml index 95d477c6f..7d53c8541 100644 --- a/battery-service-relay/Cargo.toml +++ b/battery-service-relay/Cargo.toml @@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true repository.workspace = true +[package.metadata.cargo-machete] +ignored = ["log"] + [dependencies] defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } @@ -12,7 +15,6 @@ embedded-services.workspace = true num_enum.workspace = true battery-service-interface.workspace = true -embedded-batteries-async.workspace = true [features] defmt = [ diff --git a/cfu-service/src/splitter.rs b/cfu-service/src/splitter.rs index e2f04670f..c0d02a546 100644 --- a/cfu-service/src/splitter.rs +++ b/cfu-service/src/splitter.rs @@ -47,24 +47,6 @@ impl<'a, C: Customization> Splitter<'a, C> { } } - // /// Create a new invalid FW version response - // fn create_invalid_fw_version_response(&self) -> component::InternalResponseData { - // let dev_inf = FwVerComponentInfo::new(FwVersion::new(0xffffffff), self.cfu_device.component_id()); - // let comp_info: [FwVerComponentInfo; MAX_CMPT_COUNT] = [dev_inf; MAX_CMPT_COUNT]; - // component::InternalResponseData::FwVersionResponse(GetFwVersionResponse { - // header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), - // component_info: comp_info, - // }) - // } - - // /// Create a content rejection response - // fn create_content_rejection(sequence: u16) -> component::InternalResponseData { - // component::InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - // sequence, - // CfuUpdateContentResponseStatus::ErrorInvalid, - // )) - // } - /// Process a fw version request async fn process_get_fw_version(&self, cfu_client: &crate::CfuClient) -> component::InternalResponseData { let mut versions = [GetFwVersionResponse { diff --git a/embedded-service/Cargo.toml b/embedded-service/Cargo.toml index b40e5fff5..9774188c1 100644 --- a/embedded-service/Cargo.toml +++ b/embedded-service/Cargo.toml @@ -12,19 +12,12 @@ workspace = true [dependencies] bitfield.workspace = true -bitvec.workspace = true critical-section.workspace = true defmt = { workspace = true, optional = true } embassy-sync.workspace = true -embassy-time.workspace = true embassy-futures.workspace = true -embedded-cfu-protocol.workspace = true -embedded-usb-pd.workspace = true -heapless.workspace = true log = { workspace = true, optional = true } -num_enum.workspace = true paste.workspace = true -uuid.workspace = true [dependencies.mctp-rs] workspace = true @@ -43,25 +36,10 @@ cortex-m.workspace = true [dev-dependencies] critical-section = { workspace = true, features = ["std"] } embassy-sync = { workspace = true, features = ["std"] } -embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } -embassy-time-driver = { workspace = true } -rstest.workspace = true static_cell.workspace = true tokio = { workspace = true, features = ["rt", "macros", "time"] } [features] default = [] -defmt = [ - "dep:defmt", - "embassy-sync/defmt", - "embassy-time/defmt", - "embedded-usb-pd/defmt", - "embedded-cfu-protocol/defmt", - "mctp-rs/defmt", -] -log = [ - "dep:log", - "embassy-sync/log", - "embassy-time/log", - "embedded-cfu-protocol/log", -] +defmt = ["dep:defmt", "embassy-sync/defmt", "mctp-rs/defmt"] +log = ["dep:log", "embassy-sync/log"] diff --git a/espi-service/Cargo.toml b/espi-service/Cargo.toml index 95b12922f..350adc4a8 100644 --- a/espi-service/Cargo.toml +++ b/espi-service/Cargo.toml @@ -8,13 +8,12 @@ rust-version.workspace = true license = "MIT" [package.metadata.cargo-machete] -ignored = ["log"] +ignored = ["log", "cortex-m", "cortex-m-rt"] [lints] workspace = true [dependencies] -bitfield.workspace = true embedded-services.workspace = true defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } @@ -22,7 +21,6 @@ embassy-sync.workspace = true embassy-imxrt = { workspace = true, features = ["mimxrt633s"] } embassy-futures.workspace = true mctp-rs = { workspace = true, features = ["espi"] } -num_enum.workspace = true odp-service-common.workspace = true [target.'cfg(target_os = "none")'.dependencies] diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 4091bd29f..50eb7f61a 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -79,20 +79,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "aquamarine" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" -dependencies = [ - "include_dir", - "itertools 0.10.5", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "arraydeque" version = "0.5.1" @@ -189,15 +175,6 @@ dependencies = [ "log", ] -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "unty", -] - [[package]] name = "bit-register" version = "0.1.0" @@ -218,26 +195,6 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" -[[package]] -name = "bitfield" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" -dependencies = [ - "bitfield-macros", -] - -[[package]] -name = "bitfield-macros" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "bitfield-struct" version = "0.12.1" @@ -408,7 +365,7 @@ dependencies = [ "bitvec", "convert_case", "dd-manifest-tree", - "itertools 0.14.0", + "itertools", "kdl", "proc-macro2", "quote", @@ -528,15 +485,6 @@ dependencies = [ "embedded-hal 1.0.0", ] -[[package]] -name = "embedded-cfu-protocol" -version = "0.2.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" -dependencies = [ - "embedded-io-async 0.6.1", - "log", -] - [[package]] name = "embedded-crc-macros" version = "1.0.0" @@ -609,33 +557,15 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "cortex-m", "critical-section", "embassy-futures", "embassy-sync", - "embassy-time", - "embedded-cfu-protocol", - "embedded-usb-pd", - "heapless 0.8.0", "log", "mctp-rs", - "num_enum", "paste", "portable-atomic", "serde", - "uuid", -] - -[[package]] -name = "embedded-usb-pd" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#9a42f07ce99a6d91032d7c9792fd87d4b4f49b6f" -dependencies = [ - "aquamarine", - "bincode", - "bitfield 0.19.4", - "embedded-hal-async", ] [[package]] @@ -814,25 +744,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "io-kit-sys" version = "0.4.1" @@ -849,15 +760,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1203,9 +1105,7 @@ dependencies = [ "critical-section", "embassy-futures", "embassy-time", - "embassy-time-driver", "embedded-batteries-async", - "embedded-hal-async", "embedded-services", "env_logger", "log", @@ -1355,32 +1255,10 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless 0.8.0", "log", "num_enum", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -1799,24 +1677,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" - [[package]] name = "valuable" version = "0.1.1" diff --git a/examples/pico-de-gallo/Cargo.toml b/examples/pico-de-gallo/Cargo.toml index f86ebbcf4..033917d6f 100644 --- a/examples/pico-de-gallo/Cargo.toml +++ b/examples/pico-de-gallo/Cargo.toml @@ -6,6 +6,10 @@ name = "pico-de-gallo" version = "0.1.0" edition = "2024" + +[package.metadata.cargo-machete] +ignored = ["critical-section"] + [workspace.lints.rust] warnings = "deny" @@ -20,7 +24,6 @@ embassy-time = { version = "0.5.0", features = [ "std", "generic-queue-8", ] } -embassy-time-driver = { version = "0.2.1", optional = true } embassy-futures = "0.1.2" embedded-batteries-async = "0.3" @@ -32,7 +35,6 @@ pico-de-gallo-hal = "0.1.0" env_logger = "0.11" log = "0.4.14" static_cell = "2" -embedded-hal-async = "1.0.0" critical-section = { version = "1.1", features = ["std"] } bq40z50-rx = { version = "0.8", features = ["r5", "embassy-timeout"] } diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 389ed1f6a..62a233a4b 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -2,41 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "aquamarine" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" -dependencies = [ - "include_dir", - "itertools 0.10.5", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "az" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" - [[package]] name = "bare-metal" version = "0.2.5" @@ -46,46 +11,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "battery-service" -version = "0.1.0" -dependencies = [ - "battery-service-interface", - "defmt 0.3.100", - "embassy-futures", - "embassy-sync", - "embassy-time", - "embedded-batteries-async", - "embedded-services", - "odp-service-common", - "power-policy-interface", -] - -[[package]] -name = "battery-service-interface" -version = "0.1.0" -dependencies = [ - "defmt 0.3.100", - "embedded-batteries-async", -] - -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "unty", -] - -[[package]] -name = "bit-register" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/odp-utilities#583015c08ad9855f310bdb25d5cf9abff77b5e08" -dependencies = [ - "num-traits", -] - [[package]] name = "bitfield" version = "0.13.2" @@ -98,43 +23,6 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c821a6e124197eb56d907ccc2188eab1038fb919c914f47976e64dd8dbc855d1" -[[package]] -name = "bitfield" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" - -[[package]] -name = "bitfield" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" -dependencies = [ - "bitfield-macros", -] - -[[package]] -name = "bitfield-macros" -version = "0.19.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "bitfield-struct" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -142,916 +30,107 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bitflags" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "bq25773" -version = "0.1.1" -source = "git+https://github.com/OpenDevicePartnership/bq25773#0ca102dec24a617df5762cf0bf4217fd387d5c63" -dependencies = [ - "device-driver", - "embedded-batteries-async", - "embedded-hal 1.0.0", - "embedded-hal-async", -] - -[[package]] -name = "bq40z50-rx" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b6faf600295f12c3fb99b45266bc9140af5c344b08f2705bc06bfa0e8b549e" -dependencies = [ - "device-driver", - "embedded-batteries-async", - "embedded-hal 1.0.0", - "embedded-hal-async", - "smbus-pec", -] - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cc" -version = "1.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] - -[[package]] -name = "cortex-m" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" -dependencies = [ - "bare-metal", - "bitfield 0.13.2", - "critical-section", - "embedded-hal 0.2.7", - "volatile-register", -] - -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "defmt" -version = "0.3.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" -dependencies = [ - "defmt 1.0.1", -] - -[[package]] -name = "defmt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "defmt-rtt" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6eca0aae8aa2cf8333200ecbd236274697bc0a394765c858b3d9372eb1abcfa" -dependencies = [ - "critical-section", - "defmt 0.3.100", -] - -[[package]] -name = "device-driver" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa3d97b2acf349b9d52c75470e2ccfc7224c49597ec12c2fb0e28826e910495" -dependencies = [ - "embedded-io 0.6.1", - "embedded-io-async 0.6.1", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "embassy-embedded-hal" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" -dependencies = [ - "embassy-futures", - "embassy-hal-internal 0.4.0", - "embassy-sync", - "embassy-time", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-executor" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" -dependencies = [ - "cordyceps", - "cortex-m", - "critical-section", - "defmt 1.0.1", - "document-features", - "embassy-executor-macros", - "embassy-executor-timer-queue", -] - -[[package]] -name = "embassy-executor-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "embassy-executor-timer-queue" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" - -[[package]] -name = "embassy-futures" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" - -[[package]] -name = "embassy-hal-internal" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" -dependencies = [ - "cortex-m", - "critical-section", - "defmt 1.0.1", - "num-traits", -] - -[[package]] -name = "embassy-hal-internal" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" -dependencies = [ - "num-traits", -] - -[[package]] -name = "embassy-imxrt" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embassy-imxrt#d95ffe560071e942d917b422bbdea7e73f6c4602" -dependencies = [ - "cfg-if", - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt 1.0.1", - "document-features", - "embassy-embedded-hal", - "embassy-futures", - "embassy-hal-internal 0.3.0", - "embassy-sync", - "embassy-time", - "embassy-time-driver", - "embassy-time-queue-utils", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-hal-nb", - "embedded-io 0.6.1", - "embedded-io-async 0.6.1", - "embedded-mcu-hal", - "embedded-storage", - "fixed", - "itertools 0.11.0", - "mimxrt600-fcb", - "mimxrt633s-pac", - "mimxrt685s-pac", - "nb 1.1.0", - "paste", - "rand_core", - "storage_bus", -] - -[[package]] -name = "embassy-sync" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" -dependencies = [ - "cfg-if", - "critical-section", - "defmt 1.0.1", - "embedded-io-async 0.7.0", - "futures-core", - "futures-sink", - "heapless 0.9.2", -] - -[[package]] -name = "embassy-time" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" -dependencies = [ - "cfg-if", - "critical-section", - "defmt 1.0.1", - "document-features", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "futures-core", -] - -[[package]] -name = "embassy-time-driver" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" -dependencies = [ - "document-features", -] - -[[package]] -name = "embassy-time-queue-utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe6ecec32eac4f98c5427904006b0fc61f77d350e1572530f744ef14650f7a1" -dependencies = [ - "embassy-executor-timer-queue", - "heapless 0.9.2", -] - -[[package]] -name = "embedded-batteries" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f975432b4e146342a1589c563cffab6b7a692024cb511bf87b6bfe78c84125" -dependencies = [ - "bitfield-struct", - "bitflags 2.11.1", - "defmt 0.3.100", - "embedded-hal 1.0.0", - "zerocopy", -] - -[[package]] -name = "embedded-batteries-async" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3bf0e4be67770cfc31f1cea8b73baf98c0baf2c57d6bd8c3a4c315acb1d8bd4" -dependencies = [ - "bitfield-struct", - "defmt 0.3.100", - "embedded-batteries", - "embedded-hal 1.0.0", -] - -[[package]] -name = "embedded-cfu-protocol" -version = "0.2.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-cfu#e0d776017cf34c902c9f2a2be0c75fe73a3a4dda" -dependencies = [ - "defmt 0.3.100", - "embedded-io-async 0.6.1", -] - -[[package]] -name = "embedded-crc-macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" - -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - -[[package]] -name = "embedded-hal-async" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" -dependencies = [ - "embedded-hal 1.0.0", -] - -[[package]] -name = "embedded-hal-nb" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" -dependencies = [ - "embedded-hal 1.0.0", - "nb 1.1.0", -] - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "embedded-io" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" - -[[package]] -name = "embedded-io-async" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" -dependencies = [ - "embedded-io 0.6.1", -] - -[[package]] -name = "embedded-io-async" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" -dependencies = [ - "embedded-io 0.7.1", -] - -[[package]] -name = "embedded-mcu-hal" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" -dependencies = [ - "defmt 1.0.1", - "num_enum", -] - -[[package]] -name = "embedded-services" -version = "0.1.0" -dependencies = [ - "bitfield 0.17.0", - "bitvec", - "cortex-m", - "critical-section", - "defmt 0.3.100", - "embassy-futures", - "embassy-sync", - "embassy-time", - "embedded-cfu-protocol", - "embedded-usb-pd", - "heapless 0.8.0", - "mctp-rs", - "num_enum", - "paste", - "portable-atomic", - "serde", - "uuid", -] - -[[package]] -name = "embedded-storage" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" - -[[package]] -name = "embedded-storage-async" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" -dependencies = [ - "embedded-storage", -] - -[[package]] -name = "embedded-usb-pd" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-usb-pd#1a8e79d3a2ac0d2837a34b045087cf0863146f7d" -dependencies = [ - "aquamarine", - "bincode", - "bitfield 0.19.4", - "defmt 0.3.100", - "embedded-hal-async", -] - -[[package]] -name = "espi-device" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#0d21aa34fd8691c6544533e6c72fe41824dc2fa8" -dependencies = [ - "bit-register", - "bitflags 2.11.1", - "num-traits", - "num_enum", - "static_assertions", - "subenum", -] - -[[package]] -name = "espi-service" -version = "0.1.0" -dependencies = [ - "bitfield 0.17.0", - "cortex-m", - "cortex-m-rt", - "defmt 0.3.100", - "embassy-futures", - "embassy-imxrt", - "embassy-sync", - "embedded-services", - "mctp-rs", - "num_enum", - "odp-service-common", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fixed" -version = "1.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "generator" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "heapless" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.185" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "mctp-rs" -version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#3d941ba5205ca7781bf37e3dc7c5dfdc99a082d6" -dependencies = [ - "bit-register", - "defmt 0.3.100", - "embedded-batteries", - "espi-device", - "num_enum", - "smbus-pec", - "thiserror", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "mimxrt600-fcb" -version = "0.2.2" +name = "cortex-m" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1ebf867c0f22d440f0ad393c28d2007af23c08953b9111379dd711083ae19a9" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ - "bitfield 0.15.0", + "bare-metal", + "bitfield 0.13.2", + "embedded-hal", + "volatile-register", ] [[package]] -name = "mimxrt633s-pac" -version = "0.5.0" +name = "critical-section" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a580cd0048e0e5b1eee17208b7f95ba05ec7ca0175789333b2fa7241798d3cdc" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt 1.0.1", - "vcell", -] +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] -name = "mimxrt685s-pac" -version = "0.5.0" +name = "defmt" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c8860258951178c4f91e42581ae8f33241a0e9a3e89bf2721d932ef366db51b" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", "defmt 1.0.1", - "vcell", ] [[package]] -name = "nb" -version = "0.1.3" +name = "defmt" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" dependencies = [ - "nb 1.1.0", + "bitflags", + "defmt-macros", ] [[package]] -name = "nb" -version = "1.1.0" +name = "defmt-macros" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "nu-ansi-term" -version = "0.50.3" +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "windows-sys", + "thiserror", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "defmt-rtt" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "c6eca0aae8aa2cf8333200ecbd236274697bc0a394765c858b3d9372eb1abcfa" dependencies = [ - "autocfg", + "critical-section", + "defmt 0.3.100", ] [[package]] -name = "num_enum" -version = "0.7.6" +name = "embedded-hal" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" dependencies = [ - "num_enum_derive", - "rustversion", + "nb 0.1.3", + "void", ] [[package]] -name = "num_enum_derive" -version = "0.7.6" +name = "mimxrt600-fcb" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +checksum = "b1ebf867c0f22d440f0ad393c28d2007af23c08953b9111379dd711083ae19a9" dependencies = [ - "proc-macro2", - "quote", - "syn", + "bitfield 0.15.0", ] [[package]] -name = "odp-service-common" -version = "0.1.0" +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" dependencies = [ - "embedded-services", - "static_cell", + "nb 1.1.0", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "nb" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "panic-probe" @@ -1063,38 +142,6 @@ dependencies = [ "defmt 0.3.100", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "power-policy-interface" -version = "0.1.0" -dependencies = [ - "bitfield 0.17.0", - "defmt 0.3.100", - "embassy-futures", - "embassy-sync", - "embedded-batteries-async", - "embedded-services", - "heapless 0.8.0", - "num_enum", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1135,58 +182,14 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - [[package]] name = "rt633-examples" version = "0.1.0" dependencies = [ - "battery-service", - "bq25773", - "bq40z50-rx", - "cortex-m", - "cortex-m-rt", "defmt 0.3.100", "defmt-rtt", - "embassy-embedded-hal", - "embassy-executor", - "embassy-imxrt", - "embassy-sync", - "embassy-time", - "embedded-batteries-async", - "embedded-services", - "espi-service", "mimxrt600-fcb", "panic-probe", - "power-policy-interface", - "static_cell", ] [[package]] @@ -1198,18 +201,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "semver" version = "0.9.0" @@ -1225,110 +216,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smbus-pec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" -dependencies = [ - "embedded-crc-macros", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "static_cell" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "storage_bus" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-mcu#528045934782abc704531aa423169c75016e7727" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subenum" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3d08fe7078c57309d5c3d938e50eba95ba1d33b9c3a101a8465fc6861a5416" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "syn" version = "2.0.117" @@ -1340,12 +227,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "thiserror" version = "2.0.18" @@ -1366,106 +247,12 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "typenum" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "vcell" version = "0.1.3" @@ -1487,55 +274,6 @@ dependencies = [ "vcell", ] -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +[[patch.unused]] +name = "embedded-services" +version = "0.1.0" diff --git a/examples/rt633/Cargo.toml b/examples/rt633/Cargo.toml index 0869611a0..479835c8b 100644 --- a/examples/rt633/Cargo.toml +++ b/examples/rt633/Cargo.toml @@ -10,53 +10,17 @@ license = "MIT" warnings = "deny" [package.metadata.cargo-machete] -ignored = ["cortex-m", "cortex-m-rt"] +ignored = ["defmt"] [lints] workspace = true [dependencies] -cortex-m = { version = "0.7.7", features = [ - "inline-asm", - "critical-section-single-core", -] } -cortex-m-rt = "0.7.3" defmt = "0.3.6" defmt-rtt = "0.4.0" panic-probe = { version = "0.3.1", features = ["print-defmt"] } -embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt", features = [ - "defmt", - "time-driver-os-timer", - "time", - "mimxrt633s", - "unstable-pac", -] } - -embassy-sync = { version = "0.8", features = ["defmt"] } -embassy-executor = { version = "0.10.0", features = [ - "platform-cortex-m", - "executor-thread", - "executor-interrupt", - "defmt", -] } -embassy-time = { version = "0.5.0", features = [ - "defmt", - "defmt-timestamp-uptime", -] } mimxrt600-fcb = "0.2.0" -espi-service = { path = "../../espi-service", features = ["defmt"] } -embedded-services = { path = "../../embedded-service", features = ["defmt"] } -power-policy-interface = { path = "../../power-policy-interface", features = [ - "defmt", -] } - -battery-service = { path = "../../battery-service", features = ["defmt"] } -embedded-batteries-async = { version = "0.3", features = ["defmt"] } -bq25773 = { git = "https://github.com/OpenDevicePartnership/bq25773" } -bq40z50-rx = { version = "0.8", features = ["r5"] } -static_cell = "2.1.0" -embassy-embedded-hal = { version = "0.6.0", default-features = false } # Needed otherwise cargo will pull from git [patch."https://github.com/OpenDevicePartnership/embedded-services"] diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 1b3a7b572..84c82d46b 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -134,18 +134,6 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "bytemuck" version = "1.25.0" @@ -658,22 +646,15 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "cortex-m", "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", - "embassy-time", - "embedded-cfu-protocol", - "embedded-usb-pd", - "heapless 0.8.0", "mctp-rs", - "num_enum", "paste", "portable-atomic", "serde", - "uuid", ] [[package]] @@ -740,12 +721,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures-core" version = "0.3.32" @@ -1110,7 +1085,6 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless 0.8.0", "num_enum", ] @@ -1118,15 +1092,12 @@ dependencies = [ name = "power-policy-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", "embedded-services", "heapless 0.8.0", - "num_enum", "power-policy-interface", ] @@ -1170,12 +1141,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand_core" version = "0.9.5" @@ -1386,12 +1351,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "thiserror" version = "2.0.18" @@ -1425,9 +1384,7 @@ dependencies = [ name = "time-alarm-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "defmt 0.3.100", - "embassy-executor", "embassy-futures", "embassy-sync", "embassy-time", @@ -1592,12 +1549,6 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" - [[package]] name = "valuable" version = "0.1.1" @@ -1655,15 +1606,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "zerocopy" version = "0.8.48" diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 91e01ced7..e7d4b5aca 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -6,12 +6,12 @@ version = "0.1.0" edition = "2024" license = "MIT" -[workspace.lints.rust] -warnings = "deny" - [package.metadata.cargo-machete] ignored = ["cortex-m", "cortex-m-rt"] +[workspace.lints.rust] +warnings = "deny" + [lints] workspace = true diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 0c76c5607..2f27ead8e 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -206,18 +206,6 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -584,31 +572,6 @@ dependencies = [ "embedded-hal 1.0.0", ] -[[package]] -name = "embedded-hal-mock" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a0f04f8886106faf281c47b6a0e4054a369baedaf63591fdb8da9761f3f379" -dependencies = [ - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-hal-nb", - "embedded-time", - "nb 1.1.0", - "void", -] - -[[package]] -name = "embedded-hal-nb" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" -dependencies = [ - "embedded-hal 1.0.0", - "nb 1.1.0", -] - [[package]] name = "embedded-io" version = "0.6.1" @@ -663,31 +626,15 @@ name = "embedded-services" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bitvec", "cortex-m", "critical-section", "embassy-futures", "embassy-sync", - "embassy-time", - "embedded-cfu-protocol", - "embedded-usb-pd", - "heapless 0.8.0", "log", "mctp-rs", - "num_enum", "paste", "portable-atomic", "serde", - "uuid", -] - -[[package]] -name = "embedded-time" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58" -dependencies = [ - "num", ] [[package]] @@ -749,12 +696,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures-core" version = "0.3.32" @@ -1005,59 +946,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "num" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" -dependencies = [ - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1164,7 +1052,6 @@ dependencies = [ "embassy-sync", "embedded-batteries-async", "embedded-services", - "heapless 0.8.0", "log", "num_enum", ] @@ -1173,15 +1060,12 @@ dependencies = [ name = "power-policy-service" version = "0.1.0" dependencies = [ - "bitfield 0.17.0", "embassy-futures", "embassy-sync", "embassy-time", - "embedded-batteries-async", "embedded-services", "heapless 0.8.0", "log", - "num_enum", "power-policy-interface", ] @@ -1225,12 +1109,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "regex" version = "1.12.3" @@ -1399,16 +1277,9 @@ dependencies = [ "debug-service-messages", "defmt 0.3.100", "embassy-executor", - "embassy-futures", "embassy-sync", "embassy-time", - "embassy-time-driver", - "embedded-batteries-async", "embedded-cfu-protocol", - "embedded-fans-async", - "embedded-hal-async", - "embedded-hal-mock", - "embedded-sensors-hal-async", "embedded-services", "embedded-usb-pd", "env_logger", @@ -1452,12 +1323,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "thermal-service" version = "0.1.0" @@ -1650,12 +1515,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" - [[package]] name = "valuable" version = "0.1.1" @@ -1713,15 +1572,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "zerocopy" version = "0.8.48" diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 6b24815a0..39d358ae9 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -16,9 +16,7 @@ workspace = true [dependencies] embassy-sync = { version = "0.8.0", features = ["log", "std"] } -embassy-time-driver = { version = "0.2.1", optional = true } embassy-time = { version = "0.5.0", features = ["log", "std"] } -embassy-futures = "0.1.2" embassy-executor = { version = "0.10.0", features = [ "platform-std", "executor-thread", @@ -39,21 +37,16 @@ power-policy-interface = { path = "../../power-policy-interface", features = [ cfu-service = { path = "../../cfu-service", features = ["log"] } embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" } -embedded-batteries-async = "0.3" battery-service = { path = "../../battery-service", features = ["log", "mock"] } type-c-service = { path = "../../type-c-service", features = ["log"] } type-c-interface = { path = "../../type-c-interface", features = ["log"] } -embedded-sensors-hal-async = "0.3.0" -embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } thermal-service-interface = { path = "../../thermal-service-interface" } env_logger = "0.11.8" log = "0.4.14" static_cell = "2" -embedded-hal-async = "1.0.0" -embedded-hal-mock = { version = "0.11.1", features = ["embedded-hal-async"] } critical-section = { version = "1.1", features = ["std"] } diff --git a/partition-manager/partition-manager/Cargo.toml b/partition-manager/partition-manager/Cargo.toml index 1b3bce14e..043d42677 100644 --- a/partition-manager/partition-manager/Cargo.toml +++ b/partition-manager/partition-manager/Cargo.toml @@ -9,6 +9,9 @@ categories = ["embedded", "hardware-support", "no-std::no-alloc", "no-std"] readme = "README.md" +[package.metadata.cargo-machete] +ignored = ["critical-section"] + [lints] workspace = true diff --git a/power-policy-interface/Cargo.toml b/power-policy-interface/Cargo.toml index 81935f916..3bdea4f33 100644 --- a/power-policy-interface/Cargo.toml +++ b/power-policy-interface/Cargo.toml @@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true repository.workspace = true +[package.metadata.cargo-machete] +ignored = ["log"] + [lints] workspace = true @@ -16,7 +19,6 @@ embassy-futures.workspace = true num_enum.workspace = true bitfield.workspace = true log = { workspace = true, optional = true } -heapless.workspace = true embedded-batteries-async.workspace = true [features] diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 3206a7b90..123d1edb8 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -7,6 +7,9 @@ repository = "https://github.com/OpenDevicePartnership/embedded-services" rust-version.workspace = true license = "MIT" +[package.metadata.cargo-machete] +ignored = ["critical-section"] + [lints] workspace = true @@ -16,15 +19,11 @@ embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true embedded-services.workspace = true -embedded-batteries-async.workspace = true -num_enum.workspace = true -bitfield.workspace = true log = { workspace = true, optional = true } heapless.workspace = true power-policy-interface.workspace = true [dev-dependencies] -static_cell.workspace = true critical-section = { workspace = true, features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } tokio = { workspace = true, features = ["rt", "macros", "time"] } @@ -50,6 +49,3 @@ log = [ "embassy-time/log", "embassy-sync/log", ] - -[package.metadata.cargo-machete] -ignored = ["log"] diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 46b6f41c0..d03ff8e7e 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -16,6 +16,11 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "0.6.21" +[[audits.anstyle]] +who = "matteotullo " +criteria = "safe-to-deploy" +delta = "1.0.10 -> 1.0.13" + [[audits.anstyle-parse]] who = "Robert Zieba " criteria = "safe-to-run" @@ -67,6 +72,21 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "1.0.3" +[[audits.colorchoice]] +who = "matteotullo " +criteria = "safe-to-deploy" +delta = "1.0.3 -> 1.0.4" + +[[audits.colorchoice]] +who = "matteotullo " +criteria = "safe-to-deploy" +delta = "1.0.3 -> 1.0.4" + +[[audits.colorchoice]] +who = "matteotullo " +criteria = "safe-to-deploy" +delta = "1.0.3 -> 1.0.4" + [[audits.const-init]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -92,40 +112,40 @@ who = "Billy Price " criteria = "safe-to-deploy" delta = "0.3.0 -> 0.4.0" -[[audits.embassy-time]] -who = "Billy Price " -criteria = "safe-to-deploy" -delta = "0.5.0 -> 0.5.1" - -[[audits.embassy-time-driver]] -who = "Billy Price " -criteria = "safe-to-deploy" -delta = "0.2.1 -> 0.2.2" - -[[audits.embassy-time-driver]] -who = "Jerry Xie " -criteria = "safe-to-deploy" -delta = "0.5.0 -> 0.6.0" -notes = "No unsafe code, no build script, no powerful imports. Added Clone for I2C devices. Updated embassy dependencies (embassy-sync 0.7→0.8, embassy-hal-internal 0.3→0.4, embassy-time 0.5→0.5.1). All changes safe." - [[audits.embassy-hal-internal]] who = "Jerry Xie " criteria = "safe-to-deploy" delta = "0.3.0 -> 0.4.0" notes = "Rust 2024 edition update with new ring buffer methods (available, is_half_full). All unsafe code is sound HAL pattern usage. Build script unchanged (cfg flags only). No powerful imports." +[[audits.embassy-time]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.5.0 -> 0.5.1" + [[audits.embassy-time]] who = "Jerry Xie " criteria = "safe-to-deploy" delta = "0.5.0 -> 0.5.1" notes = "Rust 2024 edition update with import reordering. Unsafe pin projection patterns unchanged and sound. No build script. No new security concerns." +[[audits.embassy-time-driver]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.2.1 -> 0.2.2" + [[audits.embassy-time-driver]] who = "Jerry Xie " criteria = "safe-to-deploy" delta = "0.2.1 -> 0.2.2" notes = "Rust 2024 edition update with 375kHz tick rate feature. Empty build.rs, no unsafe code, no powerful imports." +[[audits.embassy-time-driver]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.5.0 -> 0.6.0" +notes = "No unsafe code, no build script, no powerful imports. Added Clone for I2C devices. Updated embassy dependencies (embassy-sync 0.7→0.8, embassy-hal-internal 0.3→0.4, embassy-time 0.5→0.5.1). All changes safe." + [[audits.embedded-batteries]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -278,6 +298,11 @@ who = "Matteo Tullo " criteria = "safe-to-deploy" version = "0.4.1" +[[audits.ident_case]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "1.0.1" + [[audits.include_dir]] who = "Robert Zieba " criteria = "safe-to-deploy" @@ -309,9 +334,6 @@ criteria = "safe-to-deploy" version = "0.11.0" notes = "Used as a dependency for embassy-imxrt." -[[audits.jiff]] - - [[audits.jiff]] who = "Robert Zieba " criteria = "safe-to-run" @@ -337,6 +359,10 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "0.2.18" +[[audits.libc]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.2.172" [[audits.log]] who = "Jerry Xie " @@ -393,7 +419,7 @@ who = "Matteo Tullo " criteria = "safe-to-deploy" version = "0.7.4" -[[audits.num_enum_derive]] +[[audits.num_enum]] who = "Billy Price " criteria = "safe-to-deploy" delta = "0.7.4 -> 0.7.5" @@ -408,15 +434,25 @@ notes = "Version bump with test infrastructure updates. No unsafe code, no build [[audits.num_enum_derive]] who = "Billy Price " criteria = "safe-to-deploy" -delta = "0.7.4 -> 0.7.5" +version = "0.7.4" notes = "Looks like mostly improvements to error messaging" -[[audits.object]] +[[audits.num_enum_derive]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.7.4 -> 0.7.5" +notes = "Looks like this is just uptaking a new version of num_enum_derive" + +[[audits.num_enum_derive]] who = "Jerry Xie " criteria = "safe-to-deploy" delta = "0.7.5 -> 0.7.6" notes = "Minor update adding byte literal support for enum discriminants. No unsafe code, no build script, no powerful imports." +[[audits.object]] +who = "Robert Zieba " +criteria = "safe-to-run" +version = "0.36.7" [[audits.once_cell_polyfill]] who = "Robert Zieba " @@ -433,7 +469,6 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "1.11.1" - [[audits.portable-atomic-util]] who = "Robert Zieba " criteria = "safe-to-run" @@ -643,228 +678,228 @@ version = "0.8.26" [[trusted.aho-corasick]] criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) +user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-03-28" end = "2026-09-03" [[trusted.anyhow]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-10-05" end = "2026-04-16" [[trusted.cc]] criteria = "safe-to-deploy" -user-id = 55123 # rust-lang-owner +user-id = 55123 # rust-lang-owner start = "2022-10-29" end = "2026-09-03" [[trusted.libc]] criteria = "safe-to-deploy" -user-id = 55123 # rust-lang-owner +user-id = 55123 # rust-lang-owner start = "2024-08-15" end = "2026-09-03" [[trusted.loom]] criteria = "safe-to-deploy" -user-id = 6741 # Alice Ryhl (Darksonn) +user-id = 6741 # Alice Ryhl (Darksonn) start = "2021-04-12" end = "2026-09-03" [[trusted.memchr]] criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) +user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-07-07" end = "2026-09-03" [[trusted.paste]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-03-19" end = "2026-09-03" [[trusted.proc-macro2]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-04-23" end = "2026-09-03" [[trusted.regex-automata]] criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) +user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-02-25" end = "2026-09-03" [[trusted.rustversion]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-07-08" end = "2026-09-03" [[trusted.ryu]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-05-02" end = "2026-09-03" [[trusted.scoped-tls]] criteria = "safe-to-deploy" -user-id = 1 # Alex Crichton (alexcrichton) +user-id = 1 # Alex Crichton (alexcrichton) start = "2019-02-26" end = "2026-09-03" [[trusted.serde_json]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-02-28" end = "2026-09-03" [[trusted.smallvec]] criteria = "safe-to-deploy" -user-id = 2017 +user-id = 2017 # Matt Brubeck (mbrubeck) start = "2019-10-28" end = "2026-09-03" [[trusted.syn]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-03-01" end = "2026-09-03" [[trusted.thiserror]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-10-09" end = "2026-09-03" [[trusted.thiserror-impl]] criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) +user-id = 3618 # David Tolnay (dtolnay) start = "2019-10-09" end = "2026-09-03" [[trusted.windows]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-01-15" end = "2026-09-03" [[trusted.windows-collections]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2025-02-06" end = "2026-09-03" [[trusted.windows-core]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-11-15" end = "2026-09-03" [[trusted.windows-future]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2025-02-10" end = "2026-09-03" [[trusted.windows-implement]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2022-01-27" end = "2026-09-03" [[trusted.windows-interface]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2022-02-18" end = "2026-09-03" [[trusted.windows-link]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2024-07-17" end = "2026-09-03" [[trusted.windows-numerics]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2023-05-15" end = "2026-09-03" [[trusted.windows-result]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2024-02-02" end = "2026-09-03" [[trusted.windows-strings]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2024-02-02" end = "2026-09-03" [[trusted.windows-sys]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-11-15" end = "2026-09-03" [[trusted.windows-targets]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2022-09-09" end = "2026-09-03" [[trusted.windows-threading]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2025-04-29" end = "2026-09-03" [[trusted.windows_aarch64_gnullvm]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2022-09-01" end = "2026-09-03" [[trusted.windows_aarch64_msvc]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-11-05" end = "2026-09-03" [[trusted.windows_i686_gnu]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-10-28" end = "2026-09-03" [[trusted.windows_i686_gnullvm]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2024-04-02" end = "2026-09-03" [[trusted.windows_i686_msvc]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-10-27" end = "2026-09-03" [[trusted.windows_x86_64_gnu]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-10-28" end = "2026-09-03" [[trusted.windows_x86_64_gnullvm]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2022-09-01" end = "2026-09-03" [[trusted.windows_x86_64_msvc]] criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) +user-id = 64539 # Kenny Kerr (kennykerr) start = "2021-10-27" end = "2026-09-03" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index b58483216..bbca560d0 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -10,18 +10,12 @@ url = "https://raw.githubusercontent.com/OpenDevicePartnership/rust-crate-audits [imports.bytecode-alliance] url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" -[imports.embark-studios] -url = "https://raw.githubusercontent.com/EmbarkStudios/rust-ecosystem/main/audits.toml" - [imports.google] url = "https://raw.githubusercontent.com/google/rust-crate-audits/main/audits.toml" [imports.mozilla] url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" -[imports.zcash] -url = "https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/audits.toml" - [policy.embassy-imxrt] audit-as-crates-io = false @@ -32,10 +26,26 @@ audit-as-crates-io = false version = "0.8.12" criteria = "safe-to-deploy" +[[exemptions.askama]] +version = "0.14.0" +criteria = "safe-to-deploy" + +[[exemptions.askama_derive]] +version = "0.14.0" +criteria = "safe-to-deploy" + +[[exemptions.askama_parser]] +version = "0.14.0" +criteria = "safe-to-deploy" + [[exemptions.az]] version = "1.2.1" criteria = "safe-to-deploy" +[[exemptions.bare-metal]] +version = "0.2.5" +criteria = "safe-to-deploy" + [[exemptions.bbq2]] version = "0.4.2" criteria = "safe-to-deploy" @@ -48,6 +58,10 @@ criteria = "safe-to-deploy" version = "2.0.1" criteria = "safe-to-deploy" +[[exemptions.bitfield]] +version = "0.13.2" +criteria = "safe-to-deploy" + [[exemptions.bitfield]] version = "0.15.0" criteria = "safe-to-deploy" @@ -80,6 +94,10 @@ criteria = "safe-to-deploy" version = "1.0.3" criteria = "safe-to-deploy" +[[exemptions.chrono]] +version = "0.4.40" +criteria = "safe-to-deploy" + [[exemptions.convert_case]] version = "0.6.0" criteria = "safe-to-deploy" @@ -88,6 +106,18 @@ criteria = "safe-to-deploy" version = "0.3.4" criteria = "safe-to-deploy" +[[exemptions.cortex-m]] +version = "0.7.7" +criteria = "safe-to-deploy" + +[[exemptions.cortex-m-rt]] +version = "0.7.5" +criteria = "safe-to-deploy" + +[[exemptions.cortex-m-rt-macros]] +version = "0.7.5" +criteria = "safe-to-deploy" + [[exemptions.crc]] version = "3.3.0" criteria = "safe-to-deploy" @@ -96,6 +126,22 @@ criteria = "safe-to-deploy" version = "2.4.0" criteria = "safe-to-deploy" +[[exemptions.critical-section]] +version = "1.2.0" +criteria = "safe-to-deploy" + +[[exemptions.darling]] +version = "0.20.11" +criteria = "safe-to-deploy" + +[[exemptions.darling_core]] +version = "0.20.11" +criteria = "safe-to-deploy" + +[[exemptions.darling_macro]] +version = "0.20.11" +criteria = "safe-to-deploy" + [[exemptions.dd-manifest-tree]] version = "1.0.0" criteria = "safe-to-deploy" @@ -104,6 +150,18 @@ criteria = "safe-to-deploy" version = "0.3.100" criteria = "safe-to-deploy" +[[exemptions.defmt]] +version = "1.0.1" +criteria = "safe-to-deploy" + +[[exemptions.defmt-macros]] +version = "1.0.1" +criteria = "safe-to-deploy" + +[[exemptions.defmt-parser]] +version = "1.0.0" +criteria = "safe-to-deploy" + [[exemptions.device-driver]] version = "1.0.7" criteria = "safe-to-deploy" @@ -120,6 +178,18 @@ criteria = "safe-to-deploy" version = "0.5.0" criteria = "safe-to-deploy" +[[exemptions.embassy-executor]] +version = "0.9.1" +criteria = "safe-to-deploy" + +[[exemptions.embassy-executor-macros]] +version = "0.7.0" +criteria = "safe-to-deploy" + +[[exemptions.embassy-executor-timer-queue]] +version = "0.1.0" +criteria = "safe-to-deploy" + [[exemptions.embassy-futures]] version = "0.1.2" criteria = "safe-to-deploy" @@ -140,6 +210,14 @@ criteria = "safe-to-deploy" version = "0.2.1" criteria = "safe-to-deploy" +[[exemptions.embassy-time-queue-utils]] +version = "0.3.0" +criteria = "safe-to-deploy" + +[[exemptions.embedded-hal]] +version = "0.2.7" +criteria = "safe-to-deploy" + [[exemptions.embedded-hal]] version = "1.0.0" criteria = "safe-to-deploy" @@ -272,10 +350,38 @@ criteria = "safe-to-deploy" version = "1.1.10" criteria = "safe-to-deploy" +[[exemptions.proc-macro-error-attr2]] +version = "2.0.0" +criteria = "safe-to-deploy" + +[[exemptions.proc-macro-error2]] +version = "2.0.1" +criteria = "safe-to-deploy" + [[exemptions.radium]] version = "0.7.0" criteria = "safe-to-deploy" +[[exemptions.rustc-demangle]] +version = "0.1.26" +criteria = "safe-to-run" + +[[exemptions.rustc_version]] +version = "0.2.3" +criteria = "safe-to-deploy" + +[[exemptions.semver]] +version = "0.9.0" +criteria = "safe-to-deploy" + +[[exemptions.semver-parser]] +version = "0.7.0" +criteria = "safe-to-deploy" + +[[exemptions.serde_spanned]] +version = "0.6.9" +criteria = "safe-to-deploy" + [[exemptions.slab]] version = "0.4.11" criteria = "safe-to-run" @@ -304,6 +410,10 @@ criteria = "safe-to-deploy" version = "0.0.4" criteria = "safe-to-deploy" +[[exemptions.vcell]] +version = "0.1.3" +criteria = "safe-to-deploy" + [[exemptions.version_check]] version = "0.9.5" criteria = "safe-to-deploy" @@ -312,6 +422,10 @@ criteria = "safe-to-deploy" version = "0.0.18" criteria = "safe-to-deploy" +[[exemptions.volatile-register]] +version = "0.2.2" +criteria = "safe-to-deploy" + [[exemptions.wasi]] version = "0.11.1+wasi-snapshot-preview1" criteria = "safe-to-run" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 7944569af..d185c6ac5 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -69,6 +69,13 @@ user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" +[[publisher.rustversion]] +version = "1.0.22" +when = "2025-08-08" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + [[publisher.ryu]] version = "1.0.20" when = "2025-03-04" @@ -90,6 +97,13 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" +[[publisher.smallvec]] +version = "1.15.1" +when = "2025-06-06" +user-id = 2017 +user-login = "mbrubeck" +user-name = "Matt Brubeck" + [[publisher.syn]] version = "1.0.109" when = "2023-02-24" @@ -188,6 +202,13 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" +[[publisher.windows-link]] +version = "0.1.3" +when = "2025-06-12" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + [[publisher.windows-link]] version = "0.2.1" when = "2025-10-06" @@ -300,6 +321,13 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" +[[audits.OpenDevicePartnership.audits.autocfg]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.4.0 -> 1.5.0" +notes = "No unsafe, no build.rs, no network access; delta adds edition-aware rustc probing and best-effort probe-file cleanup only. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.bare-metal]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -336,6 +364,13 @@ criteria = "safe-to-deploy" version = "1.2.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.crunchy]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.3 -> 0.2.4" +notes = "Tiny diff to use newer core/std features via build.rs env var for path separator; no safety impact. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.defmt]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -366,12 +401,6 @@ criteria = "safe-to-deploy" version = "0.5.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/tps6699x/refs/heads/main/supply-chain/audits.toml" -[[audits.OpenDevicePartnership.audits.windows-sys]] -who = "Felipe Balbi " -criteria = "safe-to-run" -version = "0.61.2" -aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" - [[audits.OpenDevicePartnership.audits.embassy-time-queue-utils]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -414,6 +443,27 @@ criteria = "safe-to-deploy" version = "0.7.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.tap]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.1" +notes = "No unsafe, no build.rs, no ambient I/O/process/network capabilities; behavior matches no_std tap/pipe/conv utility traits. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.thread_local]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.1.4 -> 1.1.9" +notes = "No build script, no FS/net/process capability expansion; unsafe refactor to lock-free insertion and nightly TLS path appears sound on review. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.valuable]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "No unsafe code; build.rs only sets target atomic cfg via env; no fs/net/process capability use observed; behavior matches value-inspection purpose. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.vcell]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -426,6 +476,12 @@ criteria = "safe-to-deploy" version = "0.2.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.windows-sys]] +who = "Felipe Balbi " +criteria = "safe-to-run" +version = "0.61.2" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" + [[audits.bytecode-alliance.audits.adler2]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -590,17 +646,6 @@ a few `unsafe` blocks related to utf-8 validation which are locally verifiable as correct and otherwise this crate is good to go. """ -[[audits.bytecode-alliance.audits.rustc-demangle]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "0.1.21" -notes = "I am the author of this crate." - -[[audits.bytecode-alliance.audits.rustc-demangle]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.1.21 -> 0.1.24" - [[audits.bytecode-alliance.audits.semver]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -619,12 +664,6 @@ criteria = "safe-to-deploy" version = "1.1.0" notes = "Only minor `unsafe` code blocks which look valid and otherwise does what it says on the tin." -[[audits.bytecode-alliance.audits.smallvec]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "1.13.2 -> 1.14.0" -notes = "Minor new feature, nothing out of the ordinary." - [[audits.bytecode-alliance.audits.static_assertions]] who = "Andrew Brown " criteria = "safe-to-deploy" @@ -651,24 +690,6 @@ who = "Pat Hickey " criteria = "safe-to-deploy" version = "0.3.17" -[[audits.embark-studios.audits.tap]] -who = "Johan Andersson " -criteria = "safe-to-deploy" -version = "1.0.1" -notes = "No unsafe usage or ambient capabilities" - -[[audits.embark-studios.audits.utf8parse]] -who = "Johan Andersson " -criteria = "safe-to-deploy" -version = "0.2.1" -notes = "Single unsafe usage that looks sound, no ambient capabilities" - -[[audits.embark-studios.audits.valuable]] -who = "Johan Andersson " -criteria = "safe-to-deploy" -version = "0.1.0" -notes = "No unsafe usage or ambient capabilities, sane build script" - [[audits.google.audits.anstyle]] who = "Yu-An Wang " criteria = "safe-to-run" @@ -1053,74 +1074,6 @@ delta = "0.4.0 -> 0.4.1" notes = "No unsafe, net or fs." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.rustversion]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.0.14" -notes = """ -Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'``, `'\bnet\b'``, `'\bunsafe\b'`` -and there were no hits except for: - -* Using trivially-safe `unsafe` in test code: - - ``` - tests/test_const.rs:unsafe fn _unsafe() {} - tests/test_const.rs:const _UNSAFE: () = unsafe { _unsafe() }; - ``` - -* Using `unsafe` in a string: - - ``` - src/constfn.rs: "unsafe" => Qualifiers::Unsafe, - ``` - -* Using `std::fs` in `build/build.rs` to write `${OUT_DIR}/version.expr` - which is later read back via `include!` used in `src/lib.rs`. - -Version `1.0.6` of this crate has been added to Chromium in -https://source.chromium.org/chromium/chromium/src/+/28841c33c77833cc30b286f9ae24c97e7a8f4057 -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.rustversion]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.14 -> 1.0.15" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.rustversion]] -who = "danakj " -criteria = "safe-to-deploy" -delta = "1.0.15 -> 1.0.16" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.rustversion]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.16 -> 1.0.17" -notes = "Just updates windows compat" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.rustversion]] -who = "Liza Burakova " -criteria = "safe-to-deploy" -delta = "1.0.17 -> 1.0.18" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.rustversion]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.18 -> 1.0.19" -notes = "No unsafe, just doc changes" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.rustversion]] -who = "Daniel Cheng " -criteria = "safe-to-deploy" -delta = "1.0.19 -> 1.0.20" -notes = "Only minor updates to documentation and the mock today used for testing." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.semver]] who = "Daniel Cheng " criteria = "safe-to-run" @@ -1363,12 +1316,6 @@ delta = "1.0.218 -> 1.0.219" notes = "Minor changes (clippy tweaks, using `mem::take` instead of `mem::replace`)." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.smallvec]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "1.13.2" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.stable_deref_trait]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -1376,6 +1323,17 @@ version = "1.2.0" notes = "Purely a trait, crates using this should be carefully vetted since self-referential stuff can be super tricky around various unsafe rust edges." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.strsim]] +who = "danakj@chromium.org" +criteria = "safe-to-deploy" +version = "0.10.0" +notes = """ +Reviewed in https://crrev.com/c/5171063 + +Previously reviewed during security review and the audit is grandparented in. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.unicode-ident]] who = "Lukasz Anforowicz " criteria = "safe-to-deploy" @@ -1426,6 +1384,12 @@ delta = "1.0.16 -> 1.0.18" notes = "Only minor comment and documentation updates." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.utf8parse]] +who = "Ying Hsu " +criteria = "safe-to-run" +version = "0.2.1" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.void]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -1459,6 +1423,12 @@ end = "2026-02-01" notes = "All code written or reviewed by Manish" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.adler2]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "2.0.0 -> 2.0.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.arraydeque]] who = "Lars Eggert " criteria = "safe-to-deploy" @@ -1597,6 +1567,13 @@ criteria = "safe-to-deploy" delta = "0.2.10 -> 0.2.11" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.fnv]] +who = "Bobby Holley " +criteria = "safe-to-deploy" +version = "1.0.7" +notes = "Simple hasher implementation with no unsafe code." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.futures-core]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -1716,12 +1693,6 @@ criteria = "safe-to-deploy" delta = "1.1.0 -> 1.3.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.smallvec]] -who = "Erich Gubler " -criteria = "safe-to-deploy" -delta = "1.14.0 -> 1.15.1" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.audits.strsim]] who = "Ben Dean-Kawamura " criteria = "safe-to-deploy" @@ -1792,117 +1763,3 @@ who = "Nika Layzell " criteria = "safe-to-deploy" delta = "0.2.1 -> 0.2.2" aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" - -[[audits.mozilla.audits.windows-link]] -who = "Mark Hammond " -criteria = "safe-to-deploy" -version = "0.1.1" -notes = "A microsoft crate allowing unsafe calls to windows apis." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.zcash.audits.adler2]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "2.0.0 -> 2.0.1" -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.anstyle]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.0.10 -> 1.0.13" -aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" - -[[audits.zcash.audits.autocfg]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.4.0 -> 1.5.0" -notes = "Filesystem change is to remove the generated LLVM IR output file after probing." -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.colorchoice]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.0.3 -> 1.0.4" -aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" - -[[audits.zcash.audits.crunchy]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.2.3 -> 0.2.4" -notes = """ -Build script change is to fix a bug where a path separator for an included file -was being selected by the target OS instead of the host OS. -""" -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.rustversion]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.0.20 -> 1.0.21" -notes = "Build script change is to fix building with `-Zfmt-debug=none`." -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.rustversion]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.0.21 -> 1.0.22" -notes = "Changes to generated code are to prepend a clippy annotation." - -[[audits.zcash.audits.rustc-demangle]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.1.24 -> 0.1.25" -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.rustc-demangle]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.1.25 -> 0.1.26" -notes = "Parser changes use existing parsing machinery in an obvious way." -aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" - -[[audits.zcash.audits.serde_spanned]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.6.8 -> 0.6.9" -aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" - -[[audits.zcash.audits.thread_local]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.1.4 -> 1.1.7" -notes = """ -New `unsafe` usage: -- An extra `deallocate_bucket`, to replace a `Mutex::lock` with a `compare_exchange`. -- Setting and getting a `#[thread_local] static mut Option` on nightly. -""" -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.thread_local]] -who = "Daira-Emma Hopwood " -criteria = "safe-to-deploy" -delta = "1.1.7 -> 1.1.8" -notes = """ -Adds `unsafe` code that makes an assumption that `ptr::null_mut::>()` is a valid representation -of an `AtomicPtr>`, but this is likely a correct assumption. -""" -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.thread_local]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.1.8 -> 1.1.9" -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.valuable]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.1.0 -> 0.1.1" -notes = "Build script changes are for linting." -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.windows-link]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.1.1 -> 0.1.3" -aggregated-from = "https://raw.githubusercontent.com/zcash/librustzcash/main/supply-chain/audits.toml" diff --git a/time-alarm-service-interface/Cargo.toml b/time-alarm-service-interface/Cargo.toml index 982f8f6c9..13a09e048 100644 --- a/time-alarm-service-interface/Cargo.toml +++ b/time-alarm-service-interface/Cargo.toml @@ -19,3 +19,6 @@ log = ["dep:log"] [lints] workspace = true + +[package.metadata.cargo-machete] +ignored = ["log"] diff --git a/time-alarm-service-relay/Cargo.toml b/time-alarm-service-relay/Cargo.toml index 611ede75e..e098b1904 100644 --- a/time-alarm-service-relay/Cargo.toml +++ b/time-alarm-service-relay/Cargo.toml @@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true repository.workspace = true +[package.metadata.cargo-machete] +ignored = ["log"] + [dependencies] defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index d9c933b84..be4d2970a 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -6,11 +6,12 @@ edition.workspace = true license.workspace = true repository.workspace = true +[package.metadata.cargo-machete] +ignored = ["log", "defmt", "critical-section"] + [dependencies] -bitfield.workspace = true defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } -embassy-executor.workspace = true embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true @@ -26,7 +27,6 @@ defmt = [ "embedded-services/defmt", "embassy-time/defmt", "embassy-sync/defmt", - "embassy-executor/defmt", "time-alarm-service-interface/defmt", ] @@ -41,4 +41,3 @@ time-alarm-service = { path = ".", features = ["mock"] } tokio = { workspace = true, features = ["rt", "macros", "time"] } critical-section = { version = "1.1", features = ["std"] } embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } -embassy-time-driver = { workspace = true } diff --git a/type-c-interface/Cargo.toml b/type-c-interface/Cargo.toml index 6d33373b3..8b11bc79c 100644 --- a/type-c-interface/Cargo.toml +++ b/type-c-interface/Cargo.toml @@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true repository.workspace = true +[package.metadata.cargo-machete] +ignored = ["log"] + [dependencies] bitfield.workspace = true embassy-sync.workspace = true diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 8514caa19..006618e8b 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -34,11 +34,8 @@ type-c-interface.workspace = true [dev-dependencies] embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } embassy-sync = { workspace = true, features = ["std"] } -critical-section = { workspace = true, features = ["std"] } -embassy-time-driver = { workspace = true } embassy-futures.workspace = true tokio = { workspace = true, features = ["rt", "macros", "time"] } -static_cell.workspace = true [features] default = [] diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml index 1bb57b81f..beb373559 100644 --- a/uart-service/Cargo.toml +++ b/uart-service/Cargo.toml @@ -7,30 +7,27 @@ repository = "https://github.com/OpenDevicePartnership/embedded-services" rust-version = "1.88" license = "MIT" +[package.metadata.cargo-machete] +ignored = ["log"] + [lints] workspace = true [dependencies] -bitfield.workspace = true embedded-services.workspace = true defmt = { workspace = true, optional = true } log = { workspace = true, optional = true } -embassy-time.workspace = true embassy-sync.workspace = true -embassy-futures.workspace = true mctp-rs = { workspace = true } embedded-io-async.workspace = true -num_enum.workspace = true [features] default = [] defmt = [ "dep:defmt", "embedded-services/defmt", - "embassy-time/defmt", - "embassy-time/defmt-timestamp-uptime", "embassy-sync/defmt", "mctp-rs/defmt", ] -log = ["dep:log", "embedded-services/log", "embassy-time/log"] +log = ["dep:log", "embedded-services/log"] From 2a53acb825ccb2942aacc5f940946b39dff3f21e Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Wed, 22 Apr 2026 11:35:22 -0700 Subject: [PATCH 66/79] Update copilot-instructions.md to refer to 1.90 toolchain (#814) --- .github/copilot-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ec5387ebb..bfc766f36 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -6,7 +6,7 @@ This is an embedded controller (EC) services workspace — a collection of `no_s ## Build, Test, and Lint -Toolchain: Rust 1.88 (`rust-toolchain.toml`), edition 2024. Targets: `x86_64-unknown-linux-gnu` (std/testing) and `thumbv8m.main-none-eabihf` (ARM Cortex-M33). +Toolchain: Rust 1.90 (`rust-toolchain.toml`), edition 2024. Targets: `x86_64-unknown-linux-gnu` (std/testing) and `thumbv8m.main-none-eabihf` (ARM Cortex-M33). ```shell # Format From 9299e498294d5627036a7526a85eba58a71d55ac Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Thu, 23 Apr 2026 09:21:38 -0700 Subject: [PATCH 67/79] type-c-service/wrapper: Break out event receiver code (#799) * Move all code related to receiving events out of `wrapper::Wrapper` and into a separate event receiver struct. * Pull-in corresponding tps6699x changes. * Refactor `PortEventStreamer` to work over an iter of`PortEventBitfield`, simpifly logic. * Introduce temporary generics and public visibility. --- Cargo.lock | 361 +----------------- Cargo.toml | 2 +- examples/rt685s-evk/Cargo.lock | 2 +- examples/rt685s-evk/Cargo.toml | 2 +- examples/rt685s-evk/src/bin/type_c.rs | 62 ++- examples/rt685s-evk/src/bin/type_c_cfu.rs | 64 +++- examples/std/Cargo.lock | 2 +- examples/std/src/bin/type_c/service.rs | 43 ++- examples/std/src/bin/type_c/ucsi.rs | 57 ++- examples/std/src/bin/type_c/unconstrained.rs | 74 +++- .../std/src/lib/type_c/mock_controller.rs | 37 +- type-c-interface/src/port/mod.rs | 14 - type-c-service/src/driver/tps6699x.rs | 230 ++++++----- type-c-service/src/lib.rs | 279 +++++--------- type-c-service/src/wrapper/backing.rs | 62 +-- type-c-service/src/wrapper/cfu.rs | 81 ++-- type-c-service/src/wrapper/event_receiver.rs | 279 ++++++++++++++ type-c-service/src/wrapper/mod.rs | 203 ++-------- type-c-service/src/wrapper/pd.rs | 66 +--- type-c-service/src/wrapper/power.rs | 36 +- 20 files changed, 837 insertions(+), 1119 deletions(-) create mode 100644 type-c-service/src/wrapper/event_receiver.rs diff --git a/Cargo.lock b/Cargo.lock index 286f96b51..3a78a9095 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,18 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -132,43 +120,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "askama" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" -dependencies = [ - "askama_derive", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "syn 2.0.106", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", - "winnow 0.7.13", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -340,18 +291,6 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-device-driver" version = "0.2.0" @@ -414,15 +353,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd422bfb4f24a97243f60b6a4443e63d810c925d8da4bb2d8fde26a7c1d57ec" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "cordyceps" version = "0.3.4" @@ -493,15 +423,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "dd-manifest-tree" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" -dependencies = [ - "yaml-rust2", -] - [[package]] name = "debug-service" version = "0.1.0" @@ -563,7 +484,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -573,40 +494,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" dependencies = [ "defmt 0.3.100", - "device-driver-macros", "embedded-io 0.6.1", "embedded-io-async 0.6.1", ] -[[package]] -name = "device-driver-generation" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3935aec9cf5bb2ab927f59ca69faecf976190390b0ce34c6023889e9041040c0" -dependencies = [ - "anyhow", - "askama", - "bitvec", - "convert_case", - "dd-manifest-tree", - "itertools 0.14.0", - "kdl", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "device-driver-macros" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdc68ed515c4eddff2e95371185b4becba066085bf36d50f07f09782af98e17" -dependencies = [ - "device-driver-generation", - "proc-macro2", - "syn 2.0.106", -] - [[package]] name = "document-features" version = "0.2.11" @@ -968,15 +859,6 @@ dependencies = [ "embedded-hal-async", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "env_filter" version = "0.1.4" @@ -1047,12 +929,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures-core" version = "0.3.31" @@ -1104,30 +980,12 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "heapless" version = "0.8.0" @@ -1193,7 +1051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown", ] [[package]] @@ -1240,12 +1098,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - [[package]] name = "jiff" version = "0.2.20" @@ -1270,18 +1122,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "kdl" -version = "6.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661358400b02cbbf1fbd05f0a483335490e8a6bd1867620f2eeb78f304a22f" -dependencies = [ - "miette", - "num", - "thiserror 1.0.69", - "winnow 0.6.24", -] - [[package]] name = "keyberon" version = "0.2.0" @@ -1394,7 +1234,7 @@ dependencies = [ "espi-device", "num_enum", "smbus-pec", - "thiserror 2.0.16", + "thiserror", ] [[package]] @@ -1403,28 +1243,6 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "mimxrt600-fcb" version = "0.2.2" @@ -1516,70 +1334,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1680,12 +1434,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - [[package]] name = "pin-project" version = "1.1.10" @@ -1847,12 +1595,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand_core" version = "0.9.5" @@ -1905,12 +1647,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.2.3" @@ -1926,12 +1662,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "scoped-tls" version = "1.0.1" @@ -1983,18 +1713,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "serde_json" -version = "1.0.143" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - [[package]] name = "serde_spanned" version = "0.6.9" @@ -2100,12 +1818,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "thermal-service" version = "0.1.0" @@ -2144,33 +1856,13 @@ dependencies = [ "uuid", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.16", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", + "thiserror-impl", ] [[package]] @@ -2294,13 +1986,13 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.13", + "winnow", ] [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x#aa3425136216dccfb107bff6b172a49b8972bb70" +source = "git+https://github.com/OpenDevicePartnership/tps6699x?branch=v0.2.0#c908a50747e8fcce831d4e53026072b5b6916a7b" dependencies = [ "bincode", "bitfield 0.19.2", @@ -2445,18 +2137,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unty" version = "0.0.4" @@ -2738,15 +2418,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.13" @@ -2756,26 +2427,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yaml-rust2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink", -] - [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index cdfe1aa48..cc22f94d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ 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" } syn = "2.0" -tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" } +tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x", branch = "v0.2.0" } tokio = { version = "1.42.0" } uuid = { version = "=1.17.0", default-features = false } zerocopy = "0.8" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 84c82d46b..f27612195 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1420,7 +1420,7 @@ dependencies = [ [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x#34c4adcf2e79f808d934b02f00542ebe0131a498" +source = "git+https://github.com/OpenDevicePartnership/tps6699x?branch=v0.2.0#c908a50747e8fcce831d4e53026072b5b6916a7b" dependencies = [ "bincode", "bitfield 0.19.4", diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index e7d4b5aca..6ebe240a8 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -60,7 +60,7 @@ power-policy-interface = { path = "../../power-policy-interface", features = [ power-policy-service = { path = "../../power-policy-service", features = [ "defmt", ] } -tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x", features = [ +tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x", branch = "v0.2.0", features = [ "defmt", "embassy", ] } diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 8ad63ac4d..c70e57de0 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -27,10 +27,11 @@ use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; use type_c_interface::port::PortRegistration; use type_c_interface::service::event::PortEvent as ServicePortEvent; -use type_c_service::driver::tps6699x::{self as tps6699x_drv}; +use type_c_service::driver::tps6699x::{self as tps6699x_drv, InterruptReceiver}; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -68,7 +69,7 @@ type Wrapper<'a> = ControllerWrapper< Validator, >; type Controller<'a> = tps6699x::controller::Controller>; -type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type InterruptProcessor<'a> = tps6699x::interrupt::InterruptProcessor<'a, GlobalRawMutex, BusDevice<'a>>; type PowerPolicySenderType = MapSender< power_policy_interface::service::event::Event<'static, DeviceType>, @@ -92,16 +93,36 @@ type PowerPolicyServiceType = Mutex< type ServiceType = Service<'static>; #[embassy_executor::task] -async fn pd_controller_task(controller: &'static Wrapper<'static>) { +async fn pd_controller_task( + mut event_receiver: ArrayPortEventReceivers< + 'static, + 2, + InterruptReceiver<'static, GlobalRawMutex, BusDevice<'static>>, + >, + wrapper: &'static Wrapper<'static>, +) { loop { - if let Err(e) = controller.process_next_event().await { - error!("Error processing controller event: {:?}", e); + let event = event_receiver.wait_event().await; + + let output = wrapper + .process_event( + &mut event_receiver.sink_ready_timeout, + &mut event_receiver.cfu_event_receiver, + event, + ) + .await; + if let Err(e) = output { + error!("Error processing event: {:?}", e); + } + let output = output.unwrap(); + if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { + error!("Error finalizing output: {:?}", e); } } } #[embassy_executor::task] -async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'static>) { +async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: InterruptProcessor<'static>) { tps6699x::task::interrupt_task(&mut int_in, &mut [&mut interrupt]).await; } @@ -120,7 +141,6 @@ async fn type_c_service_task( wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { - info!("Starting type-c task"); type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } @@ -140,15 +160,15 @@ async fn main(spawner: Spawner) { let device = I2cDevice::new(bus); static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Controller::new_tps66994(device, ADDR1).unwrap()); - let (mut tps6699x, interrupt) = controller.make_parts(); + let controller = CONTROLLER.init(Controller::new_tps66994(device, Default::default(), ADDR1).unwrap()); + let (mut tps6699x, interrupt_processor, interrupt_receiver) = controller.make_parts(); info!("Resetting PD controller"); let mut delay = Delay; tps6699x.reset(&mut delay).await.unwrap(); info!("Spawining interrupt task"); - spawner.spawn(interrupt_task(int_in, interrupt).expect("Failed to spawn interrupt task")); + spawner.spawn(interrupt_task(int_in, interrupt_processor).expect("Failed to spawn interrupt task")); // These aren't enabled by default tps6699x @@ -198,14 +218,13 @@ async fn main(spawner: Spawner) { let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); + let (intermediate, power_event_receivers) = storage + .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) + .expect("Failed to create intermediate storage"); static INTERMEDIATE: StaticCell< IntermediateStorage>, > = StaticCell::new(); - let intermediate = INTERMEDIATE.init( - storage - .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"), - ); + let intermediate = INTERMEDIATE.init(intermediate); static REFERENCED: StaticCell< ReferencedStorage>, @@ -289,5 +308,16 @@ async fn main(spawner: Spawner) { .expect("Failed to create power policy task"), ); - spawner.spawn(pd_controller_task(wrapper).expect("Failed to create pd controller task")); + spawner.spawn( + pd_controller_task( + ArrayPortEventReceivers::new( + InterruptReceiver::new(interrupt_receiver), + power_event_receivers, + &referenced.pd_controller, + &storage.cfu_device, + ), + wrapper, + ) + .expect("Failed to create pd controller task"), + ); } diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index cd0bd3ae8..57100233d 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -30,10 +30,11 @@ use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; use type_c_interface::port::PortRegistration; use type_c_interface::service::event::PortEvent as ServicePortEvent; -use type_c_service::driver::tps6699x::{self as tps6699x_drv}; +use type_c_service::driver::tps6699x::{self as tps6699x_drv, InterruptReceiver}; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; use type_c_service::wrapper::proxy::PowerProxyDevice; extern crate rt685s_evk_example; @@ -66,7 +67,7 @@ type Wrapper<'a> = ControllerWrapper< Validator, >; type Controller<'a> = tps6699x::controller::Controller>; -type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type InterruptProcessor<'a> = tps6699x::interrupt::InterruptProcessor<'a, GlobalRawMutex, BusDevice<'a>>; type PowerPolicySenderType = MapSender< power_policy_interface::service::event::Event<'static, DeviceType>, @@ -96,16 +97,36 @@ const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); #[embassy_executor::task] -async fn pd_controller_task(controller: &'static Wrapper<'static>) { +async fn pd_controller_task( + mut event_receiver: ArrayPortEventReceivers< + 'static, + 2, + InterruptReceiver<'static, GlobalRawMutex, BusDevice<'static>>, + >, + wrapper: &'static Wrapper<'static>, +) { loop { - if let Err(e) = controller.process_next_event().await { - error!("Error processing controller event: {:?}", e); + let event = event_receiver.wait_event().await; + + let output = wrapper + .process_event( + &mut event_receiver.sink_ready_timeout, + &mut event_receiver.cfu_event_receiver, + event, + ) + .await; + if let Err(e) = output { + error!("Error processing event: {:?}", e); + } + let output = output.unwrap(); + if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { + error!("Error finalizing output: {:?}", e); } } } #[embassy_executor::task] -async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'static>) { +async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: InterruptProcessor<'static>) { tps6699x::task::interrupt_task(&mut int_in, &mut [&mut interrupt]).await; } @@ -204,7 +225,6 @@ async fn type_c_service_task( wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], cfu_client: &'static CfuClient, ) { - info!("Starting type-c task"); type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } @@ -224,15 +244,15 @@ async fn main(spawner: Spawner) { let device = I2cDevice::new(bus); static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Controller::new_tps66994(device, ADDR1).unwrap()); - let (mut tps6699x, interrupt) = controller.make_parts(); + let controller = CONTROLLER.init(Controller::new_tps66994(device, Default::default(), ADDR1).unwrap()); + let (mut tps6699x, interrupt_processor, interrupt_receiver) = controller.make_parts(); info!("Resetting PD controller"); let mut delay = Delay; tps6699x.reset(&mut delay).await.unwrap(); info!("Spawining interrupt task"); - spawner.spawn(interrupt_task(int_in, interrupt).expect("Failed to spawn interrupt task")); + spawner.spawn(interrupt_task(int_in, interrupt_processor).expect("Failed to spawn interrupt task")); // These aren't enabled by default tps6699x @@ -257,7 +277,7 @@ async fn main(spawner: Spawner) { let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, - 0, // CFU component ID + CONTROLLER0_CFU_ID, [ PortRegistration { id: PORT0_ID, @@ -282,14 +302,13 @@ async fn main(spawner: Spawner) { let policy_sender1 = policy_channel1.dyn_sender(); let policy_receiver1 = policy_channel1.dyn_receiver(); + let (intermediate, power_event_receivers) = storage + .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) + .expect("Failed to create intermediate storage"); static INTERMEDIATE: StaticCell< IntermediateStorage>, > = StaticCell::new(); - let intermediate = INTERMEDIATE.init( - storage - .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"), - ); + let intermediate = INTERMEDIATE.init(intermediate); static REFERENCED: StaticCell< ReferencedStorage< @@ -377,7 +396,18 @@ async fn main(spawner: Spawner) { .expect("Failed to create power policy task"), ); - spawner.spawn(pd_controller_task(wrapper).expect("Failed to create pd controller task")); + spawner.spawn( + pd_controller_task( + ArrayPortEventReceivers::new( + InterruptReceiver::new(interrupt_receiver), + power_event_receivers, + &referenced.pd_controller, + &storage.cfu_device, + ), + wrapper, + ) + .expect("Failed to create pd controller task"), + ); spawner.spawn(fw_update_task().expect("Failed to create fw update task")); } diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 2f27ead8e..834116373 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1380,7 +1380,7 @@ dependencies = [ [[package]] name = "tps6699x" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/tps6699x#34c4adcf2e79f808d934b02f00542ebe0131a498" +source = "git+https://github.com/OpenDevicePartnership/tps6699x?branch=v0.2.0#c908a50747e8fcce831d4e53026072b5b6916a7b" dependencies = [ "bincode", "bitfield 0.19.4", diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index f1be329e1..048baaeda 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -24,6 +24,7 @@ use type_c_service::service::config::Config; use type_c_service::service::{EventReceiver, Service}; use type_c_service::util::power_capability_from_current; use type_c_service::wrapper::backing::Storage; +use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; use type_c_service::wrapper::message::*; use type_c_service::wrapper::proxy::PowerProxyDevice; @@ -58,18 +59,22 @@ type ServiceType = Service<'static>; #[embassy_executor::task] async fn controller_task( + mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, wrapper: &'static Wrapper<'static>, controller: &'static Mutex>, ) { controller.lock().await.custom_function(); loop { - let event = wrapper.wait_next().await; - if let Err(e) = event { - error!("Error waiting for event: {e:?}"); - continue; - } - let output = wrapper.process_event(event.unwrap()).await; + let event = event_receiver.wait_event().await; + + let output = wrapper + .process_event( + &mut event_receiver.sink_ready_timeout, + &mut event_receiver.cfu_event_receiver, + event, + ) + .await; if let Err(e) = output { error!("Error processing event: {e:?}"); } @@ -79,7 +84,7 @@ async fn controller_task( info!("Port{}: PD alert received: {:?}", port.0, ado); } - if let Err(e) = wrapper.finalize(output).await { + if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { error!("Error finalizing output: {e:?}"); } } @@ -96,7 +101,7 @@ async fn task(spawner: Spawner) { static CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTEXT.init(type_c_interface::service::context::Context::new()); - let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); + let (event_receiver, wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -145,7 +150,8 @@ async fn task(spawner: Spawner) { ) .expect("Failed to create type-c service task"), ); - spawner.spawn(controller_task(wrapper, controller).expect("Failed to create controller task")); + + spawner.spawn(controller_task(event_receiver, wrapper, controller).expect("Failed to create controller task")); Timer::after_millis(1000).await; info!("Simulating connection"); @@ -193,6 +199,7 @@ async fn type_c_service_task( fn create_wrapper( context: &'static Context, ) -> ( + ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, &'static Wrapper<'static>, DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, &'static Mutex>, @@ -222,6 +229,10 @@ fn create_wrapper( let policy_sender = policy_channel.dyn_sender(); let policy_receiver = policy_channel.dyn_receiver(); + let (intermediate, power_event_receivers) = storage + .try_create_intermediate([("Pd0", policy_sender)]) + .expect("Failed to create intermediate storage"); + static INTERMEDIATE: StaticCell< type_c_service::wrapper::backing::IntermediateStorage< 1, @@ -229,11 +240,7 @@ fn create_wrapper( DynamicSender<'static, psu::event::EventData>, >, > = StaticCell::new(); - let intermediate = INTERMEDIATE.init( - storage - .try_create_intermediate([("Pd0", policy_sender)]) - .expect("Failed to create intermediate storage"), - ); + let intermediate = INTERMEDIATE.init(intermediate); static REFERENCED: StaticCell< type_c_service::wrapper::backing::ReferencedStorage< @@ -248,11 +255,19 @@ fn create_wrapper( .expect("Failed to create referenced storage"), ); + let event_receiver = ArrayPortEventReceivers::new( + state.create_interrupt_receiver(), + power_event_receivers, + &referenced.pd_controller, + &storage.cfu_device, + ); + static CONTROLLER: StaticCell> = StaticCell::new(); let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); static WRAPPER: StaticCell = StaticCell::new(); ( + event_receiver, WRAPPER.init(mock_controller::Wrapper::new( controller, Default::default(), diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 3ef119815..babb2ae06 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -28,6 +28,7 @@ use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::service::config::Config; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::Storage; +use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; use type_c_service::wrapper::proxy::PowerProxyDevice; const CHANNEL_CAPACITY: usize = 4; @@ -195,10 +196,26 @@ async fn opm_task(_context: &'static Context, _state: [&'static mock_controller: } #[embassy_executor::task(pool_size = 2)] -async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { +async fn wrapper_task( + mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, + wrapper: &'static mock_controller::Wrapper<'static>, +) { loop { - if let Err(e) = wrapper.process_next_event().await { - error!("Error processing wrapper: {e:#?}"); + let event = event_receiver.wait_event().await; + + let output = wrapper + .process_event( + &mut event_receiver.sink_ready_timeout, + &mut event_receiver.cfu_event_receiver, + event, + ) + .await; + if let Err(e) = output { + error!("Error processing event: {e:?}"); + } + let output = output.unwrap(); + if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { + error!("Error finalizing output: {e:#?}"); } } } @@ -256,11 +273,10 @@ async fn task(spawner: Spawner) { DynamicSender<'_, psu::event::EventData>, >, > = StaticCell::new(); - let intermediate0 = INTERMEDIATE0.init( - storage0 - .try_create_intermediate([("Pd0", policy_sender0)]) - .expect("Failed to create intermediate storage"), - ); + let (intermediate0, power_event_receivers0) = storage0 + .try_create_intermediate([("Pd0", policy_sender0)]) + .expect("Failed to create intermediate storage"); + let intermediate0 = INTERMEDIATE0.init(intermediate0); static REFERENCED0: StaticCell< type_c_service::wrapper::backing::ReferencedStorage< @@ -277,6 +293,12 @@ async fn task(spawner: Spawner) { static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); + let event_receiver0 = ArrayPortEventReceivers::new( + state0.create_interrupt_receiver(), + power_event_receivers0, + &referenced0.pd_controller, + &storage0.cfu_device, + ); static CONTROLLER0: StaticCell> = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); static WRAPPER0: StaticCell = StaticCell::new(); @@ -311,11 +333,10 @@ async fn task(spawner: Spawner) { DynamicSender<'_, psu::event::EventData>, >, > = StaticCell::new(); - let intermediate1 = INTERMEDIATE1.init( - storage1 - .try_create_intermediate([("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"), - ); + let (intermediate1, power_event_receivers1) = storage1 + .try_create_intermediate([("Pd1", policy_sender1)]) + .expect("Failed to create intermediate storage"); + let intermediate1 = INTERMEDIATE1.init(intermediate1); static REFERENCED1: StaticCell< type_c_service::wrapper::backing::ReferencedStorage< @@ -332,6 +353,12 @@ async fn task(spawner: Spawner) { static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); + let event_receiver1 = ArrayPortEventReceivers::new( + state1.create_interrupt_receiver(), + power_event_receivers1, + &referenced1.pd_controller, + &storage1.cfu_device, + ); static CONTROLLER1: StaticCell> = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); static WRAPPER1: StaticCell = StaticCell::new(); @@ -423,8 +450,8 @@ async fn task(spawner: Spawner) { ) .expect("Failed to create type-c service task"), ); - spawner.spawn(wrapper_task(wrapper0).expect("Failed to create wrapper0 task")); - spawner.spawn(wrapper_task(wrapper1).expect("Failed to create wrapper1 task")); + spawner.spawn(wrapper_task(event_receiver0, wrapper0).expect("Failed to create wrapper0 task")); + spawner.spawn(wrapper_task(event_receiver1, wrapper1).expect("Failed to create wrapper1 task")); spawner.spawn(opm_task(controller_context, [state0, state1]).expect("Failed to create opm task")); } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index d43822e11..21c20cb4f 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -23,6 +23,7 @@ use type_c_interface::port::PortRegistration; use type_c_interface::service::event::PortEvent as ServicePortEvent; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; +use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; use type_c_service::wrapper::proxy::PowerProxyDevice; const CHANNEL_CAPACITY: usize = 4; @@ -67,10 +68,26 @@ type PowerPolicyServiceType = Mutex< type ServiceType = Service<'static>; #[embassy_executor::task(pool_size = 3)] -async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { +async fn controller_task( + mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, + wrapper: &'static mock_controller::Wrapper<'static>, +) { loop { - if let Err(e) = wrapper.process_next_event().await { - error!("Error processing wrapper: {e:#?}"); + let event = event_receiver.wait_event().await; + + let output = wrapper + .process_event( + &mut event_receiver.sink_ready_timeout, + &mut event_receiver.cfu_event_receiver, + event, + ) + .await; + if let Err(e) = output { + error!("Error processing event: {e:?}"); + } + let output = output.unwrap(); + if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { + error!("Error finalizing output: {e:#?}"); } } } @@ -106,11 +123,10 @@ async fn task(spawner: Spawner) { static INTERMEDIATE0: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); - let intermediate0 = INTERMEDIATE0.init( - storage0 - .try_create_intermediate([("Pd0", policy_sender0)]) - .expect("Failed to create intermediate storage"), - ); + let (intermediate0, power_event_receivers0) = storage0 + .try_create_intermediate([("Pd0", policy_sender0)]) + .expect("Failed to create intermediate storage"); + let intermediate0 = INTERMEDIATE0.init(intermediate0); static REFERENCED0: StaticCell>> = StaticCell::new(); @@ -122,6 +138,12 @@ async fn task(spawner: Spawner) { static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); + let event_receiver0 = ArrayPortEventReceivers::new( + state0.create_interrupt_receiver(), + power_event_receivers0, + &referenced0.pd_controller, + &storage0.cfu_device, + ); static CONTROLLER0: StaticCell> = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); static WRAPPER0: StaticCell = StaticCell::new(); @@ -152,11 +174,10 @@ async fn task(spawner: Spawner) { static INTERMEDIATE1: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); - let intermediate1 = INTERMEDIATE1.init( - storage1 - .try_create_intermediate([("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"), - ); + let (intermediate1, power_event_receivers1) = storage1 + .try_create_intermediate([("Pd1", policy_sender1)]) + .expect("Failed to create intermediate storage"); + let intermediate1 = INTERMEDIATE1.init(intermediate1); static REFERENCED1: StaticCell>> = StaticCell::new(); @@ -168,6 +189,12 @@ async fn task(spawner: Spawner) { static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); + let event_receiver1 = ArrayPortEventReceivers::new( + state1.create_interrupt_receiver(), + power_event_receivers1, + &referenced1.pd_controller, + &storage1.cfu_device, + ); static CONTROLLER1: StaticCell> = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); static WRAPPER1: StaticCell = StaticCell::new(); @@ -198,11 +225,10 @@ async fn task(spawner: Spawner) { static INTERMEDIATE2: StaticCell< IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, > = StaticCell::new(); - let intermediate2 = INTERMEDIATE2.init( - storage2 - .try_create_intermediate([("Pd2", policy_sender2)]) - .expect("Failed to create intermediate storage"), - ); + let (intermediate2, power_event_receivers2) = storage2 + .try_create_intermediate([("Pd2", policy_sender2)]) + .expect("Failed to create intermediate storage"); + let intermediate2 = INTERMEDIATE2.init(intermediate2); static REFERENCED2: StaticCell>> = StaticCell::new(); @@ -214,6 +240,12 @@ async fn task(spawner: Spawner) { static STATE2: StaticCell = StaticCell::new(); let state2 = STATE2.init(mock_controller::ControllerState::new()); + let event_receiver2 = ArrayPortEventReceivers::new( + state2.create_interrupt_receiver(), + power_event_receivers2, + &referenced2.pd_controller, + &storage2.cfu_device, + ); static CONTROLLER2: StaticCell> = StaticCell::new(); let controller2 = CONTROLLER2.init(Mutex::new(mock_controller::Controller::new(state2))); static WRAPPER2: StaticCell = StaticCell::new(); @@ -283,9 +315,9 @@ async fn task(spawner: Spawner) { .expect("Failed to create type-c service task"), ); - spawner.spawn(controller_task(wrapper0).expect("Failed to create controller0 task")); - spawner.spawn(controller_task(wrapper1).expect("Failed to create controller1 task")); - spawner.spawn(controller_task(wrapper2).expect("Failed to create controller2 task")); + spawner.spawn(controller_task(event_receiver0, wrapper0).expect("Failed to create controller0 task")); + spawner.spawn(controller_task(event_receiver1, wrapper1).expect("Failed to create controller1 task")); + spawner.spawn(controller_task(event_receiver2, wrapper2).expect("Failed to create controller2 task")); const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index b778be04c..cc97c19bd 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -7,7 +7,7 @@ use embedded_usb_pd::{Error, ado::Ado}; use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; -use log::{debug, info, trace}; +use log::{debug, info}; use power_policy_interface::capability::PowerCapability; use type_c_interface::port::SystemPowerState; @@ -32,6 +32,10 @@ impl ControllerState { } } + pub fn create_interrupt_receiver(&self) -> InterruptReceiver<'_> { + InterruptReceiver { events: &self.events } + } + /// Simulate a connection pub async fn connect(&self, role: PowerRole, capability: PowerCapability, debug: bool, unconstrained: bool) { let mut status = PortStatus::new(); @@ -99,15 +103,11 @@ impl Default for ControllerState { pub struct Controller<'a> { state: &'a ControllerState, - events: PortEventBitfield, } impl<'a> Controller<'a> { pub fn new(state: &'a ControllerState) -> Self { - Self { - state, - events: PortEventBitfield::none(), - } + Self { state } } /// Function to demonstrate calling functions directly on the controller @@ -116,22 +116,21 @@ impl<'a> Controller<'a> { } } -impl type_c_interface::port::Controller for Controller<'_> { - type BusError = (); +pub struct InterruptReceiver<'a> { + events: &'a Signal, +} - async fn wait_port_event(&mut self) -> Result<(), Error> { - let events = self.state.events.wait().await; - trace!("Port event: {events:#?}"); - self.events = self.events.union(events); - Ok(()) +impl type_c_service::wrapper::event_receiver::InterruptReceiver for InterruptReceiver<'_> { + async fn wait_interrupt(&mut self) -> [PortEventBitfield; N] { + let events = self.events.wait().await; + let mut result = [PortEventBitfield::none(); N]; + result[0] = events; + result } +} - async fn clear_port_events(&mut self, _port: LocalPortId) -> Result> { - let events = self.events; - debug!("Clear port events: {events:#?}"); - self.events = PortEventBitfield::none(); - Ok(events) - } +impl type_c_interface::port::Controller for Controller<'_> { + type BusError = (); async fn get_port_status(&mut self, _port: LocalPortId) -> Result> { debug!("Get port status: {:#?}", *self.state.status.lock().await); diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index 6c515d635..f4099228d 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -16,7 +16,6 @@ use embedded_services::{GlobalRawMutex, intrusive_list}; pub mod event; -use crate::port::event::PortEventBitfield; use crate::service::event::PortEvent as ServicePortEvent; /// Length of the Other VDM data @@ -532,19 +531,6 @@ pub trait Controller { /// Type of error returned by the bus type BusError; - /// Wait for a port event to occur - /// # Implementation guide - /// This function should be drop safe. - /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. - fn wait_port_event(&mut self) -> impl Future>>; - /// Returns and clears current events for the given port - /// # Implementation guide - /// This function should be drop safe. - /// Any intermediate side effects must be undone if the returned [`Future`] is dropped before completing. - fn clear_port_events( - &mut self, - port: LocalPortId, - ) -> impl Future>>; /// Returns the port status fn get_port_status(&mut self, port: LocalPortId) -> impl Future>>; diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index b7e0e5483..35d9faa25 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -1,9 +1,7 @@ -use ::tps6699x::registers::field_sets::IntEventBus1; use ::tps6699x::registers::{PdCcPullUp, PpExtVbusSw, PpIntVbusSw}; use ::tps6699x::{PORT0, PORT1, TPS66993_NUM_PORTS, TPS66994_NUM_PORTS}; use bitfield::bitfield; use bitflags::bitflags; -use core::array::from_fn; use core::future::Future; use core::iter::zip; use core::num::NonZeroU8; @@ -18,7 +16,7 @@ use embedded_usb_pd::type_c::Current as TypecCurrent; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{DataRole, Error, LocalPortId, PdError, PlugOrientation, PowerRole}; use tps6699x::MAX_SUPPORTED_PORTS; -use tps6699x::asynchronous::embassy as tps6699x_drv; +use tps6699x::asynchronous::embassy::{self as tps6699x_drv, interrupt}; use tps6699x::asynchronous::fw_update::UpdateTarget; use tps6699x::asynchronous::fw_update::{ BorrowedUpdater, BorrowedUpdaterInProgress, disable_all_interrupts, enable_port0_interrupts, @@ -28,6 +26,7 @@ use tps6699x::command::{ vdms::{INITIATOR_WAIT_TIME_MS, MAX_NUM_DATA_OBJECTS, Version}, }; use tps6699x::fw_update::UpdateConfig as FwUpdateConfig; +use tps6699x::registers::field_sets::IntEventBus1; use tps6699x::registers::port_config::TypeCStateMachine; use type_c_interface::port::event::PortEventBitfield; use type_c_interface::port::{ @@ -51,7 +50,7 @@ struct FwUpdateState<'a, M: RawMutex, B: I2c> { /// /// This value is never read, only used to keep the interrupt guard alive #[allow(dead_code)] - guards: [Option>; MAX_SUPPORTED_PORTS], + guards: [Option>; MAX_SUPPORTED_PORTS], } /// The method used to control USB capabilities. @@ -83,7 +82,6 @@ pub struct Config { } pub struct Tps6699x<'a, M: RawMutex, B: I2c> { - port_events: heapless::Vec, tps6699x: tps6699x_drv::Tps6699x<'a, M, B>, update_state: Option>, /// Firmware update configuration @@ -106,7 +104,6 @@ impl<'a, M: RawMutex, B: I2c> Tps6699x<'a, M, B> { } else { Some(Self { // num_ports validated by branch - port_events: heapless::Vec::from_iter((0..num_ports).map(|_| PortEventBitfield::none())), tps6699x, update_state: None, fw_update_config, @@ -204,116 +201,6 @@ impl Controller for Tps6699x<'_, M, B> { Ok(()) } - /// Wait for an event on any port - async fn wait_port_event(&mut self) -> Result<(), Error> { - let interrupts = self - .tps6699x - .wait_interrupt_any(false, from_fn(|_| IntEventBus1::all())) - .await; - - for (interrupt, event) in zip(interrupts.iter(), self.port_events.iter_mut()) { - if *interrupt == IntEventBus1::new_zero() { - continue; - } - - { - if interrupt.plug_event() { - debug!("Event: Plug event"); - event.status.set_plug_inserted_or_removed(true); - } - if interrupt.source_caps_received() { - debug!("Event: Source Caps received"); - event.status.set_source_caps_received(true); - } - - if interrupt.sink_ready() { - debug!("Event: Sink ready"); - event.status.set_sink_ready(true); - } - - if interrupt.new_consumer_contract() { - debug!("Event: New contract as consumer, PD controller act as Sink"); - // Port is consumer and power negotiation is complete - event.status.set_new_power_contract_as_consumer(true); - } - - if interrupt.new_provider_contract() { - debug!("Event: New contract as provider, PD controller act as source"); - // Port is provider and power negotiation is complete - event.status.set_new_power_contract_as_provider(true); - } - - if interrupt.power_swap_completed() { - debug!("Event: power swap completed"); - event.status.set_power_swap_completed(true); - } - - if interrupt.data_swap_completed() { - debug!("Event: data swap completed"); - event.status.set_data_swap_completed(true); - } - - if interrupt.am_entered() { - debug!("Event: alt mode entered"); - event.status.set_alt_mode_entered(true); - } - - if interrupt.hard_reset() { - debug!("Event: hard reset"); - event.status.set_pd_hard_reset(true); - } - - if interrupt.crossbar_error() { - debug!("Event: crossbar error"); - event.notification.set_usb_mux_error_recovery(true); - } - - if interrupt.usvid_mode_entered() { - debug!("Event: user svid mode entered"); - event.notification.set_custom_mode_entered(true); - } - - if interrupt.usvid_mode_exited() { - debug!("Event: usvid mode exited"); - event.notification.set_custom_mode_exited(true); - } - - if interrupt.usvid_attention_vdm_received() { - debug!("Event: user svid attention vdm received"); - event.notification.set_custom_mode_attention_received(true); - } - - if interrupt.usvid_other_vdm_received() { - debug!("Event: user svid other vdm received"); - event.notification.set_custom_mode_other_vdm_received(true); - } - - if interrupt.discover_mode_completed() { - debug!("Event: discover mode completed"); - event.notification.set_discover_mode_completed(true); - } - - if interrupt.dp_sid_status_updated() { - debug!("Event: dp sid status updated"); - event.notification.set_dp_status_update(true); - } - - if interrupt.alert_message_received() { - debug!("Event: alert message received"); - event.notification.set_alert(true); - } - } - } - Ok(()) - } - - /// Returns and clears current events for the given port - async fn clear_port_events(&mut self, port: LocalPortId) -> Result> { - Ok(core::mem::take( - self.port_events.get_mut(port.0 as usize).ok_or(PdError::InvalidPort)?, - )) - } - /// Returns the current status of the port async fn get_port_status(&mut self, port: LocalPortId) -> Result> { let status = self.tps6699x.get_port_status(port).await?; @@ -899,3 +786,114 @@ bitfield! { /// TI FW version pub u32, ti_fw_version, set_ti_fw_version: 63, 32; } + +pub struct InterruptReceiver<'a, M: RawMutex, BUS: I2c> { + interrupt_receiver: interrupt::InterruptReceiver<'a, M, BUS>, +} + +impl<'a, M: RawMutex, BUS: I2c> InterruptReceiver<'a, M, BUS> { + pub fn new(interrupt_receiver: interrupt::InterruptReceiver<'a, M, BUS>) -> Self { + Self { interrupt_receiver } + } +} + +impl<'a, M: RawMutex, BUS: I2c> crate::wrapper::event_receiver::InterruptReceiver + for InterruptReceiver<'a, M, BUS> +{ + async fn wait_interrupt(&mut self) -> [PortEventBitfield; MAX_SUPPORTED_PORTS] { + let interrupts = self.interrupt_receiver.wait_any(false).await; + let mut port_events = [PortEventBitfield::none(); MAX_SUPPORTED_PORTS]; + for (interrupt, event) in zip(interrupts.iter(), port_events.iter_mut()) { + if *interrupt == IntEventBus1::new_zero() { + continue; + } + + if interrupt.plug_event() { + debug!("Event: Plug event"); + event.status.set_plug_inserted_or_removed(true); + } + if interrupt.source_caps_received() { + debug!("Event: Source Caps received"); + event.status.set_source_caps_received(true); + } + + if interrupt.sink_ready() { + debug!("Event: Sink ready"); + event.status.set_sink_ready(true); + } + + if interrupt.new_consumer_contract() { + debug!("Event: New contract as consumer, PD controller act as Sink"); + // Port is consumer and power negotiation is complete + event.status.set_new_power_contract_as_consumer(true); + } + + if interrupt.new_provider_contract() { + debug!("Event: New contract as provider, PD controller act as source"); + // Port is provider and power negotiation is complete + event.status.set_new_power_contract_as_provider(true); + } + + if interrupt.power_swap_completed() { + debug!("Event: power swap completed"); + event.status.set_power_swap_completed(true); + } + + if interrupt.data_swap_completed() { + debug!("Event: data swap completed"); + event.status.set_data_swap_completed(true); + } + + if interrupt.am_entered() { + debug!("Event: alt mode entered"); + event.status.set_alt_mode_entered(true); + } + + if interrupt.hard_reset() { + debug!("Event: hard reset"); + event.status.set_pd_hard_reset(true); + } + + if interrupt.crossbar_error() { + debug!("Event: crossbar error"); + event.notification.set_usb_mux_error_recovery(true); + } + + if interrupt.usvid_mode_entered() { + debug!("Event: user svid mode entered"); + event.notification.set_custom_mode_entered(true); + } + + if interrupt.usvid_mode_exited() { + debug!("Event: usvid mode exited"); + event.notification.set_custom_mode_exited(true); + } + + if interrupt.usvid_attention_vdm_received() { + debug!("Event: user svid attention vdm received"); + event.notification.set_custom_mode_attention_received(true); + } + + if interrupt.usvid_other_vdm_received() { + debug!("Event: user svid other vdm received"); + event.notification.set_custom_mode_other_vdm_received(true); + } + + if interrupt.discover_mode_completed() { + debug!("Event: discover mode completed"); + event.notification.set_discover_mode_completed(true); + } + + if interrupt.dp_sid_status_updated() { + debug!("Event: dp sid status updated"); + event.notification.set_dp_status_update(true); + } + + if interrupt.alert_message_received() { + debug!("Event: alert message received"); + event.notification.set_alert(true); + } + } + port_events + } +} diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index 7fa30c01f..12d975c72 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -5,7 +5,7 @@ pub mod task; pub mod util; pub mod wrapper; -use core::future::Future; +use core::iter::Enumerate; use type_c_interface::port::event::{ PortEvent, PortEventBitfield, PortNotificationEventBitfield, PortStatusEventBitfield, @@ -13,98 +13,55 @@ use type_c_interface::port::event::{ /// Struct to convert port events into a stream of events #[derive(Clone)] -pub struct PortEventStreamer> { - /// Current port index being processed - port_index: Option, - /// Iterator over pending ports - port_iter: Iter, +pub struct PortEventStreamer> { + /// Iterator over pending event bitfields + port_iter: Enumerate, /// Notification to be streamed - pending_notifications: Option, + pending_notifications: Option<(usize, PortNotificationEventBitfield)>, } -impl> PortEventStreamer { +impl> PortEventStreamer { /// Create new PortEventStreamer - /// - /// Returns none if there are no pending ports to process. pub fn new(port_iter: Iter) -> Self { Self { - port_index: None, - port_iter, + port_iter: port_iter.enumerate(), pending_notifications: None, } } } -impl> PortEventStreamer { - /// Get the next port event, calls the closure if it needs to get pending events for the current port. - pub async fn next>, F: FnMut(usize) -> Fut>( - &mut self, - mut f: F, - ) -> Result, E> { - loop { - let port_index = if let Some(index) = self.port_index { - index - } else if let Some(next_port) = self.port_iter.next() { - // First time this function is called, get our starting port index - self.port_index = Some(next_port); - next_port - } else { - // No pending ports to process - return Ok(None); - }; - - let mut advance_port = false; - let mut ret = None; +impl> Iterator for PortEventStreamer { + type Item = (usize, PortEvent); - if let Some(mut pending) = self.pending_notifications { - if let Some(port_event) = pending.next() { - // Return a single notification - self.pending_notifications = Some(pending); - ret = Some((port_index, port_event)); - } else { - // Done with pending notifications, continue to the next port - advance_port = true; - self.pending_notifications = None; - } - } else { - // Haven't read port events yet - let event = f(port_index).await?; + fn next(&mut self) -> Option { + loop { + // Handle any pending notifications first + if let Some((port_index, pending)) = &mut self.pending_notifications + && let Some(port_event) = pending.next() + { + // Return a single notification + return Some((*port_index, port_event)); + } - if event.notification != PortNotificationEventBitfield::none() { - // Have pending notifications to stream as events, store those for the next loop/call to this function - self.pending_notifications = Some(event.notification); + // No pending notifications, fetch the next port event + if let Some((port_index, event_bitfield)) = self.port_iter.next() { + // Pending notifications for this port if there are any + if event_bitfield.notification != PortNotificationEventBitfield::none() { + self.pending_notifications = Some((port_index, event_bitfield.notification)); } else { - // No pending notifications, we can advance to the next port - advance_port = true; self.pending_notifications = None; } - if event.status != PortStatusEventBitfield::none() { - // Return the port status changed event first if there is one - ret = Some((port_index, PortEvent::StatusChanged(event.status))); - } - } - - if advance_port { - if let Some(next_port) = self.port_iter.next() { - // Move to the next port - self.port_index = Some(next_port); - } else if ret.is_none() { - // Don't have any more ports to process - // And we didn't have any events to return, we're done - return Ok(None); - } else { - // This is the last port, but we have an event to return - // We'll have to return none on the next call, achieve this by setting port_index to None - // The next call will call next() on the pending port iterator which will return None - self.port_index = None; + // Return a status changed event if there is one + if event_bitfield.status != PortStatusEventBitfield::none() { + return Some((port_index, PortEvent::StatusChanged(event_bitfield.status))); } + } else { + // No more ports to process, we're done + return None; } - // Return the event if we have one, otherwise loop to get the next event - if ret.is_some() { - return Ok(ret); - } + //Otherwise loop, to handle any remaining notifications } } } @@ -113,7 +70,6 @@ impl> PortEventStreamer { #[allow(clippy::unwrap_used)] mod tests { use super::*; - use core::sync::atomic::AtomicBool; /// Utility function to create a PortStatusChanged event fn status_changed(plug_event: bool, power_contract: bool, sink_ready: bool) -> PortStatusEventBitfield { @@ -133,155 +89,110 @@ mod tests { } /// Test iterating over port status changed events - #[tokio::test] - async fn test_port_status_changed() { - let mut streamer = PortEventStreamer::new(0..3); + #[test] + fn test_port_status_changed() { + let events = [ + status_changed(true, true, true).into(), + status_changed(true, false, true).into(), + status_changed(false, false, true).into(), + ]; + let mut streamer = PortEventStreamer::new(events.iter().copied()); - let event = streamer - .next::<(), _, _>(async |_| Ok(status_changed(true, true, true).into())) - .await; assert_eq!( - event, - Ok(Some((0, PortEvent::StatusChanged(status_changed(true, true, true))))) + streamer.next(), + Some((0, PortEvent::StatusChanged(status_changed(true, true, true)))) ); - - let event = streamer - .next::<(), _, _>(async |_| Ok(status_changed(true, false, true).into())) - .await; assert_eq!( - event, - Ok(Some((1, PortEvent::StatusChanged(status_changed(true, false, true))))) + streamer.next(), + Some((1, PortEvent::StatusChanged(status_changed(true, false, true)))) ); - - let event = streamer - .next::<(), _, _>(async |_| Ok(status_changed(false, false, true).into())) - .await; assert_eq!( - event, - Ok(Some((2, PortEvent::StatusChanged(status_changed(false, false, true))))) + streamer.next(), + Some((2, PortEvent::StatusChanged(status_changed(false, false, true)))) ); - - let event = streamer - .next::<(), _, _>(async |_| Ok(status_changed(false, false, true).into())) - .await; - assert_eq!(event, Ok(None)); + assert_eq!(streamer.next(), None); } /// Test iterating over port notifications - #[tokio::test] - async fn test_port_notification() { - let mut streamer = PortEventStreamer::new(0..1); - let event = streamer - .next::<(), _, _>(async |_| Ok(notification(true, true).into())) - .await; - assert_eq!(event, Ok(Some((0, PortEvent::Alert)))); - - let event = streamer - .next::<(), _, _>(async |_| Ok(notification(true, true).into())) - .await; - assert_eq!(event, Ok(Some((0, PortEvent::DiscoverModeCompleted)))); - - let event = streamer - .next::<(), _, _>(async |_| Ok(notification(true, true).into())) - .await; - assert_eq!(event, Ok(None)); + #[test] + fn test_port_notification() { + let events = [notification(true, true).into()]; + let mut streamer = PortEventStreamer::new(events.iter().copied()); + + assert_eq!(streamer.next(), Some((0, PortEvent::Alert))); + assert_eq!(streamer.next(), Some((0, PortEvent::DiscoverModeCompleted))); + assert_eq!(streamer.next(), None); } - /// Test the the final port with no pending notifications - #[tokio::test] - async fn test_last_notifications() { - let mut streamer = PortEventStreamer::new(0..1); - - // Test p0 events + /// Test the final port with no pending notifications + #[test] + fn test_last_notifications() { let p0_event = status_changed(true, true, true).into(); - let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; + let events = [p0_event]; + let mut streamer = PortEventStreamer::new(events.iter().copied()); + assert_eq!( - event, - Ok(Some((0, PortEvent::StatusChanged(status_changed(true, true, true))))) + streamer.next(), + Some((0, PortEvent::StatusChanged(status_changed(true, true, true)))) ); - - let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; - assert_eq!(event, Ok(None)); + assert_eq!(streamer.next(), None); } /// Test iterating over both status and notification events - #[tokio::test] - async fn test_port_event() { - let mut streamer = PortEventStreamer::new(0..2); - - // Test p0 events + #[test] + fn test_port_event() { let p0_event = PortEventBitfield { status: status_changed(true, true, true), notification: notification(true, false), }; - - let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; - assert_eq!( - event, - Ok(Some((0, PortEvent::StatusChanged(status_changed(true, true, true))))) - ); - - let event = streamer.next::<(), _, _>(async |_| Ok(p0_event)).await; - assert_eq!(event, Ok(Some((0, PortEvent::Alert)))); - - // Test p1 events let p1_event = PortEventBitfield { status: status_changed(false, true, false), notification: notification(false, true), }; + let events = [p0_event, p1_event]; + let mut streamer = PortEventStreamer::new(events.iter().copied()); - let event = streamer.next::<(), _, _>(async |_| Ok(p1_event)).await; assert_eq!( - event, - Ok(Some((1, PortEvent::StatusChanged(status_changed(false, true, false))))) + streamer.next(), + Some((0, PortEvent::StatusChanged(status_changed(true, true, true)))) ); - - let event = streamer.next::<(), _, _>(async |_| Ok(p1_event)).await; - assert_eq!(event, Ok(Some((1, PortEvent::DiscoverModeCompleted)))); - - let event = streamer.next::<(), _, _>(async |_| Ok(p1_event)).await; - assert_eq!(event, Ok(None)); + assert_eq!(streamer.next(), Some((0, PortEvent::Alert))); + assert_eq!( + streamer.next(), + Some((1, PortEvent::StatusChanged(status_changed(false, true, false)))) + ); + assert_eq!(streamer.next(), Some((1, PortEvent::DiscoverModeCompleted))); + assert_eq!(streamer.next(), None); } /// Test no pending ports - #[tokio::test] - async fn test_no_pending_ports() { - let mut streamer = PortEventStreamer::new(0..0); - let event = streamer - .next::<(), _, _>(async |_| Ok(status_changed(true, true, true).into())) - .await; - assert_eq!(event, Ok(None)); + #[test] + fn test_no_pending_ports() { + let events: [PortEventBitfield; 0] = []; + let mut streamer = PortEventStreamer::new(events.iter().copied()); + + assert_eq!(streamer.next(), None); } /// Test a port with a pending event with no actual event - #[tokio::test] - async fn test_empty_event() { - let mut streamer = PortEventStreamer::new(0..1); - let event = streamer.next::<(), _, _>(async |_| Ok(PortEventBitfield::none())).await; - assert_eq!(event, Ok(None)); + #[test] + fn test_empty_event() { + let events = [PortEventBitfield::none()]; + let mut streamer = PortEventStreamer::new(events.iter().copied()); + + assert_eq!(streamer.next(), None); } /// Test advancing to the next port when there are no events - #[tokio::test] - async fn test_skip_no_pending() { - let mut streamer = PortEventStreamer::new(0..2); - let event = streamer - .next::<(), _, _>(async |_| { - static HAVE_EVENTS: AtomicBool = AtomicBool::new(false); - let have_events = HAVE_EVENTS.load(core::sync::atomic::Ordering::Relaxed); - let event = Ok(status_changed(have_events, have_events, have_events).into()); - HAVE_EVENTS.store(true, core::sync::atomic::Ordering::Relaxed); - event - }) - .await; + #[test] + fn test_skip_no_pending() { + let events = [PortEventBitfield::none(), status_changed(true, true, true).into()]; + let mut streamer = PortEventStreamer::new(events.iter().copied()); + assert_eq!( - event, - Ok(Some((1, PortEvent::StatusChanged(status_changed(true, true, true))))) + streamer.next(), + Some((1, PortEvent::StatusChanged(status_changed(true, true, true)))) ); - - let event = streamer - .next::<(), _, _>(async |_| Ok(status_changed(false, false, false).into())) - .await; - assert_eq!(event, Ok(None)); + assert_eq!(streamer.next(), None); } } diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 7ee663fba..24e613598 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -2,42 +2,17 @@ //! //! TODO: update this documentation when the type-C service is refactored //! -use core::{array::from_fn, ops::Range}; +use core::array::from_fn; use cfu_service::component::CfuDevice; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; -use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::event; use type_c_interface::port::{ControllerId, PortRegistration, PortStatus, event::PortStatusEventBitfield}; -use crate::{ - PortEventStreamer, - wrapper::{ - cfu, - proxy::{PowerProxyChannel, PowerProxyDevice, PowerProxyReceiver}, - }, -}; - -/// Internal per-controller state -#[derive(Clone)] -pub struct ControllerState { - /// If we're currently doing a firmware update - pub(crate) fw_update_state: cfu::FwUpdateState, - /// State used to keep track of where we are as we turn the event bitfields into a stream of events - pub(crate) port_event_streaming_state: Option>>, -} - -impl Default for ControllerState { - fn default() -> Self { - Self { - fw_update_state: cfu::FwUpdateState::Idle, - port_event_streaming_state: None, - } - } -} +use crate::wrapper::proxy::{PowerProxyChannel, PowerProxyDevice, PowerProxyReceiver}; /// Service registration objects pub struct Registration<'a, M: RawMutex> { @@ -59,7 +34,7 @@ pub struct Storage<'a, const N: usize, M: RawMutex> { context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, pd_ports: [PortRegistration; N], - cfu_device: CfuDevice, + pub cfu_device: CfuDevice, power_proxy_channels: [PowerProxyChannel; N], } @@ -83,7 +58,7 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn try_create_intermediate>( &self, power_policy_init: [(&'static str, S); N], - ) -> Option> { + ) -> Option<(IntermediateStorage<'_, N, M, S>, [PowerProxyReceiver<'_>; N])> { IntermediateStorage::try_from_storage(self, power_policy_init) } } @@ -98,8 +73,6 @@ pub struct PortState, /// Sender to send events to the power policy service pub(crate) power_policy_sender: S, } @@ -109,7 +82,6 @@ impl> PortState< Self { status: PortStatus::default(), sw_status_event: PortStatusEventBitfield::default(), - sink_ready_deadline: None, power_policy_sender, } } @@ -124,13 +96,15 @@ pub struct IntermediateStorage< > { storage: &'a Storage<'a, N, M>, ports: [Port<'a, M, S>; N], - power_proxy_receivers: [Mutex>; N], } impl<'a, const N: usize, M: RawMutex, S: event::Sender> IntermediateStorage<'a, N, M, S> { - fn try_from_storage(storage: &'a Storage<'a, N, M>, power_policy_init: [(&'static str, S); N]) -> Option { + fn try_from_storage( + storage: &'a Storage<'a, N, M>, + power_policy_init: [(&'static str, S); N], + ) -> Option<(Self, [PowerProxyReceiver<'a>; N])> { let mut ports = heapless::Vec::<_, N>::new(); let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); @@ -145,16 +119,16 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender, > { intermediate: &'a IntermediateStorage<'a, N, M, S>, - pd_controller: type_c_interface::port::Device<'a>, + pub pd_controller: type_c_interface::port::Device<'a>, power_devices: [&'a Mutex>; N], } @@ -211,7 +185,6 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender> { pub(crate) registration: Registration<'a, M>, pub(crate) ports: &'a [Port<'a, M, S>], - pub(crate) power_receivers: &'a [Mutex>], } diff --git a/type-c-service/src/wrapper/cfu.rs b/type-c-service/src/wrapper/cfu.rs index 4e206ded5..5e6af0294 100644 --- a/type-c-service/src/wrapper/cfu.rs +++ b/type-c-service/src/wrapper/cfu.rs @@ -1,13 +1,11 @@ //! CFU message bridge //! TODO: remove this once we have a more generic FW update implementation -use crate::wrapper::backing::ControllerState; +use crate::wrapper::event_receiver::CfuEventReceiver; use cfu_service::component::{InternalResponseData, RequestData}; -use embassy_futures::select::{Either, select}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_services::{debug, error}; use type_c_interface::port::Controller; -use super::message::EventCfu; use super::*; /// Current state of the firmware update process @@ -103,22 +101,22 @@ where async fn process_abort_update( &self, - controller_state: &mut ControllerState, + event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, ) -> InternalResponseData { // abort the update process match controller.abort_fw_update().await { Ok(_) => { debug!("FW update aborted successfully"); - controller_state.fw_update_state = FwUpdateState::Idle; + event_receiver.fw_update_state = FwUpdateState::Idle; } Err(Error::Pd(e)) => { error!("Failed to abort FW update: {:?}", e); - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; } Err(Error::Bus(_)) => { error!("Failed to abort FW update, bus error"); - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; } } @@ -128,7 +126,7 @@ where /// Process a GiveContent command async fn process_give_content( &self, - controller_state: &mut ControllerState, + event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, content: &FwUpdateContentCommand, ) -> InternalResponseData { @@ -162,14 +160,14 @@ where } // Need to start the update - self.fw_update_ticker.lock().await.reset(); + event_receiver.reset_ticker(); match controller.start_fw_update().await { Ok(_) => { debug!("FW update started successfully"); } Err(Error::Pd(e)) => { error!("Failed to start FW update: {:?}", e); - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( content.header.sequence_num, CfuUpdateContentResponseStatus::ErrorPrepare, @@ -177,7 +175,7 @@ where } Err(Error::Bus(_)) => { error!("Failed to start FW update, bus error"); - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( content.header.sequence_num, CfuUpdateContentResponseStatus::ErrorPrepare, @@ -185,7 +183,7 @@ where } } - controller_state.fw_update_state = FwUpdateState::InProgress(0); + event_receiver.fw_update_state = FwUpdateState::InProgress(0); } match controller @@ -215,16 +213,16 @@ where match controller.finalize_fw_update().await { Ok(_) => { debug!("FW update finalized successfully"); - controller_state.fw_update_state = FwUpdateState::Idle; + event_receiver.fw_update_state = FwUpdateState::Idle; } Err(Error::Pd(e)) => { error!("Failed to finalize FW update: {:?}", e); - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; return Self::create_offer_rejection(); } Err(Error::Bus(_)) => { error!("Failed to finalize FW update, bus error"); - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; return Self::create_offer_rejection(); } } @@ -237,8 +235,8 @@ where } /// Process a CFU tick - pub async fn process_cfu_tick(&self, controller_state: &mut ControllerState, controller: &mut D::Inner) { - match controller_state.fw_update_state { + pub async fn process_cfu_tick(&self, event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner) { + match event_receiver.fw_update_state { FwUpdateState::Idle => { // No FW update in progress, nothing to do return; @@ -246,7 +244,7 @@ where FwUpdateState::InProgress(ticks) => { if ticks + 1 < DEFAULT_FW_UPDATE_TIMEOUT_TICKS { trace!("CFU tick: {}", ticks); - controller_state.fw_update_state = FwUpdateState::InProgress(ticks + 1); + event_receiver.fw_update_state = FwUpdateState::InProgress(ticks + 1); return; } else { error!("FW update timed out after {} ticks", ticks); @@ -258,7 +256,7 @@ where }; // Update timed out, attempt to exit the FW update - controller_state.fw_update_state = FwUpdateState::Recovery; + event_receiver.fw_update_state = FwUpdateState::Recovery; match controller.abort_fw_update().await { Ok(_) => { debug!("FW update aborted successfully"); @@ -273,17 +271,17 @@ where } } - controller_state.fw_update_state = FwUpdateState::Idle; + event_receiver.fw_update_state = FwUpdateState::Idle; } /// Process a CFU command pub async fn process_cfu_command( &self, - controller_state: &mut ControllerState, + event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, command: &RequestData, ) -> InternalResponseData { - if controller_state.fw_update_state == FwUpdateState::Recovery { + if event_receiver.fw_update_state == FwUpdateState::Recovery { debug!("FW update in recovery state, rejecting command"); return InternalResponseData::ComponentBusy; } @@ -299,11 +297,11 @@ where } RequestData::GiveContent(content) => { debug!("Got GiveContent"); - self.process_give_content(controller_state, controller, content).await + self.process_give_content(event_receiver, controller, content).await } RequestData::AbortUpdate => { debug!("Got AbortUpdate"); - self.process_abort_update(controller_state, controller).await + self.process_abort_update(event_receiver, controller).await } RequestData::FinalizeUpdate => { debug!("Got FinalizeUpdate"); @@ -328,39 +326,4 @@ where pub async fn send_cfu_response(&self, response: InternalResponseData) { self.registration.cfu_device.send_response(response).await; } - - /// Wait for a CFU command - /// - /// Returns None if the FW update ticker has ticked - /// DROP SAFETY: No state that needs to be restored - pub async fn wait_cfu_command(&self) -> EventCfu { - // Only lock long enough to grab our state - let fw_update_state = self.controller_state.lock().await.fw_update_state; - match fw_update_state { - FwUpdateState::Idle => { - // No FW update in progress, just wait for a command - EventCfu::Request(self.registration.cfu_device.wait_request().await) - } - FwUpdateState::InProgress(_) => { - match select( - self.registration.cfu_device.wait_request(), - self.fw_update_ticker.lock().await.next(), - ) - .await - { - Either::First(command) => EventCfu::Request(command), - Either::Second(_) => { - debug!("FW update ticker ticked"); - EventCfu::RecoveryTick - } - } - } - FwUpdateState::Recovery => { - // Recovery state, wait for the next attempt to recover the device - self.fw_update_ticker.lock().await.next().await; - debug!("FW update ticker ticked"); - EventCfu::RecoveryTick - } - } - } } diff --git a/type-c-service/src/wrapper/event_receiver.rs b/type-c-service/src/wrapper/event_receiver.rs new file mode 100644 index 000000000..a3f967b4a --- /dev/null +++ b/type-c-service/src/wrapper/event_receiver.rs @@ -0,0 +1,279 @@ +//! This module contains event receiver types for the controller wrapper. +use core::array; +use core::future::pending; +use core::pin::pin; +use embassy_futures::select::{Either, Either5, select, select_slice, select5}; +use embassy_time::{Instant, Ticker, Timer}; +use embedded_services::{debug, trace}; +use embedded_usb_pd::LocalPortId; + +use crate::PortEventStreamer; +use crate::wrapper::DEFAULT_FW_UPDATE_TICK_INTERVAL_MS; +use crate::wrapper::cfu::FwUpdateState; +use crate::wrapper::message::{Event, EventCfu, LocalPortEvent, PowerPolicyCommand}; +use crate::wrapper::proxy::PowerProxyReceiver; +use type_c_interface::port::event::{PortEvent, PortEventBitfield, PortStatusEventBitfield}; +use type_c_interface::port::{self}; + +/// Trait used for receiving interrupt from the controller. +pub trait InterruptReceiver { + /// Wait for the next interrupt event. + fn wait_interrupt(&mut self) -> impl Future; +} + +/// Struct to receive and stream port events from the controller. +pub struct PortEventReceiver> { + /// Receiver for the controller's interrupt events + receiver: Receiver, + /// Port event streaming state + streaming_state: Option>>, +} + +impl> PortEventReceiver { + /// Create a new instance + pub fn new(receiver: Receiver) -> Self { + Self { + receiver, + streaming_state: None, + } + } + + /// Wait for the next port event + pub async fn wait_next(&mut self) -> LocalPortEvent { + loop { + let streaming_state = if let Some(streaming_state) = &mut self.streaming_state { + // Yield to ensure we don't monopolize the executor + embassy_futures::yield_now().await; + streaming_state + } else { + let events = self.receiver.wait_interrupt().await; + self.streaming_state.insert(PortEventStreamer::new(events.into_iter())) + }; + + if let Some((port_index, event)) = streaming_state.next() { + return LocalPortEvent { + port: LocalPortId(port_index as u8), + event, + }; + } else { + self.streaming_state = None; + } + } + } +} + +/// Struct to receive power policy messages. +pub struct ArrayPowerProxyEventReceiver<'device, const N: usize> { + receivers: [PowerProxyReceiver<'device>; N], +} + +impl<'device, const N: usize> ArrayPowerProxyEventReceiver<'device, N> { + /// Create a new array power proxy event receiver + pub fn new(receivers: [PowerProxyReceiver<'device>; N]) -> Self { + Self { receivers } + } + + /// Wait for the next power policy command + pub async fn wait_next(&mut self) -> PowerPolicyCommand { + let mut futures = heapless::Vec::<_, N>::new(); + for receiver in self.receivers.iter_mut() { + // Size is fixed at compile time, so no chance of overflow + let _ = futures.push(async { receiver.receive().await }); + } + + // DROP SAFETY: Select over drop safe futures + let (request, local_id) = select_slice(pin!(futures.as_mut_slice())).await; + trace!("Power command: device{} {:#?}", local_id, request); + PowerPolicyCommand { + port: LocalPortId(local_id as u8), + request, + } + } + + /// Temporary function until the conversion to direct function calls is complete + pub async fn send_response( + &mut self, + port: LocalPortId, + response: power_policy_interface::psu::InternalResponseData, + ) -> Result<(), ()> { + self.receivers.get_mut(port.0 as usize).ok_or(())?.send(response).await; + Ok(()) + } +} + +/// Struct to receive CFU events. +pub struct CfuEventReceiver { + /// FW update ticker used to check for timeouts and recovery attempts + fw_update_ticker: Ticker, + /// CFU device used for firmware updates + cfu_device: &'static cfu_service::component::CfuDevice, + pub fw_update_state: FwUpdateState, +} + +impl CfuEventReceiver { + /// Create a new CFU event receiver + pub fn new(cfu_device: &'static cfu_service::component::CfuDevice) -> Self { + Self { + fw_update_ticker: Ticker::every(embassy_time::Duration::from_millis(DEFAULT_FW_UPDATE_TICK_INTERVAL_MS)), + cfu_device, + fw_update_state: FwUpdateState::Idle, + } + } + + /// Wait for the next CFU event + pub async fn wait_next(&mut self) -> EventCfu { + match self.fw_update_state { + FwUpdateState::Idle => { + // No FW update in progress, just wait for a command + EventCfu::Request(self.cfu_device.wait_request().await) + } + FwUpdateState::InProgress(_) => { + match select(self.cfu_device.wait_request(), self.fw_update_ticker.next()).await { + Either::First(command) => EventCfu::Request(command), + Either::Second(_) => { + debug!("FW update ticker ticked"); + EventCfu::RecoveryTick + } + } + } + FwUpdateState::Recovery => { + // Recovery state, wait for the next attempt to recover the device + self.fw_update_ticker.next().await; + debug!("FW update ticker ticked"); + EventCfu::RecoveryTick + } + } + } + + /// Reset the firmware update ticker + pub fn reset_ticker(&mut self) { + self.fw_update_ticker.reset(); + } +} + +/// Struct to receive sink ready timeout events. +pub struct SinkReadyTimeoutEvent { + timeouts: [Option; N], +} + +impl SinkReadyTimeoutEvent { + /// Create a new instance + pub fn new() -> Self { + Self { timeouts: [None; N] } + } + + /// Set a timeout for a specific port + pub fn set_timeout(&mut self, port: LocalPortId, new_timeout: Instant) { + let index = port.0 as usize; + if let Some(timeout) = self.timeouts.get_mut(index) { + *timeout = Some(new_timeout); + } + } + + /// Clear the timeout for a specific port + pub fn clear_timeout(&mut self, port: LocalPortId) { + let index = port.0 as usize; + if let Some(timeout) = self.timeouts.get_mut(index) { + *timeout = None; + } + } + + pub fn get_timeout(&self, port: LocalPortId) -> Option { + let index = port.0 as usize; + self.timeouts.get(index).copied().flatten() + } + + /// Wait for a sink ready timeout and return the port that has timed out. + /// + /// DROP SAFETY: No state to restore + pub async fn wait_next(&mut self) -> LocalPortId { + let mut futures = heapless::Vec::<_, N>::new(); + for (i, timeout) in self.timeouts.iter().enumerate() { + let timeout = *timeout; + // Size is fixed at compile time, so no chance of overflow + let _ = futures.push(async move { + if let Some(timeout) = timeout { + Timer::at(timeout).await; + debug!("Port{}: Sink ready timeout reached", i); + } else { + pending::<()>().await; + } + }); + } + + // DROP SAFETY: Select over drop safe futures + let (_, port_index) = select_slice(pin!(futures.as_mut_slice())).await; + if let Some(timeout) = self.timeouts.get_mut(port_index) { + *timeout = None; + } + LocalPortId(port_index as u8) + } +} + +impl Default for SinkReadyTimeoutEvent { + fn default() -> Self { + Self::new() + } +} + +/// Struct used for containing controller event receivers. +pub struct ArrayPortEventReceivers<'device, const N: usize, PortInterrupts: InterruptReceiver> { + /// Port event receiver + pub port_events: PortEventReceiver, + /// Power proxy event receiver + pub power_proxies: ArrayPowerProxyEventReceiver<'device, N>, + /// PD controller + pub pd_controller: &'static port::Device<'static>, + /// CFU event receiver + pub cfu_event_receiver: CfuEventReceiver, + /// Sink ready timeout event receiver + pub sink_ready_timeout: SinkReadyTimeoutEvent, +} + +impl<'device, const N: usize, PortInterrupts: InterruptReceiver> + ArrayPortEventReceivers<'device, N, PortInterrupts> +{ + /// Create a new instance + pub fn new( + port_interrupts: PortInterrupts, + power_proxies: [PowerProxyReceiver<'device>; N], + pd_controller: &'static port::Device<'static>, + cfu_device: &'static cfu_service::component::CfuDevice, + ) -> Self { + Self { + port_events: PortEventReceiver::new(port_interrupts), + power_proxies: ArrayPowerProxyEventReceiver::new(power_proxies), + pd_controller, + cfu_event_receiver: CfuEventReceiver::new(cfu_device), + sink_ready_timeout: SinkReadyTimeoutEvent::new(), + } + } + + /// Wait for the next port event from any port. + /// + /// Returns the local port ID and the event bitfield. + pub async fn wait_event(&mut self) -> Event<'device> { + match select5( + self.port_events.wait_next(), + self.power_proxies.wait_next(), + self.pd_controller.receive(), + self.cfu_event_receiver.wait_next(), + self.sink_ready_timeout.wait_next(), + ) + .await + { + Either5::First(event) => Event::PortEvent(event), + Either5::Second(command) => Event::PowerPolicyCommand(command), + Either5::Third(request) => Event::ControllerCommand(request), + Either5::Fourth(cfu_event) => Event::CfuEvent(cfu_event), + Either5::Fifth(port) => { + let mut status_event = PortStatusEventBitfield::none(); + status_event.set_sink_ready(true); + Event::PortEvent(LocalPortEvent { + port, + event: PortEvent::StatusChanged(status_event), + }) + } + } + } +} diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 95ee565d7..574ab5424 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -5,44 +5,37 @@ //! * Type-C: [`type_c_interface::port::Command`] //! * CFU: [`cfu_service::Request`] //! # Event loop -//! This struct follows a standard wait/process/finalize event loop. -//! -//! [`ControllerWrapper::wait_next`] returns [`message::Event`] and does not perform any actions on the controller -//! aside from reading pending events. +//! This struct follows a standard process/finalize event loop. //! //! [`ControllerWrapper::process_event`] reads any additional data relevant to the event and returns [`message::Output`]. //! e.g. port status for a port status changed event, VDM data for a VDM event //! -//! [`ControllerWrapper::process_event`] consumes [`message::Output`] and responds to any deferred requests, performs +//! [`ControllerWrapper::finalize`] consumes [`message::Output`] and responds to any deferred requests, performs //! any caching/buffering of data, and notifies the type-C service implementation of the event if needed. -use core::array::from_fn; -use core::future::pending; -use core::ops::{DerefMut, Range}; +use core::ops::DerefMut; -use crate::wrapper::backing::{ControllerState, PortState}; +use crate::wrapper::backing::PortState; +use crate::wrapper::event_receiver::{ArrayPowerProxyEventReceiver, CfuEventReceiver, SinkReadyTimeoutEvent}; use cfu_service::CfuClient; -use embassy_futures::select::{Either, Either5, select, select_array, select5}; use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::mutex::Mutex; use embassy_sync::signal::Signal; use embassy_time::Instant; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::event; use embedded_services::sync::Lockable; -use embedded_services::{debug, error, info, trace}; +use embedded_services::{error, info, trace}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{Error, LocalPortId, PdError}; use type_c_interface::port::event::PortEvent as InterfacePortEvent; use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventData as ServicePortEventData}; -use crate::PortEventStreamer; use crate::wrapper::message::*; -use crate::wrapper::proxy::PowerProxyReceiver; pub mod backing; mod cfu; pub mod config; mod dp; +pub mod event_receiver; pub mod message; mod pd; mod power; @@ -82,20 +75,14 @@ pub struct ControllerWrapper< controller: &'device D, /// Trait object for validating firmware versions fw_version_validator: V, - /// FW update ticker used to check for timeouts and recovery attempts - fw_update_ticker: Mutex, /// Registration information for services pub registration: backing::Registration<'device, M>, /// SW port status event signal sw_status_event: Signal, /// General config config: config::Config, - /// Power proxy receivers - power_proxy_receivers: &'device [Mutex>], /// Port proxies pub ports: &'device [backing::Port<'device, M, S>], - /// Controller state - controller_state: Mutex, } impl< @@ -124,14 +111,9 @@ where controller, config, fw_version_validator, - fw_update_ticker: Mutex::new(embassy_time::Ticker::every(embassy_time::Duration::from_millis( - DEFAULT_FW_UPDATE_TICK_INTERVAL_MS, - ))), registration: backing.registration, sw_status_event: Signal::new(), - power_proxy_receivers: backing.power_receivers, ports: backing.ports, - controller_state: Mutex::new(backing::ControllerState::default()), } } @@ -210,8 +192,9 @@ where } /// Process port status changed events - async fn process_port_status_changed<'b>( + async fn process_port_status_changed<'b, const N: usize>( &self, + sink_ready_timeout: &mut SinkReadyTimeoutEvent, controller: &mut D::Inner, local_port_id: LocalPortId, status_event: PortStatusEventBitfield, @@ -248,7 +231,8 @@ where } self.check_sink_ready_timeout( - &mut port_state, + sink_ready_timeout, + &port_state.status, &status, local_port_id, status_event.new_power_contract_as_consumer(), @@ -315,125 +299,16 @@ where .map_err(Error::Pd) } - /// Wait for a pending port event - /// - /// DROP SAFETY: No state that needs to be restored - async fn wait_port_pending( - &self, - controller_state: &ControllerState, - controller: &mut D::Inner, - ) -> Result>, Error<::BusError>> { - if controller_state.fw_update_state.in_progress() { - // Don't process events while firmware update is in progress - debug!("Firmware update in progress, ignoring port events"); - return pending().await; - } - - let streaming_state = controller_state.port_event_streaming_state.clone(); - if let Some(streamer) = streaming_state { - // If we're converting the bitfields into an event stream yield first to prevent starving other tasks - embassy_futures::yield_now().await; - Ok(streamer) - } else { - // Otherwise, wait for the next port event - // DROP SAFETY: Safe as long as `wait_port_event` is drop safe - match select(controller.wait_port_event(), async { - self.sw_status_event.wait().await; - Ok::<_, Error<::BusError>>(()) - }) - .await - { - Either::First(r) => r?, - Either::Second(_) => (), - }; - Ok(PortEventStreamer::new(0..self.registration.num_ports())) - } - } - - /// Wait for the next event - pub async fn wait_next(&self) -> Result, Error<::BusError>> { - // This loop is to ensure that if we finish streaming events we go back to waiting for the next port event - loop { - let event = { - let controller_state = self.controller_state.lock().await; - let mut controller = self.controller.lock().await; - // DROP SAFETY: Select over drop safe functions - select5( - self.wait_port_pending(&controller_state, &mut controller), - self.wait_power_command(), - self.registration.pd_controller.receive(), - self.wait_cfu_command(), - self.wait_sink_ready_timeout(), - ) - .await - }; - match event { - Either5::First(stream) => { - let mut stream = stream?; - if let Some((port_index, event)) = stream - .next::::BusError>, _, _>(async |port_index| { - // Combine the event read from the controller with any software generated events - // Acquire the locks first to centralize the awaits here - let mut controller = self.controller.lock().await; - let mut port_state = self - .ports - .get(port_index) - .ok_or(Error::Pd(PdError::InvalidPort))? - .state - .lock() - .await; - - let hw_event = controller.clear_port_events(LocalPortId(port_index as u8)).await?; - - // No more awaits, modify state here for drop safety - let sw_event = - core::mem::replace(&mut port_state.sw_status_event, PortStatusEventBitfield::none()); - Ok(hw_event.union(sw_event.into())) - }) - .await? - { - let port_id = LocalPortId(port_index as u8); - self.controller_state.lock().await.port_event_streaming_state = Some(stream); - return Ok(Event::PortEvent(LocalPortEvent { port: port_id, event })); - } else { - self.controller_state.lock().await.port_event_streaming_state = None; - } - } - Either5::Second((port, request)) => { - return Ok(Event::PowerPolicyCommand(PowerPolicyCommand { port, request })); - } - Either5::Third(request) => return Ok(Event::ControllerCommand(request)), - Either5::Fourth(event) => return Ok(Event::CfuEvent(event)), - Either5::Fifth(port) => { - // Sink ready timeout event - debug!("Port{0}: Sink ready timeout", port.0); - self.ports - .get(port.0 as usize) - .ok_or(Error::Pd(PdError::InvalidPort))? - .state - .lock() - .await - .sink_ready_deadline = None; - let mut status_event = PortStatusEventBitfield::none(); - status_event.set_sink_ready(true); - return Ok(Event::PortEvent(LocalPortEvent { - port, - event: type_c_interface::port::event::PortEvent::StatusChanged(status_event), - })); - } - } - } - } - /// Process a port notification - async fn process_port_event<'b>( + async fn process_port_event<'b, const N: usize>( &self, + sink_ready_timeout: &mut SinkReadyTimeoutEvent, controller: &mut D::Inner, event: LocalPortEvent, ) -> Result, Error<::BusError>> { match event.event { InterfacePortEvent::StatusChanged(status_event) => { - self.process_port_status_changed(controller, event.port, status_event) + self.process_port_status_changed(sink_ready_timeout, controller, event.port, status_event) .await } InterfacePortEvent::Alert => { @@ -464,36 +339,40 @@ where /// Top-level processing function /// Only call this fn from one place in a loop. Otherwise a deadlock could occur. - pub async fn process_event<'b>( + pub async fn process_event<'b, const N: usize>( &self, + sink_ready_timeout: &mut SinkReadyTimeoutEvent, + cfu_event_receiver: &mut CfuEventReceiver, event: Event<'b>, ) -> Result, Error<::BusError>> { let mut controller = self.controller.lock().await; - let mut controller_state = self.controller_state.lock().await; match event { - Event::PortEvent(port_event) => self.process_port_event(&mut controller, port_event).await, + Event::PortEvent(port_event) => { + self.process_port_event(sink_ready_timeout, &mut controller, port_event) + .await + } Event::PowerPolicyCommand(PowerPolicyCommand { port, request }) => { let response = self - .process_power_command(&mut controller_state, &mut controller, port, &request) + .process_power_command(cfu_event_receiver, &mut controller, port, &request) .await; Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) } Event::ControllerCommand(request) => { let response = self - .process_pd_command(&mut controller_state, &mut controller, &request.command) + .process_pd_command(cfu_event_receiver, &mut controller, &request.command) .await; Ok(Output::ControllerCommand(OutputControllerCommand { request, response })) } Event::CfuEvent(event) => match event { EventCfu::Request(request) => { let response = self - .process_cfu_command(&mut controller_state, &mut controller, &request) + .process_cfu_command(cfu_event_receiver, &mut controller, &request) .await; Ok(Output::CfuResponse(response)) } EventCfu::RecoveryTick => { // FW Update tick, process timeouts and recovery attempts - self.process_cfu_tick(&mut controller_state, &mut controller).await; + self.process_cfu_tick(cfu_event_receiver, &mut controller).await; Ok(Output::CfuRecovery) } }, @@ -501,7 +380,11 @@ where } /// Event loop finalize - pub async fn finalize<'b>(&self, output: Output<'b>) -> Result<(), Error<::BusError>> { + pub async fn finalize<'b, const N: usize>( + &self, + event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, + output: Output<'b>, + ) -> Result<(), Error<::BusError>> { match output { Output::Nop => Ok(()), Output::PortStatusChanged(OutputPortStatusChanged { @@ -512,13 +395,10 @@ where Output::PdAlert(OutputPdAlert { port, ado }) => self.finalize_pd_alert(port, ado).await, Output::Vdm(vdm) => self.finalize_vdm(vdm).await.map_err(Error::Pd), Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response }) => { - self.power_proxy_receivers - .get(port.0 as usize) - .ok_or(Error::Pd(PdError::InvalidPort))? - .lock() + event_receiver + .send_response(port, response) .await - .send(response) - .await; + .map_err(|_| Error::Pd(PdError::Failed))?; Ok(()) } Output::ControllerCommand(OutputControllerCommand { request, response }) => { @@ -541,18 +421,17 @@ where } /// Combined processing and finialization function - pub async fn process_and_finalize_event<'b>( + pub async fn process_and_finalize_event<'b, const N: usize>( &self, + sink_ready_timeout: &mut SinkReadyTimeoutEvent, + cfu_event_receiver: &mut CfuEventReceiver, + power_event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, event: Event<'b>, ) -> Result<(), Error<::BusError>> { - let output = self.process_event(event).await?; - self.finalize(output).await - } - - /// Combined processing function - pub async fn process_next_event(&self) -> Result<(), Error<::BusError>> { - let event = self.wait_next().await?; - self.process_and_finalize_event(event).await + let output = self + .process_event(sink_ready_timeout, cfu_event_receiver, event) + .await?; + self.finalize(power_event_receiver, output).await } /// Register all devices with their respective services diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index 8ff97cb79..671aab1a8 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -1,5 +1,5 @@ -use crate::wrapper::backing::ControllerState; -use embassy_time::{Duration, Timer}; +use crate::wrapper::event_receiver::SinkReadyTimeoutEvent; +use embassy_time::Duration; use embedded_services::debug; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; use embedded_usb_pd::ucsi::{self, lpm}; @@ -24,27 +24,28 @@ where /// After accepting a sink contract (new contract as consumer), the PD spec guarantees that the /// source will be available to provide power after `tPSTransition`. This allows us to handle transitions /// even for controllers that might not always broadcast sink ready events. - pub(super) fn check_sink_ready_timeout( + pub(super) fn check_sink_ready_timeout( &self, - port_state: &mut PortState, - status: &PortStatus, + sink_ready_timeout: &mut SinkReadyTimeoutEvent, + previous_status: &PortStatus, + new_status: &PortStatus, port: LocalPortId, new_contract: bool, sink_ready: bool, ) -> Result<(), PdError> { - let contract_changed = port_state.status.available_sink_contract != status.available_sink_contract; - let deadline = &mut port_state.sink_ready_deadline; + let contract_changed = previous_status.available_sink_contract != new_status.available_sink_contract; + let timeout = sink_ready_timeout.get_timeout(port); // Don't start the timeout if the sink has signaled it's ready or if the contract didn't change. // The latter ensures that soft resets won't continually reset the ready timeout debug!( "Port{}: Check sink ready: new_contract={:?}, sink_ready={:?}, contract_changed={:?}, deadline={:?}", - port.0, new_contract, sink_ready, contract_changed, deadline, + port.0, new_contract, sink_ready, contract_changed, timeout, ); if new_contract && !sink_ready && contract_changed { // Start the timeout // Double the spec maximum transition time to provide a safety margin for hardware/controller delays or out-of-spec controllers. - let timeout_ms = if status.epr { + let timeout_ms = if new_status.epr { T_PS_TRANSITION_EPR_MS } else { T_PS_TRANSITION_SPR_MS @@ -53,41 +54,16 @@ where .0 * 2; debug!("Port{}: Sink ready timeout started for {}ms", port.0, timeout_ms); - *deadline = Some(Instant::now() + Duration::from_millis(timeout_ms as u64)); - } else if deadline.is_some() - && (!status.is_connected() || status.available_sink_contract.is_none() || sink_ready) + sink_ready_timeout.set_timeout(port, Instant::now() + Duration::from_millis(timeout_ms as u64)); + } else if timeout.is_some() + && (!new_status.is_connected() || new_status.available_sink_contract.is_none() || sink_ready) { debug!("Port{}: Sink ready timeout cleared", port.0); - *deadline = None; + sink_ready_timeout.clear_timeout(port); } Ok(()) } - /// Wait for a sink ready timeout and return the port that has timed out. - /// - /// DROP SAFETY: No state to restore - pub(super) async fn wait_sink_ready_timeout(&self) -> LocalPortId { - let futures: [_; MAX_SUPPORTED_PORTS] = from_fn(|i| async move { - let Some(port) = self.ports.get(i) else { - pending::<()>().await; - return; - }; - - let deadline = port.state.lock().await.sink_ready_deadline; - if let Some(deadline) = deadline { - Timer::at(deadline).await; - debug!("Port{}: Sink ready timeout reached", i); - port.state.lock().await.sink_ready_deadline = None; - } else { - pending::<()>().await; - } - }); - - // DROP SAFETY: Select over drop safe futures - let (_, port_index) = select_array(futures).await; - LocalPortId(port_index as u8) - } - /// Process a request to set the maximum sink voltage for a port async fn process_set_max_sink_voltage( &self, @@ -127,11 +103,11 @@ where /// Handle a port command async fn process_port_command( &self, - controller_state: &mut ControllerState, + cfu_event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, command: &port::PortCommand, ) -> Response<'static> { - if controller_state.fw_update_state.in_progress() { + if cfu_event_receiver.fw_update_state.in_progress() { debug!("FW update in progress, ignoring port command"); return port::Response::Port(Err(PdError::Busy)); } @@ -322,11 +298,11 @@ where async fn process_controller_command( &self, - controller_state: &mut ControllerState, + cfu_event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, command: &port::InternalCommandData, ) -> Response<'static> { - if controller_state.fw_update_state.in_progress() { + if cfu_event_receiver.fw_update_state.in_progress() { debug!("FW update in progress, ignoring controller command"); return port::Response::Controller(Err(PdError::Busy)); } @@ -358,14 +334,14 @@ where /// Handle a PD controller command pub(super) async fn process_pd_command( &self, - controller_state: &mut ControllerState, + cfu_event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, command: &port::Command, ) -> Response<'static> { match command { - port::Command::Port(command) => self.process_port_command(controller_state, controller, command).await, + port::Command::Port(command) => self.process_port_command(cfu_event_receiver, controller, command).await, port::Command::Controller(command) => { - self.process_controller_command(controller_state, controller, command) + self.process_controller_command(cfu_event_receiver, controller, command) .await } port::Command::Lpm(_) => port::Response::Ucsi(ucsi::Response { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index bc683ab06..8e2022fab 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -1,17 +1,12 @@ //! Module contain power-policy related message handling -use core::pin::pin; - -use embassy_futures::select::select_slice; use embedded_services::debug; +use crate::wrapper::config::UnconstrainedSink; use power_policy_interface::capability::{ConsumerPowerCapability, ProviderPowerCapability, PsuType}; use power_policy_interface::psu::CommandData as PowerCommand; use power_policy_interface::psu::Error as PowerError; use power_policy_interface::psu::{CommandData, InternalResponseData, ResponseData}; -use crate::wrapper::backing::ControllerState; -use crate::wrapper::config::UnconstrainedSink; - use super::*; impl< @@ -97,42 +92,17 @@ where Ok(()) } - /// Wait for a power command - /// - /// Returns (local port ID, deferred request) - /// DROP SAFETY: Call to a select over drop safe futures - pub(super) async fn wait_power_command(&self) -> (LocalPortId, CommandData) { - let mut futures = heapless::Vec::<_, MAX_SUPPORTED_PORTS>::new(); - for receiver in self.power_proxy_receivers { - // TODO: check this at compile time - if futures - .push(async { - let mut lock = receiver.lock().await; - lock.receive().await - }) - .is_err() - { - error!("Futures vec overflow"); - } - } - - // DROP SAFETY: Select over drop safe futures - let (request, local_id) = select_slice(pin!(futures.as_mut_slice())).await; - trace!("Power command: device{} {:#?}", local_id, request); - (LocalPortId(local_id as u8), request) - } - /// Process a power command /// Returns no error because this is a top-level function pub(super) async fn process_power_command( &self, - controller_state: &mut ControllerState, + cfu_event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, port: LocalPortId, command: &CommandData, ) -> InternalResponseData { trace!("Processing power command: device{} {:#?}", port.0, command); - if controller_state.fw_update_state.in_progress() { + if cfu_event_receiver.fw_update_state.in_progress() { debug!("Port{}: Firmware update in progress", port.0); return Err(PowerError::Busy); } From cf169a5d14bca5eac19eeb9a4ec18405639a867e Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Fri, 24 Apr 2026 14:43:55 -0700 Subject: [PATCH 68/79] Add cargo deny ignore for deprecated bare-metal dependency (#817) The bare-metal crate has been [deprecated](https://github.com/rust-embedded/bare-metal/pull/49), however an upstream dependency [cortex-m](https://github.com/rust-embedded/cortex-m/issues/651) takes a dependency on it. Ignore the advisory for now until cortex-m migrates away from it. --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index d856b23a2..9bdc71bda 100644 --- a/deny.toml +++ b/deny.toml @@ -78,6 +78,7 @@ ignore = [ { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, { id = "RUSTSEC-2023-0089", reason = "this is a deprecation warning for a dependency of a dependency. https://github.com/jamesmunns/postcard/issues/223 tracks fixing the dependency; until that's resolved, we can accept the deprecated code as it has no known vulnerabilities." }, { id = "RUSTSEC-2025-0141", reason = "bincode is unmaintained, planning on migrating to an alternative." }, + { id = "RUSTSEC-2026-0110", reason = "bare-metal is deprecated and archived, which cortex-m has a dependency on. Need cortex-m to migrate away from it." }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. From 681159de6ac06a28cd6dff7493df294a4245b2c9 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Fri, 24 Apr 2026 15:11:31 -0700 Subject: [PATCH 69/79] power-policy charger: remove intrusive list and move to array registration (#798) power-policy: Charger v0.2.0 refactor - Moved away from intrusive lists for storing chargers, instead using an array based approach - Switched to PSU style charger event receivers for receiving events - Added charger event types and structures in power-policy-interface. - Introduced charger state management with transitions based on PSU state changes. - **OEM now handles charger state transitions by calling the associated state methods to drive the charger state forward** - One exception to this is when the power policy gets a new contract, but the charger is still unpowered. The charger will be forced into an initialized state. - Created tests for charger state transitions and event handling. Resolves #778 , #779 , and #761 --- Cargo.lock | 3 +- docs/power-policy.md | 76 ++- examples/pico-de-gallo/Cargo.lock | 1 - examples/rt685s-evk/Cargo.lock | 1 - examples/rt685s-evk/src/bin/type_c.rs | 16 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 16 +- examples/std/Cargo.lock | 2 +- examples/std/Cargo.toml | 2 + examples/std/src/bin/power_policy.rs | 260 ++++++++- examples/std/src/bin/type_c/service.rs | 21 +- examples/std/src/bin/type_c/ucsi.rs | 16 +- examples/std/src/bin/type_c/unconstrained.rs | 16 +- power-policy-interface/Cargo.toml | 4 +- power-policy-interface/src/charger.rs | 511 ------------------ power-policy-interface/src/charger/event.rs | 44 ++ power-policy-interface/src/charger/mock.rs | 62 +++ power-policy-interface/src/charger/mod.rs | 206 +++++++ power-policy-interface/src/charger/tests.rs | 285 ++++++++++ power-policy-service/Cargo.toml | 1 + power-policy-service/src/charger.rs | 43 ++ power-policy-service/src/lib.rs | 1 + power-policy-service/src/psu.rs | 4 +- power-policy-service/src/service/consumer.rs | 59 +- power-policy-service/src/service/context.rs | 93 ---- power-policy-service/src/service/mod.rs | 40 +- .../src/service/registration.rs | 20 +- power-policy-service/src/service/task.rs | 61 ++- power-policy-service/tests/common/mock.rs | 78 ++- power-policy-service/tests/common/mod.rs | 13 +- 29 files changed, 1232 insertions(+), 723 deletions(-) delete mode 100644 power-policy-interface/src/charger.rs create mode 100644 power-policy-interface/src/charger/event.rs create mode 100644 power-policy-interface/src/charger/mock.rs create mode 100644 power-policy-interface/src/charger/mod.rs create mode 100644 power-policy-interface/src/charger/tests.rs create mode 100644 power-policy-service/src/charger.rs delete mode 100644 power-policy-service/src/service/context.rs diff --git a/Cargo.lock b/Cargo.lock index 3a78a9095..b38c692c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1505,8 +1505,8 @@ name = "power-policy-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", + "critical-section", "defmt 0.3.100", - "embassy-futures", "embassy-sync", "embedded-batteries-async", "embedded-services", @@ -1523,6 +1523,7 @@ dependencies = [ "embassy-futures", "embassy-sync", "embassy-time", + "embedded-batteries-async", "embedded-services", "env_logger", "heapless 0.8.0", diff --git a/docs/power-policy.md b/docs/power-policy.md index 2b99d8f9b..bccddbd6b 100644 --- a/docs/power-policy.md +++ b/docs/power-policy.md @@ -65,4 +65,78 @@ These messages are used to communicate through the comms serivce. The given device has stopped consuming. #### `ConsumerConnected(device ID, max power)` -The given device has started consuming at the specified power. \ No newline at end of file +The given device has started consuming at the specified power. + +## Charger State Machine + +The power policy service manages charger hardware through a dedicated state machine. The charger tracks two pieces of state: an `InternalState` representing the charger's power and initialization status, and an optional cached `ConsumerPowerCapability` representing the current power policy configuration. + +### Charger State + +The charger can be in one of the following states (`charger::InternalState`): + +* `Unpowered`: The charger hardware is not powered and cannot communicate. +* `Powered(Init)`: The charger is powered and uninitialized. +* `Powered(PsuAttached)`: The charger is powered, initialized, and a PSU is attached (ready to charge). +* `Powered(PsuDetached)`: The charger is powered, initialized, but no PSU is attached. + +```mermaid +stateDiagram-v2 + [*] --> Unpowered + + Unpowered --> Init : on_ready_success() + + state Powered { + Init --> PsuAttached : on_initialized(Attached) + Init --> PsuDetached : on_initialized(Detached) + PsuAttached --> PsuDetached : PsuStateChange(Detached) + PsuDetached --> PsuAttached : PsuStateChange(Attached) + } + + Init --> Unpowered : on_timeout() / on_ready_failure() + PsuAttached --> Unpowered : on_timeout() / on_ready_failure() + PsuDetached --> Unpowered : on_timeout() / on_ready_failure() +``` + +### Charger Events + +Events originate from the charger hardware driver and are broadcast to the power policy service via `charger::event::EventData`: + +#### `PsuStateChange(PsuState)` +Reports that the PSU attachment state has changed. Transitions between `Powered(PsuAttached)` and `Powered(PsuDetached)`. Invalid from `Unpowered` or `Powered(Init)`. Typically this is tied to a GPIO or some other signal that represents that the charger hardware has detected a PSU state change. + +### Charger State Transition Methods + +State transitions are driven by the driver (trait implementer) by calling methods on `charger::State` (accessed via `state_mut()`). The power policy service does not call these methods directly — it is the driver's responsibility to advance the state machine based on hardware observations: + +#### `on_ready_success()` +Transitions an `Unpowered` charger to `Powered(Init)`. No-op if already powered. Should be called by the driver after confirming the charger hardware is powered and can communicate. + +#### `on_ready_failure()` +Transitions a `Powered` charger to `Unpowered`. Capability is preserved for diagnostics. No-op if already unpowered. Should be called by the driver after `is_ready()` fails. + +#### `on_initialized(PsuState)` +Transitions `Powered(Init)` to either `Powered(PsuAttached)` or `Powered(PsuDetached)` based on the PSU state. Returns an error if not in `Powered(Init)`. Should be called by the driver after hardware initialization completes, like at the end of the `init_charger()` trait method call. + +#### `on_timeout()` +Unconditionally transitions to `Unpowered` and clears the cached capability. Should be called by the driver when a communication timeout with the charger hardware is detected. + +#### `on_psu_state_change(PsuState)` +Transitions between `Powered(PsuAttached)` and `Powered(PsuDetached)`. Returns an error if not in a valid powered state. Typically this is called when a GPIO or some other signal that represents that the charger hardware has detected a PSU state change. + +### Policy Integration + +The charger state machine integrates with the PSU consumer selection flow: + +* **Consumer connected**: When the policy selects a new power consumer, it calls `attach_handler(capability)` on each registered charger and caches the capability via `on_policy_attach()`. If a charger is `Unpowered` at this point, the policy calls `is_ready()` followed by `init_charger()` to bring it to an initialized state before attaching. +* **Consumer switched/disconnected**: When the policy disconnects a consumer (to switch or because no consumer is available), it calls `detach_handler()` on each powered charger and clears the cached capability via `on_policy_detach()`. + +### Charger Trait + +Device drivers implement the `charger::Charger` trait (which extends `embedded_batteries_async::charger::Charger`) to integrate with the power policy. The driver is responsible for driving the charger state machine — it must call the appropriate `State` transition methods (via `state_mut()`) based on hardware observations: + +* `init_charger()` — Initializes charger hardware. Returns the current `PsuState`. The driver should call `state_mut().on_initialized(psu_state)` after successful initialization. +* `attach_handler(capability)` — Called when the power policy attaches to a new, best consumer. Should program the hardware for the requested capability. +* `detach_handler()` — Called when the power policy detaches the current PSU consumer, either to switch consumers or because the PSU was disconnected. +* `is_ready()` — Verifies the charger is powered and can communicate. Has a default implementation that returns `Ok(())`. The driver should call `state_mut().on_ready_success()` or `state_mut().on_ready_failure()` to advance the state machine accordingly. +* `state()` / `state_mut()` — Access the charger's `State`. Used by the driver to call state transition methods. The `State` struct fields are private to the `power-policy-interface` crate; transitions must go through the provided methods (e.g. `on_ready_success()`, `on_initialized()`, `on_timeout()`). \ No newline at end of file diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 50eb7f61a..270a48222 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -1251,7 +1251,6 @@ name = "power-policy-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "embassy-futures", "embassy-sync", "embedded-batteries-async", "embedded-services", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index f27612195..30ba8371a 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1081,7 +1081,6 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", - "embassy-futures", "embassy-sync", "embedded-batteries-async", "embedded-services", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index c70e57de0..f2bdeb380 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -20,7 +20,7 @@ use embedded_services::event::MapSender; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; @@ -44,6 +44,7 @@ const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); type DeviceType = Mutex>; +type ChargerType = power_policy_interface::charger::mock::ChargerType; bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; @@ -86,7 +87,7 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1>, + ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1, ChargerType, 0>, >, >; @@ -128,10 +129,10 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: InterruptProc #[embassy_executor::task] async fn power_policy_task( - psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { - power_policy_service::service::task::task(psu_events, power_policy).await; + power_policy_service::service::task::psu_task(psu_events, power_policy).await; } #[embassy_executor::task] @@ -263,18 +264,15 @@ async fn main(spawner: Spawner) { let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - let power_policy_registration = ArrayRegistration { psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + chargers: [], service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( power_policy_registration, - power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -299,7 +297,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.spawn( power_policy_task( - ArrayEventReceivers::new( + PsuEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 57100233d..65a961bad 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -23,7 +23,7 @@ use embedded_services::event::MapSender; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; @@ -55,6 +55,7 @@ impl type_c_service::wrapper::FwOfferValidator for Validator { } type DeviceType = Mutex>; +type ChargerType = power_policy_interface::charger::mock::ChargerType; type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; @@ -84,7 +85,7 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1>, + ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1, ChargerType, 0>, >, >; @@ -212,10 +213,10 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( - psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { - power_policy_service::service::task::task(psu_events, power_policy).await; + power_policy_service::service::task::psu_task(psu_events, power_policy).await; } #[embassy_executor::task] @@ -340,9 +341,6 @@ async fn main(spawner: Spawner) { )); // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< PubSubChannel, @@ -356,13 +354,13 @@ async fn main(spawner: Spawner) { let power_policy_registration = ArrayRegistration { psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + chargers: [], service_senders: [power_policy_sender], }; static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( power_policy_registration, - power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -387,7 +385,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.spawn( power_policy_task( - ArrayEventReceivers::new( + PsuEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 834116373..07fe2465f 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1048,7 +1048,6 @@ name = "power-policy-interface" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "embassy-futures", "embassy-sync", "embedded-batteries-async", "embedded-services", @@ -1279,6 +1278,7 @@ dependencies = [ "embassy-executor", "embassy-sync", "embassy-time", + "embedded-batteries-async", "embedded-cfu-protocol", "embedded-services", "embedded-usb-pd", diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 39d358ae9..5bca75b3c 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -53,6 +53,8 @@ critical-section = { version = "1.1", features = ["std"] } debug-service = { path = "../../debug-service" } debug-service-messages = { path = "../../debug-service-messages" } +embedded-batteries-async = "0.3" + [lib] name = "std_examples" path = "src/lib/lib.rs" diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 410cddb15..9df1dfbb5 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -5,19 +5,28 @@ use embassy_sync::{ mutex::Mutex, }; use embassy_time::{self as _, Timer}; +use embedded_batteries_async::charger::{MilliAmps, MilliVolts}; use embedded_services::{GlobalRawMutex, event::NoopSender, named::Named}; use log::*; -use power_policy_interface::psu::{Error, Psu}; use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, - psu, + charger, psu, +}; +use power_policy_interface::{ + charger::Charger, + psu::{Error, Psu}, +}; +use power_policy_service::{ + charger::ChargerEventReceivers, psu::PsuEventReceivers, service::registration::ArrayRegistration, }; -use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; use static_cell::StaticCell; type ServiceType = Mutex< GlobalRawMutex, - power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, + power_policy_service::service::Service< + 'static, + ArrayRegistration<'static, DeviceType, 2, NoopSender, 1, ChargerType, 1>, + >, >; const LOW_POWER: PowerCapability = PowerCapability { @@ -111,6 +120,97 @@ impl Named for ExampleDevice<'_> { type DeviceType = Mutex>; +struct ExampleCharger<'a> { + sender: channel::DynamicSender<'a, power_policy_interface::charger::event::EventData>, + state: charger::State, +} + +impl<'a> ExampleCharger<'a> { + fn new(sender: channel::DynamicSender<'a, power_policy_interface::charger::event::EventData>) -> Self { + Self { + sender, + state: charger::State::default(), + } + } + + fn assert_state(&self, internal_state: charger::InternalState, capability: Option) { + assert_eq!(*self.state.internal_state(), internal_state); + assert_eq!(*self.state.capability(), capability); + } + + pub async fn simulate_psu_state_change(&mut self, psu_state: charger::PsuState) { + self.sender.send(charger::EventData::PsuStateChange(psu_state)).await; + self.state_mut().on_psu_state_change(psu_state).unwrap(); + } + + pub fn simulate_timeout(&mut self) { + self.state_mut().on_timeout(); + } + + pub async fn simulate_check_ready(&mut self) { + self.is_ready().await.unwrap(); + } + + pub async fn simulate_init_request(&mut self) { + self.init_charger().await.unwrap(); + } +} + +impl<'a> embedded_batteries_async::charger::ErrorType for ExampleCharger<'a> { + type Error = core::convert::Infallible; +} + +impl<'a> embedded_batteries_async::charger::Charger for ExampleCharger<'a> { + async fn charging_current(&mut self, current: MilliAmps) -> Result { + Ok(current) + } + + async fn charging_voltage(&mut self, voltage: MilliVolts) -> Result { + Ok(voltage) + } +} + +impl<'a> charger::Charger for ExampleCharger<'a> { + type ChargerError = core::convert::Infallible; + + async fn init_charger(&mut self) -> Result { + info!("Charger init"); + self.state_mut().on_initialized(charger::PsuState::Detached).unwrap(); + Ok(charger::PsuState::Detached) + } + + fn attach_handler( + &mut self, + capability: ConsumerPowerCapability, + ) -> impl Future> { + info!("Charger attach: {:?}", capability); + self.state_mut().on_policy_attach(capability); + async { Ok(()) } + } + + fn detach_handler(&mut self) -> impl Future> { + info!("Charger detach"); + self.state_mut().on_policy_detach(); + async { Ok(()) } + } + + async fn is_ready(&mut self) -> Result<(), Self::ChargerError> { + info!("Charger check ready"); + self.state_mut().on_ready_success(); + Ok(()) + } + + fn state(&self) -> &charger::State { + &self.state + } + + fn state_mut(&mut self) -> &mut charger::State { + &mut self.state + } +} + +type ChargerType = Mutex>; + #[embassy_executor::task] async fn run(spawner: Spawner) { embedded_services::init().await; @@ -135,35 +235,64 @@ async fn run(spawner: Spawner) { device1_event_channel.dyn_sender(), ))); - static SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); + info!("Creating charger 0"); + static CHARGER0_EVENT_CHANNEL: StaticCell< + Channel, + > = StaticCell::new(); + let charger0_event_channel = CHARGER0_EVENT_CHANNEL.init(Channel::new()); + static CHARGER0: StaticCell = StaticCell::new(); + let charger0 = CHARGER0.init(Mutex::new(ExampleCharger::new(charger0_event_channel.dyn_sender()))); let registration = ArrayRegistration { psus: [device0, device1], service_senders: [NoopSender], + chargers: [charger0], }; static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( registration, - service_context, power_policy_service::service::config::Config::default(), ))); spawner.spawn( power_policy_task( - ArrayEventReceivers::new( + PsuEventReceivers::new( [device0, device1], [ device0_event_channel.dyn_receiver(), device1_event_channel.dyn_receiver(), ], ), + ChargerEventReceivers::new([charger0], [charger0_event_channel.dyn_receiver()]), service, ) .expect("Failed to create power policy task"), ); + // Check ready charger 0, should transition to Powered(Init) + info!("Charger 0 check ready"); + { + let mut chrg0 = charger0.lock().await; + chrg0.simulate_check_ready().await; + chrg0.assert_state(charger::InternalState::Powered(charger::PoweredSubstate::Init), None); + } + + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Check ready charger 0, should transition to Powered(PsuDetached) + info!("Charger 0 init"); + { + let mut chrg0 = charger0.lock().await; + // For production code, use more robust error handling (eg. retries) instead of blowing up. + chrg0.simulate_init_request().await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuDetached), + None, + ); + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + // Plug in device 0, should become current consumer info!("Connecting device 0"); { @@ -175,9 +304,22 @@ async fn run(spawner: Spawner) { })) .await; } - Timer::after_millis(PER_CALL_DELAY_MS).await; + Timer::after_millis(PER_CALL_DELAY_MS).await; + { + // Simulate PSU attach + let mut chrg0 = charger0.lock().await; + chrg0.simulate_psu_state_change(charger::PsuState::Attached).await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuAttached), + Some(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }), + ); + } // Plug in device 1, should become current consumer + // Charger should detach from device 0 and attach to device 1 with higher power info!("Connecting device 1"); { let mut dev1 = device1.lock().await; @@ -187,6 +329,14 @@ async fn run(spawner: Spawner) { } Timer::after_millis(PER_CALL_DELAY_MS).await; + { + let chrg0 = charger0.lock().await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuAttached), + Some(HIGH_POWER.into()), + ); + } + // Unplug device 0, device 1 should remain current consumer info!("Unplugging device 0"); { @@ -206,6 +356,7 @@ async fn run(spawner: Spawner) { Timer::after_millis(PER_CALL_DELAY_MS).await; // Unplug device 1, device 0 should become current consumer + // Charger should detach from device 1 and attach to device 0 with lower power info!("Unplugging device 1"); { let mut dev1 = device1.lock().await; @@ -213,6 +364,14 @@ async fn run(spawner: Spawner) { } Timer::after_millis(PER_CALL_DELAY_MS).await; + { + let chrg0 = charger0.lock().await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuAttached), + Some(LOW_POWER.into()), + ); + } + // Replug device 1, device 1 becomes current consumer info!("Connecting device 1"); { @@ -232,12 +391,85 @@ async fn run(spawner: Spawner) { } Timer::after_millis(PER_CALL_DELAY_MS).await; + // Charger should still have device 1 capability + { + let chrg0 = charger0.lock().await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuAttached), + Some(HIGH_POWER.into()), + ); + } + + // Detach device 1, no consumers available + // Charger should detach and clear capability + info!("Detaching device 1"); { let mut dev1 = device1.lock().await; dev1.simulate_detach().await; } Timer::after_millis(PER_CALL_DELAY_MS).await; + { + let chrg0 = charger0.lock().await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuAttached), + None, + ); + } + + // Simulate charger PSU detach, charger should transition to PsuDetached + info!("Simulating charger PSU detach"); + { + let mut chrg0 = charger0.lock().await; + chrg0.simulate_psu_state_change(charger::PsuState::Detached).await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuDetached), + None, + ); + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Simulate charger PSU reattach + info!("Simulating charger PSU reattach"); + { + let mut chrg0 = charger0.lock().await; + chrg0.simulate_psu_state_change(charger::PsuState::Attached).await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuAttached), + None, + ); + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Simulate charger timeout, should transition to Unpowered + info!("Simulating charger timeout"); + { + let mut chrg0 = charger0.lock().await; + chrg0.simulate_timeout(); + chrg0.assert_state(charger::InternalState::Unpowered, None); + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + // Recover charger: CheckReady -> Init -> PsuDetached + info!("Recovering charger: CheckReady"); + { + let mut chrg0 = charger0.lock().await; + chrg0.simulate_check_ready().await; + chrg0.assert_state(charger::InternalState::Powered(charger::PoweredSubstate::Init), None); + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + + info!("Recovering charger: InitRequest"); + { + let mut chrg0 = charger0.lock().await; + chrg0.simulate_init_request().await; + chrg0.assert_state( + charger::InternalState::Powered(charger::PoweredSubstate::PsuDetached), + None, + ); + } + Timer::after_millis(PER_CALL_DELAY_MS).await; + // Switch device 0 to provider info!("Device 0 switch to provider"); { @@ -286,15 +518,21 @@ async fn run(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: ArrayEventReceivers< + psu_events: PsuEventReceivers< 'static, 2, DeviceType, channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, + charger_events: ChargerEventReceivers< + 'static, + 1, + ChargerType, + channel::DynamicReceiver<'static, power_policy_interface::charger::event::EventData>, + >, power_policy: &'static ServiceType, ) { - power_policy_service::service::task::task(psu_events, power_policy).await; + power_policy_service::service::task::task(psu_events, charger_events, power_policy).await; } fn main() { diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 048baaeda..278a6b5d9 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -11,8 +11,9 @@ use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; +use power_policy_interface::charger::mock::ChargerType; use power_policy_interface::psu; -use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; @@ -51,7 +52,7 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 1, PowerPolicySenderType, 1>, + ArrayRegistration<'static, DeviceType, 1, PowerPolicySenderType, 1, ChargerType, 0>, >, >; @@ -95,9 +96,6 @@ async fn task(spawner: Spawner) { embedded_services::init().await; // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTEXT.init(type_c_interface::service::context::Context::new()); @@ -118,12 +116,12 @@ async fn task(spawner: Spawner) { let power_policy_registration = ArrayRegistration { psus: [&wrapper.ports[0].proxy], service_senders: [power_policy_sender], + chargers: [], }; static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( power_policy_registration, - power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -135,8 +133,8 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.spawn( - power_policy_task( - ArrayEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), + power_policy_psu_task( + PsuEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), power_service, ) .expect("Failed to create power policy task"), @@ -150,7 +148,6 @@ async fn task(spawner: Spawner) { ) .expect("Failed to create type-c service task"), ); - spawner.spawn(controller_task(event_receiver, wrapper, controller).expect("Failed to create controller task")); Timer::after_millis(1000).await; @@ -178,11 +175,11 @@ async fn task(spawner: Spawner) { } #[embassy_executor::task] -async fn power_policy_task( - psu_events: ArrayEventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, +async fn power_policy_psu_task( + psu_events: PsuEventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { - power_policy_service::service::task::task(psu_events, power_policy).await; + power_policy_service::service::task::psu_task(psu_events, power_policy).await; } #[embassy_executor::task] diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index babb2ae06..42d7de23b 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -17,8 +17,9 @@ use embedded_usb_pd::ucsi::ppm::set_notification_enable::NotificationEnable; use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; use power_policy_interface::capability::PowerCapability; +use power_policy_interface::charger::mock::ChargerType; use power_policy_interface::psu; -use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; @@ -57,7 +58,7 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1>, + ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1, ChargerType, 0>, >, >; @@ -222,10 +223,10 @@ async fn wrapper_task( #[embassy_executor::task] async fn power_policy_task( - psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { - power_policy_service::service::task::task(psu_events, power_policy).await; + power_policy_service::service::task::psu_task(psu_events, power_policy).await; } #[embassy_executor::task] @@ -370,9 +371,6 @@ async fn task(spawner: Spawner) { )); // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< PubSubChannel, @@ -387,12 +385,12 @@ async fn task(spawner: Spawner) { let power_policy_registration = ArrayRegistration { psus: [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], service_senders: [power_policy_sender], + chargers: [], }; static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( power_policy_registration, - power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -432,7 +430,7 @@ async fn task(spawner: Spawner) { spawner.spawn( power_policy_task( - ArrayEventReceivers::new( + PsuEventReceivers::new( [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 21c20cb4f..8695caac7 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -13,8 +13,9 @@ use embedded_services::event::MapSender; use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; +use power_policy_interface::charger::mock::ChargerType; use power_policy_interface::psu; -use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; @@ -61,7 +62,7 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 3, PowerPolicySenderType, 1>, + ArrayRegistration<'static, DeviceType, 3, PowerPolicySenderType, 1, ChargerType, 0>, >, >; @@ -97,9 +98,6 @@ async fn task(spawner: Spawner) { embedded_services::init().await; // Create power policy service - static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); - let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); @@ -274,12 +272,12 @@ async fn task(spawner: Spawner) { &wrapper2.ports[0].proxy, ], service_senders: [power_policy_sender], + chargers: [], }; static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( power_policy_registration, - power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -293,7 +291,7 @@ async fn task(spawner: Spawner) { spawner.spawn( power_policy_task( - ArrayEventReceivers::new( + PsuEventReceivers::new( [ &wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy, @@ -370,10 +368,10 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: ArrayEventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { - power_policy_service::service::task::task(psu_events, power_policy).await; + power_policy_service::service::task::psu_task(psu_events, power_policy).await; } #[embassy_executor::task] diff --git a/power-policy-interface/Cargo.toml b/power-policy-interface/Cargo.toml index 3bdea4f33..e65a83371 100644 --- a/power-policy-interface/Cargo.toml +++ b/power-policy-interface/Cargo.toml @@ -15,7 +15,6 @@ workspace = true defmt = { workspace = true, optional = true } embassy-sync.workspace = true embedded-services.workspace = true -embassy-futures.workspace = true num_enum.workspace = true bitfield.workspace = true log = { workspace = true, optional = true } @@ -25,3 +24,6 @@ embedded-batteries-async.workspace = true default = [] defmt = ["dep:defmt", "embedded-services/defmt", "embassy-sync/defmt"] log = ["dep:log", "embedded-services/log", "embassy-sync/log"] + +[dev-dependencies] +critical-section = { workspace = true, features = ["std"] } diff --git a/power-policy-interface/src/charger.rs b/power-policy-interface/src/charger.rs deleted file mode 100644 index 8de99e8d6..000000000 --- a/power-policy-interface/src/charger.rs +++ /dev/null @@ -1,511 +0,0 @@ -//! Charger device struct and controller -use core::{future::Future, ops::DerefMut}; - -use embassy_futures::select::select; -use embassy_sync::{channel::Channel, mutex::Mutex}; -use embedded_services::{GlobalRawMutex, debug, error, info, intrusive_list, trace, warn}; - -use crate::{ - capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability}, - charger, -}; - -/// Charger controller trait that device drivers may use to integrate with internal messaging system -pub trait ChargeController: embedded_batteries_async::charger::Charger { - /// Type of error returned by the bus - type ChargeControllerError; - - /// Returns with pending events - fn wait_event(&mut self) -> impl Future; - /// Initialize charger hardware, after this returns the charger should be ready to charge - fn init_charger(&mut self) -> impl Future>; - /// Returns if the charger hardware detects if a PSU is attached - fn is_psu_attached(&mut self) -> impl Future>; - /// Called after power policy attaches to a power port. - fn attach_handler( - &mut self, - capability: ConsumerPowerCapability, - ) -> impl Future>; - /// Called after power policy detaches from a power port, either to switch consumers, - /// or because PSU was disconnected. - fn detach_handler(&mut self) -> impl Future>; - /// Called when a charger CheckReady request (PolicyEvent::CheckReady) is sent to the power policy. - /// Upon successful return of this method, the charger is assumed to be powered and ready to communicate, - /// transitioning state from unpowered to powered. - /// - /// If the charger is powered, an Ok(()) does nothing. An Err(_) will put the charger into an - /// unpowered state, meaning another PolicyEvent::CheckReady must be sent to re-establish communications - /// with the charger. Upon successful return, the charger must be re-initialized by sending a - /// `PolicyEvent::InitRequest`. - fn is_ready(&mut self) -> impl Future> { - core::future::ready(Ok(())) - } -} - -/// Charger Device ID new type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ChargerId(pub u8); - -/// PSU state as determined by charger device -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PsuState { - /// Charger detected PSU attached - Attached, - /// Charger detected PSU detached - Detached, -} - -impl From for PsuState { - fn from(value: bool) -> Self { - match value { - true => PsuState::Attached, - false => PsuState::Detached, - } - } -} - -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ChargerEvent { - /// Charger finished initialization sequence - Initialized(PsuState), - /// PSU state changed - PsuStateChange(PsuState), - /// A timeout of some sort was detected - Timeout, - /// An error occured on the bus - BusError, -} - -/// Charger state errors -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ChargerError { - /// Charger received command in an invalid state - InvalidState(State), - /// Charger hardware timed out responding - Timeout, - /// Charger underlying bus error - BusError, -} - -impl From for crate::psu::Error { - fn from(value: ChargerError) -> Self { - Self::Charger(value) - } -} - -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PolicyEvent { - /// Request to initialize charger hardware - InitRequest, - /// New power policy detected - PolicyConfiguration(ConsumerPowerCapability), - /// Request to check if the charger hardware is ready to receive communications. - /// For example, if the charger is powered. - CheckReady, -} - -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ChargerResponseData { - /// Command completed - Ack, - /// Charger Unpowered, but we are still Ok - UnpoweredAck, -} - -/// Response for charger requests from policy commands -pub type ChargerResponse = Result; - -/// Current state of the charger -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { - /// Device is unpowered - Unpowered, - /// Device is powered - Powered(PoweredSubstate), -} - -/// Powered state substates -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PoweredSubstate { - /// Device is initializing - Init, - /// PSU is attached and device can charge if desired - PsuAttached, - /// PSU is detached - PsuDetached, -} - -/// Current state of the charger -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InternalState { - /// Charger device state - pub state: State, - /// Current charger capability - pub capability: Option, -} - -/// Channel size for device requests -pub const CHARGER_CHANNEL_SIZE: usize = 1; - -/// Device struct -pub struct Device { - /// Intrusive list node - node: intrusive_list::Node, - /// Device ID - id: ChargerId, - /// Current state of the device - state: Mutex, - /// Channel for requests to the device - commands: Channel, - /// Channel for responses from the device - response: Channel, -} - -impl Device { - /// Create a new device - pub fn new(id: ChargerId) -> Self { - Self { - node: intrusive_list::Node::uninit(), - id, - state: Mutex::new(InternalState { - state: State::Unpowered, - capability: None, - }), - commands: Channel::new(), - response: Channel::new(), - } - } - - /// Get the device ID - pub fn id(&self) -> ChargerId { - self.id - } - - /// Returns the current state of the device - pub async fn state(&self) -> InternalState { - *self.state.lock().await - } - - /// Set the state of the device - pub async fn set_state(&self, new_state: InternalState) { - let mut lock = self.state.lock().await; - let current_state = lock.deref_mut(); - *current_state = new_state; - } - - /// Wait for a command from policy - pub async fn wait_command(&self) -> PolicyEvent { - self.commands.receive().await - } - - /// Send a command to the charger - pub async fn send_command(&self, policy_event: PolicyEvent) { - self.commands.send(policy_event).await - } - - /// Send a response to the power policy - pub async fn send_response(&self, response: ChargerResponse) { - self.response.send(response).await - } - - /// Send a command and wait for a response from the charger - pub async fn execute_command(&self, policy_event: PolicyEvent) -> ChargerResponse { - self.send_command(policy_event).await; - self.response.receive().await - } -} - -impl intrusive_list::NodeContainer for Device { - fn get_node(&self) -> &intrusive_list::Node { - &self.node - } -} - -/// Trait for any container that holds a device -pub trait ChargerContainer { - /// Get the underlying device struct - fn get_charger(&self) -> &Device; -} - -impl ChargerContainer for Device { - fn get_charger(&self) -> &Device { - self - } -} - -pub struct Wrapper<'a, C: ChargeController> -where - ChargerError: From<::ChargeControllerError>, -{ - charger_policy_state: &'a Device, - controller: Mutex, -} - -impl<'a, C: ChargeController> Wrapper<'a, C> -where - ChargerError: From<::ChargeControllerError>, -{ - pub fn new(charger_policy_state: &'a Device, controller: C) -> Self { - Self { - charger_policy_state, - controller: Mutex::new(controller), - } - } - - pub async fn get_state(&self) -> InternalState { - self.charger_policy_state.state().await - } - - pub async fn set_state(&self, new_state: InternalState) { - self.charger_policy_state.set_state(new_state).await - } - - async fn wait_policy_command(&self) -> PolicyEvent { - self.charger_policy_state.wait_command().await - } - - #[allow(clippy::single_match)] - async fn process_controller_event(&self, controller: &mut C, event: ChargerEvent) { - let state = self.get_state().await; - match state.state { - State::Powered(powered_substate) => match powered_substate { - PoweredSubstate::Init => match event { - ChargerEvent::Initialized(psu_state) => { - self.set_state(InternalState { - state: match psu_state { - PsuState::Attached => State::Powered(PoweredSubstate::PsuAttached), - PsuState::Detached => State::Powered(PoweredSubstate::PsuDetached), - }, - capability: state.capability, - }) - .await; - - // If we have a cached capability, it means a power contract was sent to the charger before we finished initializing - // Go ahead and apply that capability to the charger hardware. - if let Some(power_capability) = state.capability { - debug!("Charger just finished initializing and a new power policy configuration was detected while initializing. - Executing attach sequence with cached capability"); - if let Err(e) = controller - .attach_handler(ConsumerPowerCapability { - capability: power_capability, - flags: ConsumerFlags::none(), - }) - .await - { - let err = charger::ChargerError::from(e); - error!("Error executing charger power port attach sequence: {:?}", err) - } - } - } - // If we are initializing, we don't want to update the state - _ => (), - }, - PoweredSubstate::PsuAttached => match event { - ChargerEvent::PsuStateChange(PsuState::Detached) => { - self.set_state(InternalState { - state: State::Powered(PoweredSubstate::PsuDetached), - capability: state.capability, - }) - .await - } - ChargerEvent::Timeout => { - self.set_state(InternalState { - state: State::Powered(PoweredSubstate::Init), - capability: None, - }) - .await - } - _ => (), - }, - PoweredSubstate::PsuDetached => match event { - ChargerEvent::PsuStateChange(PsuState::Attached) => { - self.set_state(InternalState { - state: State::Powered(PoweredSubstate::PsuAttached), - capability: state.capability, - }) - .await - } - ChargerEvent::Timeout => { - self.set_state(InternalState { - state: State::Powered(PoweredSubstate::Init), - capability: None, - }) - .await - } - _ => (), - }, - }, - State::Unpowered => warn!( - "Charger is unpowered but ChargeController event received event: {:?}", - event - ), - } - } - - async fn process_policy_command(&self, controller: &mut C, event: PolicyEvent) { - let state = self.get_state().await; - let res: ChargerResponse = match event { - PolicyEvent::InitRequest => { - if state.state == State::Unpowered { - error!("Charger received request to initialize but it's unpowered!"); - Err(ChargerError::InvalidState(State::Unpowered)) - } else { - if state.state == State::Powered(PoweredSubstate::Init) { - info!("Charger received request to initialize."); - } else { - warn!("Charger received request to initialize but it's already initialized! Reinitializing..."); - } - - if let Err(err) = controller.init_charger().await { - error!("Charger failed initialzation sequence."); - Err(err.into()) - } else { - Ok(ChargerResponseData::Ack) - } - } - } - PolicyEvent::PolicyConfiguration(power_capability) => match state.state { - State::Unpowered => { - // Power policy sends this event when a new type-c plug event comes in - // For the scenario where the charger is unpowered, we don't want to block the power policy - // from completing it's connect_consumer() call, as there might be cases where we don't want - // chargers to be powered or the charger can't be powered. - error!( - "Charger detected new power policy configuration but it's unpowered! - Caching capability so when we finish initializing, we start off with this capability set." - ); - self.set_state(InternalState { - state: state.state, - capability: Some(power_capability.capability), - }) - .await; - Ok(charger::ChargerResponseData::UnpoweredAck) - } - State::Powered(substate) => match substate { - PoweredSubstate::Init => { - warn!( - "Charger detected new power policy configuration but charger is still initializing. - Caching capability so when we finish initializing, we start off with this capability set." - ); - self.set_state(InternalState { - state: state.state, - capability: Some(power_capability.capability), - }) - .await; - Err(charger::ChargerError::InvalidState(State::Powered( - PoweredSubstate::Init, - ))) - } - PoweredSubstate::PsuAttached | PoweredSubstate::PsuDetached => { - if power_capability.capability.current_ma == 0 { - // Policy detected a detach - debug!("Charger detected new power policy configuration. Executing detach sequence"); - if let Err(err) = controller - .detach_handler() - .await - .inspect_err(|_| error!("Error executing charger power port detach sequence!")) - { - Err(err.into()) - } else { - // Update power capability but do not change controller state. - // That is handled by process_controller_event(). - // This way capability is cached even if the - // hardware charger device lags on changing its PSU state. - self.set_state(InternalState { - state: state.state, - capability: None, - }) - .await; - Ok(ChargerResponseData::Ack) - } - } else { - // Policy detected an attach - debug!("Charger detected new power policy configuration. Executing attach sequence"); - if let Err(err) = controller - .attach_handler(power_capability) - .await - .inspect_err(|_| error!("Error executing charger power port attach sequence!")) - { - Err(err.into()) - } else { - // Update power capability but do not change controller state. - // That is handled by process_controller_event(). - // This way capability is cached even if the - // hardware charger device lags on changing its PSU state. - self.set_state(InternalState { - state: state.state, - capability: Some(power_capability.capability), - }) - .await; - Ok(ChargerResponseData::Ack) - } - } - } - }, - }, - PolicyEvent::CheckReady => { - debug!("Charger received check ready request."); - let ret = controller.is_ready().await; - match state.state { - State::Powered(_) => { - if let Err(e) = ret { - self.set_state(InternalState { - state: State::Unpowered, - // Cache capability for logging/debug - capability: state.capability, - }) - .await; - Err(e.into()) - } else { - Ok(ChargerResponseData::Ack) - } - } - State::Unpowered => { - if let Err(e) = ret { - Err(e.into()) - } else { - self.set_state(InternalState { - state: State::Powered(PoweredSubstate::Init), - capability: None, - }) - .await; - Ok(ChargerResponseData::Ack) - } - } - } - } - }; - - // Send response - self.charger_policy_state.send_response(res).await; - } - - pub async fn process(&self) { - let mut controller = self.controller.lock().await; - loop { - let res = select(controller.wait_event(), self.wait_policy_command()).await; - match res { - embassy_futures::select::Either::First(event) => { - trace!("New charger device event."); - self.process_controller_event(&mut controller, event).await; - } - embassy_futures::select::Either::Second(event) => { - trace!("New charger policy command."); - self.process_policy_command(&mut controller, event).await; - } - }; - } - } -} diff --git a/power-policy-interface/src/charger/event.rs b/power-policy-interface/src/charger/event.rs new file mode 100644 index 000000000..c59de5f29 --- /dev/null +++ b/power-policy-interface/src/charger/event.rs @@ -0,0 +1,44 @@ +//! Events originating from a charger device + +use embedded_services::sync::Lockable; + +/// PSU state as determined by charger device +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PsuState { + /// Charger detected PSU attached + Attached, + /// Charger detected PSU detached + Detached, +} + +impl From for PsuState { + fn from(value: bool) -> Self { + match value { + true => PsuState::Attached, + false => PsuState::Detached, + } + } +} + +/// Data for a charger event +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum EventData { + /// PSU state changed + PsuStateChange(PsuState), +} + +/// Event broadcast from a charger. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Event<'a, D: Lockable> +where + D::Inner: crate::charger::Charger, +{ + /// Device that sent this request + pub charger: &'a D, + /// Event data + pub event: EventData, +} diff --git a/power-policy-interface/src/charger/mock.rs b/power-policy-interface/src/charger/mock.rs new file mode 100644 index 000000000..4e0e8ae1c --- /dev/null +++ b/power-policy-interface/src/charger/mock.rs @@ -0,0 +1,62 @@ +use embassy_sync::mutex::Mutex; +use embedded_batteries_async::charger::{MilliAmps, MilliVolts}; +use embedded_services::{GlobalRawMutex, debug}; + +pub type ChargerType = Mutex; + +pub struct NoopCharger(super::State); + +impl NoopCharger { + pub fn new() -> Self { + Self(super::State::default()) + } +} +impl Default for NoopCharger { + fn default() -> Self { + Self::new() + } +} + +impl super::Charger for NoopCharger { + type ChargerError = core::convert::Infallible; + + async fn init_charger(&mut self) -> Result { + debug!("Charger initialized"); + Ok(super::PsuState::Attached) + } + + async fn attach_handler( + &mut self, + capability: crate::capability::ConsumerPowerCapability, + ) -> Result<(), Self::ChargerError> { + debug!("Charger recvd capability {:?}", capability); + Ok(()) + } + + async fn detach_handler(&mut self) -> Result<(), Self::ChargerError> { + debug!("Charger recvd detach"); + Ok(()) + } + + fn state(&self) -> &super::State { + &self.0 + } + + fn state_mut(&mut self) -> &mut super::State { + &mut self.0 + } +} + +impl embedded_batteries_async::charger::Charger for NoopCharger { + async fn charging_current(&mut self, current: MilliAmps) -> Result { + Ok(current) + } + + async fn charging_voltage(&mut self, voltage: MilliVolts) -> Result { + Ok(voltage) + } +} + +impl embedded_batteries_async::charger::ErrorType for NoopCharger { + type Error = core::convert::Infallible; +} diff --git a/power-policy-interface/src/charger/mod.rs b/power-policy-interface/src/charger/mod.rs new file mode 100644 index 000000000..40af43661 --- /dev/null +++ b/power-policy-interface/src/charger/mod.rs @@ -0,0 +1,206 @@ +//! Charger events, state machine, and trait + +use crate::capability::ConsumerPowerCapability; +use core::{convert::Infallible, future::Future}; + +pub mod event; +/// Mock software representation of a charger +pub mod mock; +#[cfg(test)] +mod tests; + +pub use event::{Event, EventData, PsuState}; + +/// Charger Device ID new type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ChargerId(pub u8); + +/// Charger state errors +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ChargerError { + /// Charger received command in an invalid state + InvalidState(InternalState), + /// Charger hardware timed out responding + Timeout, + /// Charger underlying bus error + BusError, + /// Charger received an unknown event + UnknownEvent, +} + +impl From for crate::psu::Error { + fn from(value: ChargerError) -> Self { + Self::Charger(value) + } +} + +impl From for ChargerError { + fn from(_value: Infallible) -> Self { + Self::BusError + } +} + +/// Current state of the charger +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InternalState { + /// Device is unpowered + Unpowered, + /// Device is powered + Powered(PoweredSubstate), +} + +/// Powered state substates +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PoweredSubstate { + /// Device is initializing + Init, + /// PSU is attached and device can charge if desired + PsuAttached, + /// PSU is detached + PsuDetached, +} + +/// Current state of the charger +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct State { + /// Charger device state + state: InternalState, + /// Current charger capability + capability: Option, +} + +impl Default for State { + fn default() -> Self { + Self { + state: InternalState::Unpowered, + capability: None, + } + } +} + +impl State { + /// Returns a reference to the current internal charger state. + pub fn internal_state(&self) -> &InternalState { + &self.state + } + + /// Returns a reference to the current cached power capability, if any. + pub fn capability(&self) -> &Option { + &self.capability + } + + /// Handle charger initialization completing. Transitions from `Powered(Init)` to + /// `Powered(PsuAttached)` or `Powered(PsuDetached)` based on PSU state. + /// + /// Returns `Err` if not in `Powered(Init)`. + pub fn on_initialized(&mut self, psu_state: PsuState) -> Result<(), ChargerError> { + match self.state { + InternalState::Powered(PoweredSubstate::Init) => { + self.state = match psu_state { + PsuState::Attached => InternalState::Powered(PoweredSubstate::PsuAttached), + PsuState::Detached => InternalState::Powered(PoweredSubstate::PsuDetached), + }; + Ok(()) + } + other => Err(ChargerError::InvalidState(other)), + } + } + + /// Handle a PSU state change event. Transitions between `Powered(PsuAttached)` and + /// `Powered(PsuDetached)`. + /// + /// Returns `Err` if not in `Powered(PsuAttached)` or `Powered(PsuDetached)`. + pub fn on_psu_state_change(&mut self, psu_state: PsuState) -> Result<(), ChargerError> { + match self.state { + InternalState::Powered(PoweredSubstate::PsuAttached) => { + if psu_state == PsuState::Detached { + self.state = InternalState::Powered(PoweredSubstate::PsuDetached); + } + Ok(()) + } + InternalState::Powered(PoweredSubstate::PsuDetached) => { + if psu_state == PsuState::Attached { + self.state = InternalState::Powered(PoweredSubstate::PsuAttached); + } + Ok(()) + } + other => Err(ChargerError::InvalidState(other)), + } + } + + /// Handle a communication timeout. Transitions to `Unpowered` and clears the cached capability. + pub fn on_timeout(&mut self) { + self.state = InternalState::Unpowered; + self.capability = None; + } + + /// Transition after a successful check-ready response. + /// + /// If currently unpowered, moves to `Powered(Init)` and clears capability. + /// If already powered, this is a no-op. + pub fn on_ready_success(&mut self) { + if self.state == InternalState::Unpowered { + self.state = InternalState::Powered(PoweredSubstate::Init); + self.capability = None; + } + } + + /// Transition after a failed check-ready response. + /// + /// If currently powered, moves to `Unpowered`. Capability is preserved for diagnostics. + /// If already unpowered, this is a no-op. + pub fn on_ready_failure(&mut self) { + if matches!(self.state, InternalState::Powered(_)) { + self.state = InternalState::Unpowered; + } + } + + /// Cache a new capability from a policy configuration attach. + /// Does not change the charger state. + pub fn on_policy_attach(&mut self, capability: ConsumerPowerCapability) { + self.capability = Some(capability); + } + + /// Clear the cached capability after a policy configuration detach. + /// Does not change the charger state. + pub fn on_policy_detach(&mut self) { + self.capability = None; + } + + /// Returns `true` if the charger is in the `Unpowered` state. + pub fn is_unpowered(&self) -> bool { + self.state == InternalState::Unpowered + } +} + +/// Charger controller trait that devices must implement to use the power policy service. +pub trait Charger: embedded_batteries_async::charger::Charger { + /// Type of error returned by the bus + type ChargerError: Into + embedded_batteries_async::charger::Error; + + /// Initialize charger hardware, after this returns the charger should be ready to charge + fn init_charger(&mut self) -> impl Future>; + /// Called after power policy attaches to a power port. + fn attach_handler( + &mut self, + capability: ConsumerPowerCapability, + ) -> impl Future>; + /// Called after power policy detaches from a power port, either to switch consumers, + /// or because PSU was disconnected. + fn detach_handler(&mut self) -> impl Future>; + /// Upon successful return of this method, the charger is assumed to be powered and ready to communicate, + /// transitioning state from unpowered to powered. + fn is_ready(&mut self) -> impl Future> { + core::future::ready(Ok(())) + } + /// Return an immutable reference to the current charger state + fn state(&self) -> &State; + /// Return a mutable reference to the current charger state + fn state_mut(&mut self) -> &mut State; +} diff --git a/power-policy-interface/src/charger/tests.rs b/power-policy-interface/src/charger/tests.rs new file mode 100644 index 000000000..518c5ea75 --- /dev/null +++ b/power-policy-interface/src/charger/tests.rs @@ -0,0 +1,285 @@ +use super::*; +use crate::capability::{ConsumerFlags, PowerCapability}; + +fn cap(voltage_mv: u16, current_ma: u16) -> ConsumerPowerCapability { + ConsumerPowerCapability { + capability: PowerCapability { voltage_mv, current_ma }, + flags: ConsumerFlags::none(), + } +} + +fn state_init() -> State { + State { + state: InternalState::Powered(PoweredSubstate::Init), + capability: None, + } +} + +fn state_psu_attached() -> State { + State { + state: InternalState::Powered(PoweredSubstate::PsuAttached), + capability: None, + } +} + +fn state_psu_detached() -> State { + State { + state: InternalState::Powered(PoweredSubstate::PsuDetached), + capability: None, + } +} + +fn state_unpowered() -> State { + State::default() +} + +// on_initialized + +#[test] +fn on_initialized_from_init_attached() { + let mut s = state_init(); + assert!(s.on_initialized(PsuState::Attached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); +} + +#[test] +fn on_initialized_from_init_detached() { + let mut s = state_init(); + assert!(s.on_initialized(PsuState::Detached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuDetached)); +} + +#[test] +fn on_initialized_from_psu_attached_fails() { + let mut s = state_psu_attached(); + assert_eq!( + s.on_initialized(PsuState::Attached), + Err(ChargerError::InvalidState(InternalState::Powered( + PoweredSubstate::PsuAttached + ))) + ); +} + +#[test] +fn on_initialized_from_unpowered_fails() { + let mut s = state_unpowered(); + assert_eq!( + s.on_initialized(PsuState::Attached), + Err(ChargerError::InvalidState(InternalState::Unpowered)) + ); +} + +// on_psu_state_change + +#[test] +fn psu_state_change_attached_to_detached() { + let mut s = state_psu_attached(); + assert!(s.on_psu_state_change(PsuState::Detached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuDetached)); +} + +#[test] +fn psu_state_change_detached_to_attached() { + let mut s = state_psu_detached(); + assert!(s.on_psu_state_change(PsuState::Attached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); +} + +#[test] +fn psu_state_change_same_state_is_noop() { + let mut s = state_psu_attached(); + assert!(s.on_psu_state_change(PsuState::Attached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); +} + +#[test] +fn psu_state_change_from_init_fails() { + let mut s = state_init(); + assert_eq!( + s.on_psu_state_change(PsuState::Attached), + Err(ChargerError::InvalidState(InternalState::Powered( + PoweredSubstate::Init + ))) + ); +} + +#[test] +fn psu_state_change_from_unpowered_fails() { + let mut s = state_unpowered(); + assert_eq!( + s.on_psu_state_change(PsuState::Attached), + Err(ChargerError::InvalidState(InternalState::Unpowered)) + ); +} + +// on_timeout + +#[test] +fn timeout_from_psu_attached() { + let mut s = state_psu_attached(); + s.capability = Some(cap(5000, 3000)); + s.on_timeout(); + assert_eq!(s.state, InternalState::Unpowered); + assert!(s.capability.is_none()); +} + +#[test] +fn timeout_from_psu_detached() { + let mut s = state_psu_detached(); + s.on_timeout(); + assert_eq!(s.state, InternalState::Unpowered); +} + +#[test] +fn timeout_from_init() { + let mut s = state_init(); + s.on_timeout(); + assert_eq!(s.state, InternalState::Unpowered); +} + +#[test] +fn timeout_from_unpowered() { + let mut s = state_unpowered(); + s.on_timeout(); + assert_eq!(s.state, InternalState::Unpowered); +} + +// on_ready_success + +#[test] +fn ready_success_from_unpowered() { + let mut s = state_unpowered(); + s.on_ready_success(); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::Init)); + assert!(s.capability.is_none()); +} + +#[test] +fn ready_success_from_powered_is_noop() { + let mut s = state_psu_attached(); + s.capability = Some(cap(5000, 3000)); + s.on_ready_success(); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); + assert!(s.capability.is_some()); +} + +// on_ready_failure + +#[test] +fn ready_failure_from_powered() { + let mut s = state_psu_attached(); + s.capability = Some(cap(5000, 3000)); + s.on_ready_failure(); + assert_eq!(s.state, InternalState::Unpowered); + assert!(s.capability.is_some()); // preserved for diagnostics +} + +#[test] +fn ready_failure_from_unpowered_is_noop() { + let mut s = state_unpowered(); + s.on_ready_failure(); + assert_eq!(s.state, InternalState::Unpowered); +} + +// on_policy_attach + +#[test] +fn policy_attach_from_psu_attached() { + let mut s = state_psu_attached(); + let c = cap(5000, 3000); + s.on_policy_attach(c); + assert_eq!(s.capability, Some(c)); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); +} + +#[test] +fn policy_attach_from_psu_detached() { + let mut s = state_psu_detached(); + let c = cap(9000, 2000); + s.on_policy_attach(c); + assert_eq!(s.capability, Some(c)); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuDetached)); +} + +#[test] +fn policy_attach_from_init() { + let mut s = state_init(); + let c = cap(5000, 3000); + s.on_policy_attach(c); + assert_eq!(s.capability, Some(c)); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::Init)); +} + +#[test] +fn policy_attach_from_unpowered() { + let mut s = state_unpowered(); + let c = cap(5000, 3000); + s.on_policy_attach(c); + assert_eq!(s.capability, Some(c)); + assert_eq!(s.state, InternalState::Unpowered); +} + +// on_policy_detach + +#[test] +fn policy_detach_from_psu_attached() { + let mut s = state_psu_attached(); + s.capability = Some(cap(5000, 3000)); + s.on_policy_detach(); + assert!(s.capability.is_none()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); +} + +#[test] +fn policy_detach_from_init() { + let mut s = state_init(); + s.capability = Some(cap(5000, 3000)); + s.on_policy_detach(); + assert!(s.capability.is_none()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::Init)); +} + +#[test] +fn policy_detach_from_unpowered() { + let mut s = state_unpowered(); + s.capability = Some(cap(5000, 3000)); + s.on_policy_detach(); + assert!(s.capability.is_none()); + assert_eq!(s.state, InternalState::Unpowered); +} + +// Full transition sequence + +#[test] +fn full_lifecycle_unpowered_to_charging_and_back() { + let mut s = state_unpowered(); + + // Check ready → powered init + s.on_ready_success(); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::Init)); + + // Initialized with PSU attached + assert!(s.on_initialized(PsuState::Attached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); + + // Policy attach + let c = cap(5000, 3000); + s.on_policy_attach(c); + assert_eq!(s.capability, Some(c)); + + // PSU detach + assert!(s.on_psu_state_change(PsuState::Detached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuDetached)); + + // Policy detach + s.on_policy_detach(); + assert!(s.capability.is_none()); + + // PSU reattach + assert!(s.on_psu_state_change(PsuState::Attached).is_ok()); + assert_eq!(s.state, InternalState::Powered(PoweredSubstate::PsuAttached)); + + // Timeout → unpowered + s.on_timeout(); + assert_eq!(s.state, InternalState::Unpowered); +} diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 123d1edb8..1be2dd6a3 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -29,6 +29,7 @@ embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } tokio = { workspace = true, features = ["rt", "macros", "time"] } env_logger = "0.11.8" log = { workspace = true } +embedded-batteries-async = { workspace = true } # TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile # Uncomment this line to enable log output in tests # power-policy-service = { workspace = true, features = ["log"] } diff --git a/power-policy-service/src/charger.rs b/power-policy-service/src/charger.rs new file mode 100644 index 000000000..98ddcca1d --- /dev/null +++ b/power-policy-service/src/charger.rs @@ -0,0 +1,43 @@ +use core::pin::pin; + +use embassy_futures::select::select_slice; +use embedded_services::event::Receiver; +use embedded_services::sync::Lockable; +use power_policy_interface::charger::Charger; +use power_policy_interface::charger::event::{Event, EventData}; + +/// Struct used to contain charger event receivers and manage mapping from a receiver to its corresponding device. +pub struct ChargerEventReceivers<'a, const N: usize, CHARGER: Lockable, R: Receiver> +where + CHARGER::Inner: Charger, +{ + pub charger_devices: [&'a CHARGER; N], + pub receivers: [R; N], +} + +impl<'a, const N: usize, CHARGER: Lockable, R: Receiver> ChargerEventReceivers<'a, N, CHARGER, R> +where + CHARGER::Inner: Charger, +{ + /// Create a new instance + pub fn new(charger_devices: [&'a CHARGER; N], receivers: [R; N]) -> Self { + Self { + charger_devices, + receivers, + } + } + + /// Get the next pending charger event + pub async fn wait_event(&mut self) -> Event<'a, CHARGER> { + let ((event, charger), _) = { + let mut futures = heapless::Vec::<_, N>::new(); + for (receiver, psu) in self.receivers.iter_mut().zip(self.charger_devices.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 { charger, event } + } +} diff --git a/power-policy-service/src/lib.rs b/power-policy-service/src/lib.rs index e9592e0e7..1142968c0 100644 --- a/power-policy-service/src/lib.rs +++ b/power-policy-service/src/lib.rs @@ -1,3 +1,4 @@ #![no_std] +pub mod charger; pub mod psu; pub mod service; diff --git a/power-policy-service/src/psu.rs b/power-policy-service/src/psu.rs index 81bd2133c..99b72cf61 100644 --- a/power-policy-service/src/psu.rs +++ b/power-policy-service/src/psu.rs @@ -7,7 +7,7 @@ use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::{Event, EventData}; /// Struct used to contain PSU event receivers and manage mapping from a receiver to its corresponding device. -pub struct ArrayEventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> +pub struct PsuEventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> where PSU::Inner: Psu, { @@ -15,7 +15,7 @@ where pub receivers: [R; N], } -impl<'a, const N: usize, PSU: Lockable, R: Receiver> ArrayEventReceivers<'a, N, PSU, R> +impl<'a, const N: usize, PSU: Lockable, R: Receiver> PsuEventReceivers<'a, N, PSU, R> where PSU::Inner: Psu, { diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index f9fcf5118..fa84aaa33 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -1,14 +1,12 @@ use core::cmp::Ordering; +use embedded_services::error; use embedded_services::named::Named; -use embedded_services::{debug, error}; use super::*; -use power_policy_interface::capability::ConsumerFlags; -use power_policy_interface::charger::Device as ChargerDevice; use power_policy_interface::psu; use power_policy_interface::service::event::Event as ServiceEvent; -use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::PsuState}; +use power_policy_interface::{capability::ConsumerPowerCapability, psu::PsuState}; /// State of the current consumer #[derive(Debug, PartialEq, Eq)] @@ -135,26 +133,26 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { embassy_time::Timer::after_millis(800).await; // If no chargers are registered, they won't receive the new power capability. - for node in self.context.charger_devices() { - let device = node.data::().ok_or(Error::InvalidDevice)?; + for node in self.registration.chargers() { + let mut locked_charger = node.lock().await; // Chargers should be powered at this point, but in case they are not... - if let power_policy_interface::charger::ChargerResponseData::UnpoweredAck = device - .execute_command(PolicyEvent::PolicyConfiguration( - connected_consumer.consumer_power_capability, - )) - .await? - { + if locked_charger.state().is_unpowered() { // Force charger CheckReady and InitRequest to get it into an initialized state. // This condition can get hit if we did not have a previous consumer and the charger is unpowered. info!("Charger is unpowered, forcing charger CheckReady and Init sequence"); - self.context.check_chargers_ready().await?; - self.context.init_chargers().await?; - device - .execute_command(PolicyEvent::PolicyConfiguration( - connected_consumer.consumer_power_capability, - )) - .await?; + + locked_charger.is_ready().await.map_err(|e| Error::Charger(e.into()))?; + locked_charger + .init_charger() + .await + .map_err(|e| Error::Charger(e.into()))?; } + + // Attach and update state to new capability + locked_charger + .attach_handler(connected_consumer.consumer_power_capability) + .await + .map_err(|e| Error::Charger(e.into()))?; } self.broadcast_event(ServiceEvent::ConsumerConnected( connected_consumer.psu, @@ -165,21 +163,15 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { Ok(()) } - /// Disconnect all chargers + /// Disconnect all chargers, skipping over unpowered chargers pub(super) async fn disconnect_chargers(&self) -> Result<(), Error> { - for node in self.context.charger_devices() { - let device = node.data::().ok_or(Error::InvalidDevice)?; - if let power_policy_interface::charger::ChargerResponseData::UnpoweredAck = device - .execute_command(PolicyEvent::PolicyConfiguration(ConsumerPowerCapability { - capability: PowerCapability { - voltage_mv: 0, - current_ma: 0, - }, - flags: ConsumerFlags::none(), - })) - .await? - { - debug!("Charger is unpowered, continuing disconnect_chargers()..."); + for charger in self.registration.chargers() { + let mut locked_charger = charger.lock().await; + if !locked_charger.state().is_unpowered() { + locked_charger + .detach_handler() + .await + .map_err(|e| Error::Charger(e.into()))?; } } @@ -278,6 +270,7 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { #[cfg(test)] mod tests { use super::*; + use power_policy_interface::capability::PowerCapability; const P0: PowerCapability = PowerCapability { voltage_mv: 5000, diff --git a/power-policy-service/src/service/context.rs b/power-policy-service/src/service/context.rs deleted file mode 100644 index 100861abe..000000000 --- a/power-policy-service/src/service/context.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Context for any power policy implementations -use embedded_services::{error, intrusive_list}; -use power_policy_interface::charger; -use power_policy_interface::charger::ChargerResponse; -use power_policy_interface::psu::Error; - -/// Power policy context -pub struct Context { - /// Registered chargers - charger_devices: intrusive_list::IntrusiveList, -} - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} - -impl Context { - /// Construct a new power policy Context - pub const fn new() -> Self { - Self { - charger_devices: intrusive_list::IntrusiveList::new(), - } - } - - /// Register a charger with the power policy service - pub fn register_charger( - &self, - charger: &'static impl charger::ChargerContainer, - ) -> Result<(), intrusive_list::Error> { - let charger = charger.get_charger(); - if self.get_charger(charger.id()).is_ok() { - return Err(intrusive_list::Error::NodeAlreadyInList); - } - - self.charger_devices.push(charger) - } - - /// Get a charger by its ID - pub fn get_charger(&self, id: charger::ChargerId) -> Result<&'static charger::Device, Error> { - for charger in &self.charger_devices { - if let Some(data) = charger.data::() { - if data.id() == id { - return Ok(data); - } - } else { - error!("Non-device located in charger list"); - } - } - Err(Error::InvalidDevice) - } - - /// Initialize chargers in hardware - pub async fn init_chargers(&self) -> ChargerResponse { - for charger in &self.charger_devices { - if let Some(data) = charger.data::() { - data.execute_command(charger::PolicyEvent::InitRequest) - .await - .inspect_err(|e| error!("Charger {:?} failed InitRequest: {:?}", data.id(), e))?; - } - } - - Ok(charger::ChargerResponseData::Ack) - } - - /// Check if charger hardware is ready for communications. - pub async fn check_chargers_ready(&self) -> ChargerResponse { - for charger in &self.charger_devices { - if let Some(data) = charger.data::() { - data.execute_command(charger::PolicyEvent::CheckReady) - .await - .inspect_err(|e| error!("Charger {:?} failed CheckReady: {:?}", data.id(), e))?; - } - } - Ok(charger::ChargerResponseData::Ack) - } - - /// Initialize Policy charger devices - pub async fn init(&self) -> Result<(), Error> { - // Check if the chargers are powered and able to communicate - self.check_chargers_ready().await?; - // Initialize chargers - self.init_chargers().await?; - - Ok(()) - } - - /// Provides access to the charger list - pub fn charger_devices(&self) -> &intrusive_list::IntrusiveList { - &self.charger_devices - } -} diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 3934bc34f..822ccfc8d 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -3,16 +3,18 @@ use core::ptr; pub mod config; pub mod consumer; -pub mod context; pub mod provider; pub mod registration; pub mod task; use embedded_services::named::Named; +use embedded_services::trace; use embedded_services::{error, event::Sender, info, sync::Lockable}; +use power_policy_interface::charger::{Charger, PsuState}; use power_policy_interface::{ - capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + capability::{ConsumerPowerCapability, ProviderPowerCapability}, + charger::{Event as ChargerEvent, EventData as ChargerEventData}, psu::{ Error, Psu, event::{Event as PsuEvent, EventData as PsuEventData}, @@ -57,8 +59,6 @@ where pub struct Service<'device, Reg: Registration<'device>> { /// Service registration registration: Reg, - /// Power policy context - pub context: &'device context::Context, /// State state: InternalState<'device, Reg::Psu>, /// Config @@ -67,10 +67,9 @@ pub struct Service<'device, Reg: Registration<'device>> { impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Create a new power policy - pub fn new(registration: Reg, context: &'device context::Context, config: config::Config) -> Self { + pub fn new(registration: Reg, config: config::Config) -> Self { Self { registration, - context, state: InternalState::default(), config, } @@ -207,4 +206,33 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { PsuEventData::Disconnected => self.process_notify_disconnect(device).await, } } + + async fn process_psu_state_change( + &mut self, + charger: &'device Reg::Charger, + psu_state: PsuState, + ) -> Result<(), Error> { + // Currently a no-op, but functionality might be added in the future. + let locked_charger = charger.lock().await; + trace!( + "Charger PSU state change to {:?} event recvd in charger state {:?}", + psu_state, + locked_charger.state() + ); + Ok(()) + } + + pub async fn process_charger_event(&mut self, event: ChargerEvent<'device, Reg::Charger>) -> Result<(), Error> { + let charger = event.charger; + + match event.event { + ChargerEventData::PsuStateChange(psu_state) => self.process_psu_state_change(charger, psu_state).await?, + _ => { + return Err(Error::Charger( + power_policy_interface::charger::ChargerError::UnknownEvent, + )); + } + }; + Ok(()) + } } diff --git a/power-policy-service/src/service/registration.rs b/power-policy-service/src/service/registration.rs index e7de916ec..722c832c7 100644 --- a/power-policy-service/src/service/registration.rs +++ b/power-policy-service/src/service/registration.rs @@ -1,16 +1,20 @@ //! Code related to registration with the power policy service. + use embedded_services::{event::Sender, sync::Lockable}; -use power_policy_interface::{psu, service::event::Event as ServiceEvent}; +use power_policy_interface::{charger, psu, service::event::Event as ServiceEvent}; /// Registration trait that abstracts over various registration details. pub trait Registration<'device> { type Psu: Lockable + 'device; type ServiceSender: Sender>; + type Charger: Lockable + 'device; /// Returns a slice to access PSU devices fn psus(&self) -> &[&'device Self::Psu]; /// Returns a slice to access power policy event senders fn event_senders(&mut self) -> &mut [Self::ServiceSender]; + /// Returns a slice to access charger devices + fn chargers(&self) -> &[&'device Self::Charger]; } /// A registration implementation based around arrays @@ -20,9 +24,13 @@ pub struct ArrayRegistration< const PSU_COUNT: usize, ServiceSender: Sender>, const SERVICE_SENDER_COUNT: usize, + Charger: Lockable + 'device, + const CHARGER_COUNT: usize, > { /// Array of registered PSUs pub psus: [&'device Psu; PSU_COUNT], + /// Array of registered chargers + pub chargers: [&'device Charger; CHARGER_COUNT], /// Array of power policy service event senders pub service_senders: [ServiceSender; SERVICE_SENDER_COUNT], } @@ -33,10 +41,14 @@ impl< const PSU_COUNT: usize, ServiceSender: Sender>, const SERVICE_SENDER_COUNT: usize, -> Registration<'device> for ArrayRegistration<'device, Psu, PSU_COUNT, ServiceSender, SERVICE_SENDER_COUNT> + Charger: Lockable + 'device, + const CHARGER_COUNT: usize, +> Registration<'device> + for ArrayRegistration<'device, Psu, PSU_COUNT, ServiceSender, SERVICE_SENDER_COUNT, Charger, CHARGER_COUNT> { type Psu = Psu; type ServiceSender = ServiceSender; + type Charger = Charger; fn psus(&self) -> &[&'device Self::Psu] { &self.psus @@ -45,4 +57,8 @@ impl< fn event_senders(&mut self) -> &mut [Self::ServiceSender] { &mut self.service_senders } + + fn chargers(&self) -> &[&'device Self::Charger] { + &self.chargers + } } diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index 41c3e2462..a3de389e6 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,24 +1,25 @@ use embedded_services::{error, info, sync::Lockable}; use embedded_services::event::Receiver; +use power_policy_interface::charger; use power_policy_interface::psu::event::EventData; use crate::service::registration::Registration; use super::Service; -/// Runs the power policy task. -pub async fn task< +/// Runs the power policy PSU task. +pub async fn psu_task< 'device, const PSU_COUNT: usize, S: Lockable>, Reg: Registration<'device>, PsuReceiver: Receiver, >( - mut psu_events: crate::psu::ArrayEventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, + mut psu_events: crate::psu::PsuEventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, policy: &'device S, ) -> ! { - info!("Starting power policy task"); + info!("Starting power policy PSU task"); loop { let event = psu_events.wait_event().await; @@ -27,3 +28,55 @@ pub async fn task< } } } + +/// Runs the power policy charger task. +pub async fn charger_task< + 'device, + const CHARGER_COUNT: usize, + S: Lockable>, + Reg: Registration<'device>, + ChargerReceiver: Receiver, +>( + mut charger_events: crate::charger::ChargerEventReceivers<'device, CHARGER_COUNT, Reg::Charger, ChargerReceiver>, + policy: &'device S, +) -> ! { + info!("Starting power policy charger task"); + loop { + let event = charger_events.wait_event().await; + + if let Err(e) = policy.lock().await.process_charger_event(event).await { + error!("Error processing request: {:?}", e); + } + } +} + +/// Runs the power policy unified task. +pub async fn task< + 'device, + const PSU_COUNT: usize, + const CHARGER_COUNT: usize, + S: Lockable>, + Reg: Registration<'device>, + PsuReceiver: Receiver, + ChargerReceiver: Receiver, +>( + mut psu_events: crate::psu::PsuEventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, + mut charger_events: crate::charger::ChargerEventReceivers<'device, CHARGER_COUNT, Reg::Charger, ChargerReceiver>, + policy: &'device S, +) -> ! { + info!("Starting power policy task"); + loop { + match embassy_futures::select::select(psu_events.wait_event(), charger_events.wait_event()).await { + embassy_futures::select::Either::First(psu_event) => { + if let Err(e) = policy.lock().await.process_psu_event(psu_event).await { + error!("Error processing PSU request: {:?}", e); + } + } + embassy_futures::select::Either::Second(charger_event) => { + if let Err(e) = policy.lock().await.process_charger_event(charger_event).await { + error!("Error processing charger request: {:?}", e); + } + } + } + } +} diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index ab3597878..77b126e87 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,9 +1,11 @@ #![allow(clippy::unwrap_used)] #![allow(dead_code)] -use embassy_sync::signal::Signal; +use embassy_sync::{channel, mutex::Mutex, signal::Signal}; +use embedded_batteries_async::charger::{MilliAmps, MilliVolts}; use embedded_services::{GlobalRawMutex, event::Sender, info, named::Named}; use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability}, + charger, psu::{Error, Psu, State, event::EventData}, }; @@ -113,3 +115,77 @@ impl<'a, S: Sender> Named for Mock<'a, S> { self.name } } + +pub struct ExampleCharger<'a> { + sender: channel::DynamicSender<'a, power_policy_interface::charger::event::EventData>, + state: charger::State, +} + +impl<'a> ExampleCharger<'a> { + pub fn new(sender: channel::DynamicSender<'a, power_policy_interface::charger::event::EventData>) -> Self { + Self { + sender, + state: charger::State::default(), + } + } + + pub fn assert_state(&self, internal_state: charger::InternalState, capability: Option) { + assert_eq!(*self.state.internal_state(), internal_state); + assert_eq!(*self.state.capability(), capability); + } + + pub async fn simulate_psu_state_change(&self, psu_state: charger::PsuState) { + self.sender.send(charger::EventData::PsuStateChange(psu_state)).await; + } +} + +impl<'a> embedded_batteries_async::charger::ErrorType for ExampleCharger<'a> { + type Error = core::convert::Infallible; +} + +impl<'a> embedded_batteries_async::charger::Charger for ExampleCharger<'a> { + async fn charging_current(&mut self, current: MilliAmps) -> Result { + Ok(current) + } + + async fn charging_voltage(&mut self, voltage: MilliVolts) -> Result { + Ok(voltage) + } +} + +impl<'a> charger::Charger for ExampleCharger<'a> { + type ChargerError = core::convert::Infallible; + + async fn init_charger(&mut self) -> Result { + info!("Charger init"); + Ok(charger::PsuState::Detached) + } + + fn attach_handler( + &mut self, + capability: ConsumerPowerCapability, + ) -> impl Future> { + info!("Charger attach: {:?}", capability); + async { Ok(()) } + } + + fn detach_handler(&mut self) -> impl Future> { + info!("Charger detach"); + async { Ok(()) } + } + + async fn is_ready(&mut self) -> Result<(), Self::ChargerError> { + info!("Charger check ready"); + Ok(()) + } + + fn state(&self) -> &charger::State { + &self.state + } + + fn state_mut(&mut self) -> &mut charger::State { + &mut self.state + } +} + +pub type ChargerType<'a> = Mutex>; diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 1c6b6e516..aab4d1a0c 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -21,13 +21,13 @@ use power_policy_interface::{ service::{UnconstrainedState, event::Event as ServiceEvent}, }; use power_policy_service::service::{Service, config::Config}; -use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; +use power_policy_service::{psu::PsuEventReceivers, service::registration::ArrayRegistration}; pub mod mock; use mock::Mock; -use crate::common::mock::FnCall; +use crate::common::mock::{ChargerType, FnCall}; pub const MINIMAL_POWER: PowerCapability = PowerCapability { voltage_mv: 5000, @@ -58,6 +58,8 @@ pub type ServiceType<'device, 'sender> = Service< 2, DynamicSender<'sender, ServiceEvent<'device, DeviceType<'device>>>, 1, + ChargerType<'device>, + 0, >, >; @@ -66,7 +68,7 @@ pub type ServiceMutex<'device, 'sender> = Mutex( completion_signal: &'device Signal, power_policy: &ServiceMutex<'device, 'sender>, - mut event_receivers: ArrayEventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, + mut event_receivers: PsuEventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { power_policy.lock().await.process_psu_event(result).await.unwrap(); @@ -111,7 +113,6 @@ pub async fn run_test(timeout: Duration, mut test: impl Test, config: Config) { let device1_receiver = device1_event_channel.dyn_receiver(); let device1 = Mutex::new(Mock::new("PSU1", device1_sender, &device1_signal)); - let service_context = power_policy_service::service::context::Context::new(); let completion_signal = Signal::new(); // For simplicity, Test::run is only generic over a single lifetime. But this causes issues with the drop checker because @@ -124,11 +125,11 @@ pub async fn run_test(timeout: Duration, mut test: impl Test, config: Config) { let power_policy_registration = ArrayRegistration { psus: [&device0, &device1], service_senders: [service_event_channel.dyn_sender()], + chargers: [], }; let power_policy = Mutex::new(power_policy_service::service::Service::new( power_policy_registration, - &service_context, config, )); @@ -138,7 +139,7 @@ pub async fn run_test(timeout: Duration, mut test: impl Test, config: Config) { power_policy_task( &completion_signal, &power_policy, - ArrayEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), + PsuEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { test.run( From 238692b670ff5c49bf2a28b1918a0ca478b8a4b1 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:02:58 -0700 Subject: [PATCH 70/79] cfu-service: Create basic updater struct (#809) Use the existing CFU logic in type-c-service to create a struct that can perform CFU updates on anything that implements a basic FW update trait --- .github/workflows/check.yml | 17 +- Cargo.lock | 25 ++ Cargo.toml | 4 + cfu-service/Cargo.toml | 12 + cfu-service/src/basic/config.rs | 47 +++ cfu-service/src/basic/event_receiver.rs | 197 +++++++++++++ cfu-service/src/basic/mod.rs | 302 +++++++++++++++++++ cfu-service/src/basic/state.rs | 56 ++++ cfu-service/src/basic/test.rs | 366 ++++++++++++++++++++++++ cfu-service/src/customization.rs | 8 + cfu-service/src/lib.rs | 5 + cfu-service/src/mocks/customization.rs | 83 ++++++ cfu-service/src/mocks/mod.rs | 2 + examples/rt685s-evk/Cargo.lock | 9 + examples/std/Cargo.lock | 9 + fw-update-interface-mocks/Cargo.toml | 16 ++ fw-update-interface-mocks/src/basic.rs | 151 ++++++++++ fw-update-interface-mocks/src/lib.rs | 2 + fw-update-interface/Cargo.toml | 26 ++ fw-update-interface/src/basic.rs | 43 +++ fw-update-interface/src/lib.rs | 3 + 21 files changed, 1382 insertions(+), 1 deletion(-) create mode 100644 cfu-service/src/basic/config.rs create mode 100644 cfu-service/src/basic/event_receiver.rs create mode 100644 cfu-service/src/basic/mod.rs create mode 100644 cfu-service/src/basic/state.rs create mode 100644 cfu-service/src/basic/test.rs create mode 100644 cfu-service/src/customization.rs create mode 100644 cfu-service/src/mocks/customization.rs create mode 100644 cfu-service/src/mocks/mod.rs create mode 100644 fw-update-interface-mocks/Cargo.toml create mode 100644 fw-update-interface-mocks/src/basic.rs create mode 100644 fw-update-interface-mocks/src/lib.rs create mode 100644 fw-update-interface/Cargo.toml create mode 100644 fw-update-interface/src/basic.rs create mode 100644 fw-update-interface/src/lib.rs diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 194c4970d..84f273f84 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -31,6 +31,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true name: check +env: + # Crates that require std and won't build on embedded-targets + STD_CRATES: "fw-update-interface-mocks" jobs: fmt: @@ -93,6 +96,8 @@ jobs: # Get early warning of new lints which are regularly introduced in beta channels. toolchain: [stable, beta] target: [x86_64-unknown-linux-gnu, thumbv8m.main-none-eabihf] + env: + COMMON_HACK_ARGS: "--feature-powerset --mutually-exclusive-features=log,defmt,defmt-timestamp-uptime" steps: - uses: actions/checkout@v4 with: @@ -112,7 +117,11 @@ jobs: # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 # --feature-powerset runs for every combination of features - name: cargo hack - run: cargo hack --feature-powerset --mutually-exclusive-features=log,defmt,defmt-timestamp-uptime clippy --locked --target ${{ matrix.target }} + if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} + 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 }} deny: # cargo-deny checks licenses, advisories, sources, and bans for @@ -203,9 +212,15 @@ jobs: with: name: updated-lock-files - name: cargo +${{ matrix.msrv }} check + if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} run: | cargo check -F log --locked --target ${{ matrix.target }} cargo check -F defmt --locked --target ${{ matrix.target }} + - 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 }} check-arm-examples: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index b38c692c1..cebf16175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,14 +331,20 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" name = "cfu-service" version = "0.1.0" dependencies = [ + "critical-section", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", "embedded-cfu-protocol", "embedded-services", + "env_logger", + "fw-update-interface", + "fw-update-interface-mocks", "heapless 0.8.0", "log", + "static_cell", + "tokio", ] [[package]] @@ -941,6 +947,25 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +[[package]] +name = "fw-update-interface" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", + "log", + "tokio", +] + +[[package]] +name = "fw-update-interface-mocks" +version = "0.1.0" +dependencies = [ + "embedded-services", + "fw-update-interface", + "tokio", +] + [[package]] name = "generator" version = "0.8.7" diff --git a/Cargo.toml b/Cargo.toml index cc22f94d8..f3fd5d7c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ members = [ "power-policy-interface", "odp-service-common", "type-c-interface", + "fw-update-interface", + "fw-update-interface-mocks", ] exclude = ["examples/*"] @@ -91,6 +93,8 @@ embedded-hal-async = "1.0" embedded-services = { path = "./embedded-service" } embedded-storage-async = "0.4.1" embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb-pd", default-features = false } +fw-update-interface = { path = "./fw-update-interface" } +fw-update-interface-mocks = { path = "./fw-update-interface-mocks" } mctp-rs = { git = "https://github.com/dymk/mctp-rs" } num_enum = { version = "0.7.5", default-features = false } portable-atomic = { version = "1.11", default-features = false } diff --git a/cfu-service/Cargo.toml b/cfu-service/Cargo.toml index 811c82ea7..ae960ad12 100644 --- a/cfu-service/Cargo.toml +++ b/cfu-service/Cargo.toml @@ -17,9 +17,19 @@ embassy-sync.workspace = true embassy-time.workspace = true embedded-cfu-protocol.workspace = true embedded-services.workspace = true +fw-update-interface.workspace = true heapless.workspace = true log = { workspace = true, optional = true } +[dev-dependencies] +static_cell.workspace = true +critical-section = { workspace = true, features = ["std"] } +embassy-time = { workspace = true, features = ["std", "generic-queue-8"] } +tokio = { workspace = true, features = ["rt", "macros", "time"] } +env_logger = "0.11.8" +log = { workspace = true } +fw-update-interface-mocks = { workspace = true } + [features] default = [] defmt = [ @@ -28,6 +38,7 @@ defmt = [ "embassy-time/defmt", "embassy-sync/defmt", "embedded-cfu-protocol/defmt", + "fw-update-interface/defmt", ] log = [ "dep:log", @@ -35,4 +46,5 @@ log = [ "embassy-time/log", "embassy-sync/log", "embedded-cfu-protocol/log", + "fw-update-interface/log", ] diff --git a/cfu-service/src/basic/config.rs b/cfu-service/src/basic/config.rs new file mode 100644 index 000000000..793de5b1b --- /dev/null +++ b/cfu-service/src/basic/config.rs @@ -0,0 +1,47 @@ +//! Configuration structs + +use embassy_time::Duration; + +/// Base interval for checking for FW update timeouts and recovery attempts +pub const DEFAULT_FW_UPDATE_TICK_INTERVAL: Duration = Duration::from_secs(5); +/// Default number of ticks before we consider a firmware update to have timed out +/// 300 seconds at 5 seconds per tick +pub const DEFAULT_FW_UPDATE_TIMEOUT_TICKS: u32 = 60; + +/// Config values for FW update recovery +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Recovery { + /// Interval between recovery ticks + pub tick_interval: Duration, + /// Timeout (in recovery ticks) before we assume the update has failed. + pub update_timeout_ticks: u32, +} + +impl Default for Recovery { + fn default() -> Self { + Self { + tick_interval: DEFAULT_FW_UPDATE_TICK_INTERVAL, + update_timeout_ticks: DEFAULT_FW_UPDATE_TIMEOUT_TICKS, + } + } +} + +/// Configuration for [`crate::basic::Updater`] +#[derive(Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Updater { + /// Recovery configuration for the updater + pub recovery: Recovery, +} + +/// Configuration for [`crate::basic::event_receiver::EventReceiver`] +#[derive(Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct EventReceiver { + /// Recovery configuration for the event receiver + pub recovery: Recovery, +} diff --git a/cfu-service/src/basic/event_receiver.rs b/cfu-service/src/basic/event_receiver.rs new file mode 100644 index 000000000..940a15291 --- /dev/null +++ b/cfu-service/src/basic/event_receiver.rs @@ -0,0 +1,197 @@ +use embassy_futures::select::{Either, select}; +use embassy_time::{Instant, Timer}; +use embedded_services::{debug, error, sync::Lockable}; + +use crate::basic::{ + Output, + config::EventReceiver as Config, + state::{FwUpdateState, SharedState}, +}; + +/// CFU events +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + /// CFU request + Request(crate::component::RequestData), + /// Recovery tick + /// + /// Occurs when the FW update has timed out to abort the update and return hardware to its normal state + RecoveryTick, +} + +/// Struct to receive CFU events. +pub struct EventReceiver<'a, Shared: Lockable> { + /// Config + config: Config, + /// CFU device used for firmware updates + cfu_device: &'static crate::component::CfuDevice, + /// State shared with [`crate::basic::Updater`] + shared_state: &'a Shared, +} + +impl<'a, Shared: Lockable> EventReceiver<'a, Shared> { + /// Create a new CFU event receiver + pub fn new(cfu_device: &'static crate::component::CfuDevice, shared_state: &'a Shared, config: Config) -> Self { + Self { + cfu_device, + shared_state, + config, + } + } + + /// Wait for the next CFU event + pub async fn wait_next(&mut self) -> Event { + loop { + let (fw_update_state, next_recovery_tick) = { + let state = self.shared_state.lock().await; + (state.fw_update_state, state.next_recovery_tick) + }; + match fw_update_state { + FwUpdateState::Idle => { + // No FW update in progress, just wait for a command + return Event::Request(self.cfu_device.wait_request().await); + } + FwUpdateState::InProgress(ticks) => { + match select(self.cfu_device.wait_request(), Timer::at(next_recovery_tick)).await { + Either::First(command) => return Event::Request(command), + Either::Second(_) => { + debug!("CFU tick: {}", ticks); + + let mut shared_state = self.shared_state.lock().await; + shared_state.next_recovery_tick = Instant::now() + self.config.recovery.tick_interval; + + if ticks + 1 < self.config.recovery.update_timeout_ticks { + shared_state.fw_update_state = FwUpdateState::InProgress(ticks + 1); + continue; + } else { + error!( + "FW update timed out after {} ticks", + self.config.recovery.update_timeout_ticks + ); + shared_state.fw_update_state = FwUpdateState::Recovery; + return Event::RecoveryTick; + } + } + } + } + FwUpdateState::Recovery => { + // Recovery state, wait for the next attempt to recover the device + let next_recovery_tick = self.shared_state.lock().await.next_recovery_tick; + Timer::at(next_recovery_tick).await; + self.shared_state.lock().await.next_recovery_tick = + Instant::now() + self.config.recovery.tick_interval; + debug!("FW update ticker ticked"); + return Event::RecoveryTick; + } + } + } + } + + /// Finalize the processing of an output + // TODO: remove this when we refactor CFU + pub async fn finalize(&mut self, output: Output) { + if let Output::CfuResponse(response) = output { + self.cfu_device.send_response(response).await + } + } +} + +#[cfg(test)] +mod test { + use crate::{basic::config::Recovery, component::CfuDevice}; + + use super::*; + use embassy_sync::mutex::Mutex; + use embassy_time::{Duration, Instant, TimeoutError, with_timeout}; + use embedded_services::GlobalRawMutex; + use static_cell::StaticCell; + + /// Test that we get recovery ticks as expected + #[tokio::test] + async fn test_recovery_timeout() { + static CFU_DEVICE: StaticCell = StaticCell::new(); + + // Maximum timeout for the recovery entry, actual time should be 1000, but gives us some margin + const RECOVERY_ENTRY_MAX_TIMEOUT: Duration = Duration::from_millis(1100); + // Maximum timeout for an individual recovery tick, actual time should be 100, but gives us some margin + const RECOVERY_TICK_MAX_TIMEOUT: Duration = Duration::from_millis(110); + // Expected measured interval between recovery ticks, actual time should be 100, but undershoot slightly for some margin + const EXPECTED_RECOVERY_TICK_INTERVAL: Duration = Duration::from_millis(90); + + let shared_state: Mutex = Mutex::new(SharedState::default()); + let cfu_device = CFU_DEVICE.init(CfuDevice::new(0)); + let recovery_config = Recovery { + tick_interval: Duration::from_millis(100), + update_timeout_ticks: 10, + }; + + let mut event_receiver = EventReceiver::new( + cfu_device, + &shared_state, + Config { + recovery: recovery_config, + }, + ); + + // First test the recovery timer isn't active in the idle state + assert_eq!( + with_timeout(RECOVERY_ENTRY_MAX_TIMEOUT, event_receiver.wait_next()).await, + Err(TimeoutError), + ); + assert_eq!( + event_receiver.shared_state.lock().await.fw_update_state, + FwUpdateState::Idle + ); + + // Start the recovery ticker, normally the update struct handles this. + shared_state + .lock() + .await + .enter_in_progress(recovery_config.tick_interval); + + let start = Instant::now(); + assert_eq!( + with_timeout(RECOVERY_ENTRY_MAX_TIMEOUT, event_receiver.wait_next()).await, + Ok(Event::RecoveryTick), + ); + let duration = Instant::now() - start; + + // Check that we waited approximately the correct amount of time + assert!(duration.as_millis() >= 1000); + assert_eq!( + event_receiver.shared_state.lock().await.fw_update_state, + FwUpdateState::Recovery + ); + + // Check the first recovery tick after the state transition + let start = Instant::now(); + assert_eq!( + with_timeout(RECOVERY_TICK_MAX_TIMEOUT, event_receiver.wait_next()).await, + Ok(Event::RecoveryTick), + ); + let duration = Instant::now() - start; + + // Check that we waited approximately the correct amount of time + assert!(duration >= EXPECTED_RECOVERY_TICK_INTERVAL); + assert_eq!( + event_receiver.shared_state.lock().await.fw_update_state, + FwUpdateState::Recovery + ); + + // Check subsequent recovery ticks + let start = Instant::now(); + assert_eq!( + with_timeout(RECOVERY_TICK_MAX_TIMEOUT, event_receiver.wait_next()).await, + Ok(Event::RecoveryTick), + ); + let duration = Instant::now() - start; + + // Check that we waited approximately the correct amount of time + assert!(duration >= EXPECTED_RECOVERY_TICK_INTERVAL); + assert_eq!( + event_receiver.shared_state.lock().await.fw_update_state, + FwUpdateState::Recovery + ); + } +} diff --git a/cfu-service/src/basic/mod.rs b/cfu-service/src/basic/mod.rs new file mode 100644 index 000000000..301d703a8 --- /dev/null +++ b/cfu-service/src/basic/mod.rs @@ -0,0 +1,302 @@ +//! Basic CFU implementation over the basic [`FwUpdate`] trait. +use crate::{ + basic::{ + config::Updater as Config, + event_receiver::Event, + state::{FwUpdateState, SharedState}, + }, + component::{InternalResponseData, RequestData}, + customization::Customization, +}; +use embedded_cfu_protocol::protocol_definitions::*; +use embedded_services::{debug, error, sync::Lockable}; +use fw_update_interface::basic::FwUpdate; + +pub mod config; +pub mod event_receiver; +pub mod state; + +#[cfg(test)] +mod test; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Output { + CfuResponse(InternalResponseData), + CfuRecovery, +} + +/// Basic CFU handler that bridges CFU protocol commands with the [`FwUpdate`] trait. +/// +/// This struct is generic over a firmware offer validator and processes CFU commands +/// by delegating firmware operations to any [`FwUpdate`] implementor passed to each method. +pub struct Updater<'a, Device: Lockable, Shared: Lockable, Cust: Customization> { + device: &'a Device, + component_id: ComponentId, + customization: Cust, + shared_state: &'a Shared, + config: Config, +} + +impl<'a, Device: Lockable, Shared: Lockable, Cust: Customization> + Updater<'a, Device, Shared, Cust> +{ + /// Create a new CfuBasic instance + pub fn new( + device: &'a Device, + shared_state: &'a Shared, + config: Config, + component_id: ComponentId, + customization: Cust, + ) -> Self { + Self { + device, + shared_state, + component_id, + customization, + config, + } + } + + /// Create a response with an invalid firmware version + fn create_invalid_fw_version_response(&self) -> InternalResponseData { + let dev_inf = FwVerComponentInfo::new(FwVersion::new(0xffffffff), self.component_id); + let comp_info: [FwVerComponentInfo; MAX_CMPT_COUNT] = [dev_inf; MAX_CMPT_COUNT]; + InternalResponseData::FwVersionResponse(GetFwVersionResponse { + header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), + component_info: comp_info, + }) + } + + /// Create an offer rejection response + fn create_offer_rejection() -> InternalResponseData { + InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_with_failure( + HostToken::Driver, + OfferRejectReason::InvalidComponent, + OfferStatus::Reject, + )) + } + + /// Returns a copy of the current update state + pub async fn update_state(&self) -> FwUpdateState { + self.shared_state.lock().await.fw_update_state + } + + /// Gives immutable access to the customization object + pub fn customization(&self) -> &Cust { + &self.customization + } + + /// Gives mutable access to the customization object + pub fn customization_mut(&mut self) -> &mut Cust { + &mut self.customization + } + + /// Process a CFU event + pub async fn process_event(&mut self, event: Event) -> Output { + match event { + Event::Request(request) => { + let response = self.process_cfu_command(&request).await; + Output::CfuResponse(response) + } + Event::RecoveryTick => { + // FW Update recovery tick, process recovery attempts + self.process_recovery_tick().await; + Output::CfuRecovery + } + } + } + + /// Process a GetFwVersion command + pub async fn process_get_fw_version(&mut self) -> InternalResponseData { + let result = self.device.lock().await.get_active_fw_version().await; + let version = match result { + Ok(v) => v, + Err(e) => { + error!("Failed to get active firmware version: {:?}", e); + return self.create_invalid_fw_version_response(); + } + }; + + let dev_inf = FwVerComponentInfo::new(FwVersion::new(version), self.component_id); + let comp_info: [FwVerComponentInfo; MAX_CMPT_COUNT] = [dev_inf; MAX_CMPT_COUNT]; + InternalResponseData::FwVersionResponse(GetFwVersionResponse { + header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), + component_info: comp_info, + }) + } + + /// Process a GiveOffer command + pub async fn process_give_offer(&mut self, offer: &FwUpdateOffer) -> InternalResponseData { + if offer.component_info.component_id != self.component_id { + return Self::create_offer_rejection(); + } + + let result = self.device.lock().await.get_active_fw_version().await; + let version = match result { + Ok(v) => v, + Err(e) => { + error!("Failed to get active firmware version: {:?}", e); + return Self::create_offer_rejection(); + } + }; + + InternalResponseData::OfferResponse(self.customization.validate(FwVersion::new(version), offer)) + } + + /// Process an AbortUpdate command + pub async fn process_abort_update(&mut self) -> InternalResponseData { + let result = self.device.lock().await.abort_fw_update().await; + match result { + Ok(_) => { + debug!("FW update aborted successfully"); + self.shared_state.lock().await.enter_idle(); + } + Err(e) => { + error!("Failed to abort FW update: {:?}", e); + self.shared_state.lock().await.enter_recovery(); + } + } + + InternalResponseData::ComponentPrepared + } + + /// Process a GiveContent command + pub async fn process_give_content(&mut self, content: &FwUpdateContentCommand) -> InternalResponseData { + let data = if let Some(data) = content.data.get(0..content.header.data_length as usize) { + data + } else { + return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + content.header.sequence_num, + CfuUpdateContentResponseStatus::ErrorPrepare, + )); + }; + + debug!("Got content {:#?}", content); + if content.header.flags & FW_UPDATE_FLAG_FIRST_BLOCK != 0 { + debug!("Got first block"); + + let result = self.device.lock().await.start_fw_update().await; + match result { + Ok(_) => { + debug!("FW update started successfully"); + } + Err(e) => { + error!("Failed to start FW update: {:?}", e); + self.shared_state.lock().await.enter_recovery(); + return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + content.header.sequence_num, + CfuUpdateContentResponseStatus::ErrorPrepare, + )); + } + } + + self.shared_state + .lock() + .await + .enter_in_progress(self.config.recovery.tick_interval); + } + + let result = self + .device + .lock() + .await + .write_fw_contents(content.header.firmware_address as usize, data) + .await; + match result { + Ok(_) => { + debug!("Block written successfully"); + } + Err(e) => { + error!("Failed to write block: {:?}", e); + return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + content.header.sequence_num, + CfuUpdateContentResponseStatus::ErrorWrite, + )); + } + } + + if content.header.flags & FW_UPDATE_FLAG_LAST_BLOCK != 0 { + let result = self.device.lock().await.finalize_fw_update().await; + match result { + Ok(_) => { + debug!("FW update finalized successfully"); + self.shared_state.lock().await.enter_idle(); + } + Err(e) => { + error!("Failed to finalize FW update: {:?}", e); + self.shared_state.lock().await.enter_recovery(); + return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + content.header.sequence_num, + CfuUpdateContentResponseStatus::ErrorWrite, + )); + } + } + } + + InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + content.header.sequence_num, + CfuUpdateContentResponseStatus::Success, + )) + } + + /// Process a CFU recovery tick. + pub async fn process_recovery_tick(&mut self) { + // Update timed out, attempt to abort + self.shared_state.lock().await.enter_recovery(); + let result = self.device.lock().await.abort_fw_update().await; + match result { + Ok(_) => { + debug!("FW update aborted successfully"); + self.shared_state.lock().await.enter_idle(); + } + Err(e) => { + error!("Failed to abort FW update: {:?}", e); + } + } + } + + /// Process a CFU command, dispatching to the appropriate handler + pub async fn process_cfu_command(&mut self, command: &RequestData) -> InternalResponseData { + let fw_update_state = self.shared_state.lock().await.fw_update_state; + if fw_update_state == FwUpdateState::Recovery { + debug!("FW update in recovery state, rejecting command"); + return InternalResponseData::ComponentBusy; + } + + match command { + RequestData::FwVersionRequest => { + debug!("Got FwVersionRequest"); + self.process_get_fw_version().await + } + RequestData::GiveOffer(offer) => { + debug!("Got GiveOffer"); + self.process_give_offer(offer).await + } + RequestData::GiveContent(content) => { + debug!("Got GiveContent"); + self.process_give_content(content).await + } + RequestData::AbortUpdate => { + debug!("Got AbortUpdate"); + self.process_abort_update().await + } + RequestData::FinalizeUpdate => { + debug!("Got FinalizeUpdate"); + InternalResponseData::ComponentPrepared + } + RequestData::PrepareComponentForUpdate => { + debug!("Got PrepareComponentForUpdate"); + InternalResponseData::ComponentPrepared + } + RequestData::GiveOfferExtended(_) => { + debug!("Got GiveExtendedOffer, rejecting"); + Self::create_offer_rejection() + } + RequestData::GiveOfferInformation(_) => { + debug!("Got GiveOfferInformation, rejecting"); + Self::create_offer_rejection() + } + } + } +} diff --git a/cfu-service/src/basic/state.rs b/cfu-service/src/basic/state.rs new file mode 100644 index 000000000..c83773614 --- /dev/null +++ b/cfu-service/src/basic/state.rs @@ -0,0 +1,56 @@ +use embassy_time::{Duration, Instant}; + +/// Current state of the firmware update process +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FwUpdateState { + /// None in progress + #[default] + Idle, + /// Firmware update in progress. + /// Integer is number of recovery ticks that have occurred since the start of the update. + InProgress(u32), + /// Firmware update has failed and the device is in an unknown state + Recovery, +} + +/// State shared between [`crate::basic::event_receiver::EventReceiver`] and [`crate::basic::Updater`] +#[derive(Clone, Copy)] +pub struct SharedState { + /// Current update state + pub(super) fw_update_state: FwUpdateState, + /// Next recovery tick + pub(super) next_recovery_tick: Instant, +} + +impl SharedState { + pub fn new() -> Self { + Self { + fw_update_state: FwUpdateState::Idle, + next_recovery_tick: Instant::MAX, + } + } + + pub(super) fn enter_idle(&mut self) { + self.fw_update_state = FwUpdateState::Idle; + self.next_recovery_tick = Instant::MAX; + } + + pub(super) fn enter_in_progress(&mut self, next_recovery_tick: Duration) { + self.fw_update_state = FwUpdateState::InProgress(0); + self.next_recovery_tick = Instant::now() + next_recovery_tick; + } + + pub(super) fn enter_recovery(&mut self) { + self.fw_update_state = FwUpdateState::Recovery; + if self.next_recovery_tick == Instant::MAX { + self.next_recovery_tick = Instant::now(); + } + } +} + +impl Default for SharedState { + fn default() -> Self { + Self::new() + } +} diff --git a/cfu-service/src/basic/test.rs b/cfu-service/src/basic/test.rs new file mode 100644 index 000000000..c675de669 --- /dev/null +++ b/cfu-service/src/basic/test.rs @@ -0,0 +1,366 @@ +//! Tests for [`crate::basic::Updater`] +#![allow(clippy::unwrap_used)] +extern crate std; + +use crate::{ + basic::{ + Output, Updater, + event_receiver::Event, + state::{FwUpdateState, SharedState}, + }, + component::{InternalResponseData, RequestData}, +}; +use embassy_sync::{mutex::Mutex, once_lock::OnceLock}; +use embassy_time::{Duration, with_timeout}; +use embedded_cfu_protocol::protocol_definitions::{ + CfuUpdateContentResponseStatus, DEFAULT_DATA_LENGTH, FW_UPDATE_FLAG_FIRST_BLOCK, FW_UPDATE_FLAG_LAST_BLOCK, + FwUpdateContentCommand, FwUpdateContentHeader, FwUpdateContentResponse, FwUpdateOffer, FwUpdateOfferResponse, + FwVerComponentInfo, FwVersion, GetFwVerRespHeaderByte3, GetFwVersionResponse, GetFwVersionResponseHeader, + HostToken, MAX_CMPT_COUNT, +}; +use embedded_services::GlobalRawMutex; + +use crate::mocks::customization::{FnCall as CustomizationFnCall, Mock as MockCustomization}; +use fw_update_interface_mocks::basic::{FnCall as FwFnCall, Mock}; + +use std::vec; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +const CURRENT_FW_VERSION: u32 = 0x12345678; +const NEW_FW_VERSION: u32 = 0x89abcdef; + +const DEVICE0_COMPONENT_ID: u8 = 5; + +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); + +type DeviceType = Mutex; +type SharedStateType = Mutex; +type UpdaterType<'a> = Updater<'a, DeviceType, SharedStateType, MockCustomization>; + +/// Test the basic flow of the updater. +/// +/// This will get the FW version, give an offer, then send a start, middle, and end content +pub struct TestBasicFlow; + +impl Test for TestBasicFlow { + async fn run<'a>(&mut self, device: &'a DeviceType, cfu_basic: &'a mut UpdaterType<'a>) { + { + // Get FW version + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::FwVersionRequest)), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::FwVersionResponse(GetFwVersionResponse { + header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), + component_info: [FwVerComponentInfo::new(FwVersion::new(CURRENT_FW_VERSION), DEVICE0_COMPONENT_ID); + MAX_CMPT_COUNT], + })) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::Idle); + assert_eq!(device.lock().await.fn_calls.len(), 1); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::GetActiveFwVersion + ); + } + + { + // Give offer + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::GiveOffer(FwUpdateOffer::new( + HostToken::Driver, + DEVICE0_COMPONENT_ID, + FwVersion::new(NEW_FW_VERSION), + 0, + 0, + )))), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_accept( + HostToken::Driver + ))) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::Idle); + assert_eq!(device.lock().await.fn_calls.len(), 1); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::GetActiveFwVersion + ); + + assert_eq!(cfu_basic.customization().fn_calls.len(), 1); + assert_eq!( + cfu_basic.customization_mut().fn_calls.pop_front(), + Some(CustomizationFnCall::Validate( + FwVersion::new(CURRENT_FW_VERSION), + FwUpdateOffer::new( + HostToken::Driver, + DEVICE0_COMPONENT_ID, + FwVersion::new(NEW_FW_VERSION), + 0, + 0, + ) + )) + ); + } + + { + // Give first content block + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::GiveContent(FwUpdateContentCommand { + header: FwUpdateContentHeader { + flags: FW_UPDATE_FLAG_FIRST_BLOCK, + data_length: DEFAULT_DATA_LENGTH as u8, + sequence_num: 0, + firmware_address: 0x0, + }, + data: [1; DEFAULT_DATA_LENGTH], + }))), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + 0, + CfuUpdateContentResponseStatus::Success + ))) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::InProgress(0)); + assert_eq!(device.lock().await.fn_calls.len(), 2); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::StartFwUpdate + ); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::WriteFwContents(0, vec![1; DEFAULT_DATA_LENGTH]) + ); + } + + { + // Give middle content block + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::GiveContent(FwUpdateContentCommand { + header: FwUpdateContentHeader { + flags: 0, + data_length: DEFAULT_DATA_LENGTH as u8, + sequence_num: 1, + firmware_address: 0x0, + }, + data: [2; DEFAULT_DATA_LENGTH], + }))), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + 1, + CfuUpdateContentResponseStatus::Success + ))) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::InProgress(0)); + assert_eq!(device.lock().await.fn_calls.len(), 1); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::WriteFwContents(0, vec![2; DEFAULT_DATA_LENGTH]) + ); + } + + { + // Give final content block + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::GiveContent(FwUpdateContentCommand { + header: FwUpdateContentHeader { + flags: FW_UPDATE_FLAG_LAST_BLOCK, + data_length: DEFAULT_DATA_LENGTH as u8, + sequence_num: 2, + firmware_address: 0x0, + }, + data: [3; DEFAULT_DATA_LENGTH], + }))), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + 2, + CfuUpdateContentResponseStatus::Success + ))) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::Idle); + assert_eq!(device.lock().await.fn_calls.len(), 2); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::WriteFwContents(0, vec![3; DEFAULT_DATA_LENGTH]) + ); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::FinalizeFwUpdate + ); + } + } +} + +/// Test that the recovery flow works immediately after sending the first content block. +struct TestStartRecoveryFlow; + +impl Test for TestStartRecoveryFlow { + async fn run<'a>(&mut self, device: &'a DeviceType, cfu_basic: &'a mut UpdaterType<'a>) { + { + // Give offer + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::GiveOffer(FwUpdateOffer::new( + HostToken::Driver, + DEVICE0_COMPONENT_ID, + FwVersion::new(NEW_FW_VERSION), + 0, + 0, + )))), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_accept( + HostToken::Driver + ))) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::Idle); + assert_eq!(device.lock().await.fn_calls.len(), 1); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::GetActiveFwVersion + ); + + assert_eq!(cfu_basic.customization().fn_calls.len(), 1); + assert_eq!( + cfu_basic.customization_mut().fn_calls.pop_front(), + Some(CustomizationFnCall::Validate( + FwVersion::new(CURRENT_FW_VERSION), + FwUpdateOffer::new( + HostToken::Driver, + DEVICE0_COMPONENT_ID, + FwVersion::new(NEW_FW_VERSION), + 0, + 0, + ) + )) + ); + } + + { + // Give first content block + let output = with_timeout( + PER_CALL_TIMEOUT, + cfu_basic.process_event(Event::Request(RequestData::GiveContent(FwUpdateContentCommand { + header: FwUpdateContentHeader { + flags: FW_UPDATE_FLAG_FIRST_BLOCK, + data_length: DEFAULT_DATA_LENGTH as u8, + sequence_num: 0, + firmware_address: 0x0, + }, + data: [1; DEFAULT_DATA_LENGTH], + }))), + ) + .await + .unwrap(); + + assert_eq!( + output, + Output::CfuResponse(InternalResponseData::ContentResponse(FwUpdateContentResponse::new( + 0, + CfuUpdateContentResponseStatus::Success + ))) + ); + assert_eq!(cfu_basic.update_state().await, FwUpdateState::InProgress(0)); + assert_eq!(device.lock().await.fn_calls.len(), 2); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::StartFwUpdate + ); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::WriteFwContents(0, vec![1; DEFAULT_DATA_LENGTH]) + ); + } + + { + // Trigger recovery + let output = with_timeout(PER_CALL_TIMEOUT, cfu_basic.process_event(Event::RecoveryTick)) + .await + .unwrap(); + + assert_eq!(output, Output::CfuRecovery); + // Idle since we should have successfully recovered + assert_eq!(cfu_basic.update_state().await, FwUpdateState::Idle); + assert_eq!(device.lock().await.fn_calls.len(), 1); + assert_eq!( + device.lock().await.fn_calls.pop_front().unwrap(), + FwFnCall::AbortFwUpdate + ); + } + } +} + +#[tokio::test] +async fn run_test_basic_flow() { + run_test(DEFAULT_TIMEOUT, TestBasicFlow).await; +} + +#[tokio::test] +async fn run_test_start_recovery_flow() { + run_test(DEFAULT_TIMEOUT, TestStartRecoveryFlow).await; +} + +/// Trait for runnable tests. +/// +/// This exists because there are lifetime issues with being generic over FnOnce or FnMut. +/// Those can be resolved, but having a dedicated trait is simpler. +pub trait Test { + fn run<'a>(&mut self, device: &'a DeviceType, cfu_basic: &'a mut UpdaterType<'a>) -> impl Future; +} + +/// Test running function +async fn run_test(timeout: Duration, 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::Debug).try_init(); + embedded_services::init().await; + + let shared_state: Mutex = Mutex::new(SharedState::default()); + let device = Mutex::new(Mock::new("PSU0", CURRENT_FW_VERSION)); + let mut cfu_basic = Updater::new( + &device, + &shared_state, + Default::default(), + DEVICE0_COMPONENT_ID, + MockCustomization::new(FwVersion::new(NEW_FW_VERSION)), + ); + + with_timeout(timeout, test.run(&device, &mut cfu_basic)).await.unwrap(); +} diff --git a/cfu-service/src/customization.rs b/cfu-service/src/customization.rs new file mode 100644 index 000000000..84d42e874 --- /dev/null +++ b/cfu-service/src/customization.rs @@ -0,0 +1,8 @@ +//! Common CFU customization trait +use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; + +/// Common CFU customization trait +pub trait Customization { + /// Determine if we are accepting the firmware update offer, returns a CFU offer response + fn validate(&mut self, current: FwVersion, offer: &FwUpdateOffer) -> FwUpdateOfferResponse; +} diff --git a/cfu-service/src/lib.rs b/cfu-service/src/lib.rs index 3c819c621..4feccfef2 100644 --- a/cfu-service/src/lib.rs +++ b/cfu-service/src/lib.rs @@ -6,13 +6,18 @@ use embedded_cfu_protocol::components::CfuComponentTraits; use embedded_cfu_protocol::protocol_definitions::*; use embedded_services::{GlobalRawMutex, comms, error, info, intrusive_list, trace}; +pub mod basic; pub mod buffer; pub mod component; +pub mod customization; pub mod host; mod responses; pub mod splitter; pub mod task; +#[cfg(test)] +pub mod mocks; + pub struct CfuClient { /// Cfu Client context context: ClientContext, diff --git a/cfu-service/src/mocks/customization.rs b/cfu-service/src/mocks/customization.rs new file mode 100644 index 000000000..ee184c102 --- /dev/null +++ b/cfu-service/src/mocks/customization.rs @@ -0,0 +1,83 @@ +//! Code related to mock for [`cfu_service::customization::Customization`] +extern crate std; + +use std::collections::VecDeque; + +use crate::customization::Customization; +use embedded_cfu_protocol::protocol_definitions::{ + FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken, OfferRejectReason, OfferStatus, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FnCall { + Validate(FwVersion, FwUpdateOffer), +} + +/// Simple mock +pub struct Mock { + /// Queue to record function calls + pub fn_calls: VecDeque, + /// Acceptable version + acceptable_version: FwVersion, +} + +impl Mock { + /// Create a new mock + pub fn new(acceptable_version: FwVersion) -> Self { + Self { + fn_calls: VecDeque::new(), + acceptable_version, + } + } + + fn record_fn_call(&mut self, fn_call: FnCall) { + self.fn_calls.push_back(fn_call); + } +} + +impl Customization for Mock { + fn validate(&mut self, current: FwVersion, fw_update_offer: &FwUpdateOffer) -> FwUpdateOfferResponse { + self.record_fn_call(FnCall::Validate(current, *fw_update_offer)); + if fw_update_offer.firmware_version == self.acceptable_version { + FwUpdateOfferResponse::new_accept(HostToken::Driver) + } else { + FwUpdateOfferResponse::new_with_failure(HostToken::Driver, OfferRejectReason::OldFw, OfferStatus::Reject) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_validate_accept() { + let acceptable_version = FwVersion::new(1); + let mut mock = Mock::new(acceptable_version); + let fw_update_offer = FwUpdateOffer::new(HostToken::Driver, 0, FwVersion::new(1), 0, 0); + let response = mock.validate(acceptable_version, &fw_update_offer); + assert_eq!(response, FwUpdateOfferResponse::new_accept(HostToken::Driver)); + assert_eq!(mock.fn_calls.len(), 1); + assert_eq!( + mock.fn_calls.pop_front(), + Some(FnCall::Validate(acceptable_version, fw_update_offer)) + ); + } + + #[test] + fn test_validate_reject() { + let acceptable_version = FwVersion::new(1); + let mut mock = Mock::new(acceptable_version); + let fw_update_offer = FwUpdateOffer::new(HostToken::Driver, 0, FwVersion::new(9), 0, 0); + let response = mock.validate(acceptable_version, &fw_update_offer); + assert_eq!( + response, + FwUpdateOfferResponse::new_with_failure(HostToken::Driver, OfferRejectReason::OldFw, OfferStatus::Reject) + ); + assert_eq!(mock.fn_calls.len(), 1); + assert_eq!( + mock.fn_calls.pop_front(), + Some(FnCall::Validate(acceptable_version, fw_update_offer)) + ); + } +} diff --git a/cfu-service/src/mocks/mod.rs b/cfu-service/src/mocks/mod.rs new file mode 100644 index 000000000..37510cd59 --- /dev/null +++ b/cfu-service/src/mocks/mod.rs @@ -0,0 +1,2 @@ +//! Mocks for [`cfu_service`] testing. +pub mod customization; diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 30ba8371a..6a4d3b315 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -172,6 +172,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", + "fw-update-interface", "heapless 0.8.0", ] @@ -733,6 +734,14 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +[[package]] +name = "fw-update-interface" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embedded-services", +] + [[package]] name = "generator" version = "0.8.8" diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 07fe2465f..ec73a321f 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -237,6 +237,7 @@ dependencies = [ "embassy-time", "embedded-cfu-protocol", "embedded-services", + "fw-update-interface", "heapless 0.8.0", "log", ] @@ -708,6 +709,14 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +[[package]] +name = "fw-update-interface" +version = "0.1.0" +dependencies = [ + "embedded-services", + "log", +] + [[package]] name = "generator" version = "0.8.8" diff --git a/fw-update-interface-mocks/Cargo.toml b/fw-update-interface-mocks/Cargo.toml new file mode 100644 index 000000000..ddf28e036 --- /dev/null +++ b/fw-update-interface-mocks/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fw-update-interface-mocks" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +embedded-services = { workspace = true } +fw-update-interface = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros", "time"] } + +[lints] +workspace = true diff --git a/fw-update-interface-mocks/src/basic.rs b/fw-update-interface-mocks/src/basic.rs new file mode 100644 index 000000000..50d1a02ae --- /dev/null +++ b/fw-update-interface-mocks/src/basic.rs @@ -0,0 +1,151 @@ +//! Module for a mock that implements [`fw_update_interface::basic::FwUpdate`] +use std::collections::VecDeque; +use std::vec::Vec; + +use embedded_services::named::Named; +use fw_update_interface::basic::{Error, FwUpdate}; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +pub enum FnCall { + GetActiveFwVersion, + StartFwUpdate, + AbortFwUpdate, + FinalizeFwUpdate, + WriteFwContents(usize, Vec), +} + +pub struct Mock { + /// Signal to record function calls + pub fn_calls: VecDeque, + /// The next error to return from the mock + next_error: Option, + /// Mock current FW version + current_fw_version: u32, + /// Human-readable name of the mock + name: &'static str, +} + +impl Mock { + pub fn new(name: &'static str, current_fw_version: u32) -> Self { + Self { + name, + fn_calls: VecDeque::new(), + next_error: None, + current_fw_version, + } + } + + fn record_fn_call(&mut self, fn_call: FnCall) { + self.fn_calls.push_back(fn_call); + } + + /// Set an error for the next function call + pub fn set_next_error(&mut self, error: Option) { + self.next_error = error; + } +} + +impl FwUpdate for Mock { + async fn get_active_fw_version(&mut self) -> Result { + self.record_fn_call(FnCall::GetActiveFwVersion); + if let Some(error) = self.next_error.take() { + return Err(error); + } + Ok(self.current_fw_version) + } + + async fn start_fw_update(&mut self) -> Result<(), Error> { + self.record_fn_call(FnCall::StartFwUpdate); + if let Some(error) = self.next_error.take() { + return Err(error); + } + Ok(()) + } + + async fn abort_fw_update(&mut self) -> Result<(), Error> { + self.record_fn_call(FnCall::AbortFwUpdate); + if let Some(error) = self.next_error.take() { + return Err(error); + } + Ok(()) + } + + async fn finalize_fw_update(&mut self) -> Result<(), Error> { + self.record_fn_call(FnCall::FinalizeFwUpdate); + if let Some(error) = self.next_error.take() { + return Err(error); + } + Ok(()) + } + + async fn write_fw_contents(&mut self, offset: usize, data: &[u8]) -> Result<(), Error> { + self.record_fn_call(FnCall::WriteFwContents(offset, Vec::from(data))); + + if let Some(error) = self.next_error.take() { + return Err(error); + } + Ok(()) + } +} + +impl Named for Mock { + fn name(&self) -> &'static str { + self.name + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::vec; + + #[tokio::test] + async fn test_get_active_fw_version() { + let mut mock = super::Mock::new("test", 1); + let version = mock.get_active_fw_version().await; + assert_eq!(version, Ok(1)); + assert_eq!(mock.fn_calls.pop_front(), Some(FnCall::GetActiveFwVersion)); + } + + #[tokio::test] + async fn test_start_fw_update() { + let mut mock = super::Mock::new("test", 1); + let result = mock.start_fw_update().await; + assert_eq!(result, Ok(())); + assert_eq!(mock.fn_calls.pop_front(), Some(FnCall::StartFwUpdate)); + } + + #[tokio::test] + async fn test_abort_fw_update() { + let mut mock = super::Mock::new("test", 1); + let result = mock.abort_fw_update().await; + assert_eq!(result, Ok(())); + assert_eq!(mock.fn_calls.pop_front(), Some(FnCall::AbortFwUpdate)); + } + + #[tokio::test] + async fn test_finalize_fw_update() { + let mut mock = super::Mock::new("test", 1); + let result = mock.finalize_fw_update().await; + assert_eq!(result, Ok(())); + assert_eq!(mock.fn_calls.pop_front(), Some(FnCall::FinalizeFwUpdate)); + } + + #[tokio::test] + async fn test_write_fw_contents() { + let mut mock = super::Mock::new("test", 1); + let data = vec![1, 2, 3, 4]; + let result = mock.write_fw_contents(0, &data).await; + assert_eq!(result, Ok(())); + assert_eq!(mock.fn_calls.pop_front(), Some(FnCall::WriteFwContents(0, data))); + } + + #[tokio::test] + async fn test_set_next_error() { + let mut mock = super::Mock::new("test", 1); + mock.set_next_error(Some(Error::Failed)); + let result = mock.get_active_fw_version().await; + assert_eq!(result, Err(Error::Failed)); + } +} diff --git a/fw-update-interface-mocks/src/lib.rs b/fw-update-interface-mocks/src/lib.rs new file mode 100644 index 000000000..10fcf6524 --- /dev/null +++ b/fw-update-interface-mocks/src/lib.rs @@ -0,0 +1,2 @@ +//! Mocks for [`fw_update_interface`] testing. +pub mod basic; diff --git a/fw-update-interface/Cargo.toml b/fw-update-interface/Cargo.toml new file mode 100644 index 000000000..19625163c --- /dev/null +++ b/fw-update-interface/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "fw-update-interface" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +log = { workspace = true, optional = true } +defmt = { workspace = true, optional = true } +embedded-services = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros", "time"] } + +[features] +default = [] +defmt = ["dep:defmt", "embedded-services/defmt"] +log = ["dep:log", "embedded-services/log"] + +[lints] +workspace = true + +[package.metadata.cargo-machete] +# Not directly needed currently, but present to match the log/defmt patterns of other crates +ignored = ["log"] diff --git a/fw-update-interface/src/basic.rs b/fw-update-interface/src/basic.rs new file mode 100644 index 000000000..cff70daa5 --- /dev/null +++ b/fw-update-interface/src/basic.rs @@ -0,0 +1,43 @@ +//! This module contains types for a very basic firmware update interface. + +use embedded_services::named::Named; + +/// Basic FW update error type +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// The operation is not valid during a FW update + UpdateInProgress, + /// This operation is only valid when a FW update is in progress + NeedsActiveUpdate, + /// Invalid address + InvalidAddress(usize), + /// The firmware content is invalid + InvalidContent, + /// The requested operation timed out + Timeout, + /// The device is busy + Busy, + /// Bus error + Bus, + /// Unspecified failure + Failed, +} + +/// Basic FW update trait +/// +/// This is for devices that don't need to expose multiple banks and can support +/// a FW update done through a few operations. Write only. +pub trait FwUpdate: Named { + /// Get current FW version + fn get_active_fw_version(&mut self) -> impl Future>; + /// Start a firmware update + fn start_fw_update(&mut self) -> impl Future>; + /// Abort a firmware update + fn abort_fw_update(&mut self) -> impl Future>; + /// Finalize a firmware update + fn finalize_fw_update(&mut self) -> impl Future>; + /// Write firmware update contents + fn write_fw_contents(&mut self, offset: usize, data: &[u8]) -> impl Future>; +} diff --git a/fw-update-interface/src/lib.rs b/fw-update-interface/src/lib.rs new file mode 100644 index 000000000..94fa3e4ed --- /dev/null +++ b/fw-update-interface/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +pub mod basic; From ab2174db81bb2e5b8fd8584e70c4e0c53993c27e Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:16:54 -0700 Subject: [PATCH 71/79] type-c-service/wrapper: Move remaining message code into bridge struct (#815) Move this code to make it easier to remove when the last of the direct async refactoring lands. Remove unused UCSI message value. --- examples/rt685s-evk/src/bin/type_c.rs | 18 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 18 +- examples/std/src/bin/type_c/basic.rs | 23 +- examples/std/src/bin/type_c/service.rs | 172 +++++------ examples/std/src/bin/type_c/ucsi.rs | 22 +- examples/std/src/bin/type_c/unconstrained.rs | 26 +- type-c-interface/src/port/mod.rs | 6 +- type-c-interface/src/service/context.rs | 41 +-- type-c-service/src/bridge/event_receiver.rs | 32 ++ type-c-service/src/bridge/mod.rs | 221 ++++++++++++++ type-c-service/src/lib.rs | 1 + type-c-service/src/wrapper/event_receiver.rs | 21 +- type-c-service/src/wrapper/message.rs | 19 +- type-c-service/src/wrapper/mod.rs | 32 +- type-c-service/src/wrapper/pd.rs | 291 ------------------- 15 files changed, 441 insertions(+), 502 deletions(-) create mode 100644 type-c-service/src/bridge/event_receiver.rs create mode 100644 type-c-service/src/bridge/mod.rs diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index f2bdeb380..910b071d4 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -27,6 +27,8 @@ use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; use type_c_interface::port::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::driver::tps6699x::{self as tps6699x_drv, InterruptReceiver}; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; @@ -93,6 +95,18 @@ type PowerPolicyServiceType = Mutex< type ServiceType = Service<'static>; +#[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] async fn pd_controller_task( mut event_receiver: ArrayPortEventReceivers< @@ -251,6 +265,8 @@ async fn main(spawner: Spawner) { referenced, Validator, )); + let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); + let bridge = Bridge::new(controller_mutex, &referenced.pd_controller); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< @@ -306,12 +322,12 @@ 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( pd_controller_task( ArrayPortEventReceivers::new( InterruptReceiver::new(interrupt_receiver), power_event_receivers, - &referenced.pd_controller, &storage.cfu_device, ), wrapper, diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 65a961bad..28affdd8c 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -30,6 +30,8 @@ use tps6699x::asynchronous::embassy as tps6699x; use type_c_interface::port::ControllerId; use type_c_interface::port::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::driver::tps6699x::{self as tps6699x_drv, InterruptReceiver}; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::ControllerWrapper; @@ -97,6 +99,18 @@ 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] async fn pd_controller_task( mut event_receiver: ArrayPortEventReceivers< @@ -339,6 +353,8 @@ async fn main(spawner: Spawner) { referenced, Validator, )); + let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); + let bridge = Bridge::new(controller_mutex, &referenced.pd_controller); // Create power policy service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -394,12 +410,12 @@ 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( pd_controller_task( ArrayPortEventReceivers::new( InterruptReceiver::new(interrupt_receiver), power_event_receivers, - &referenced.pd_controller, &storage.cfu_device, ), wrapper, diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index c278f8ac6..69f811316 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -2,7 +2,7 @@ use embassy_executor::{Executor, Spawner}; use embassy_sync::channel::Channel; use embassy_time::Timer; use embedded_services::GlobalRawMutex; -use embedded_usb_pd::ucsi::lpm; + use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; @@ -16,7 +16,6 @@ const PORT1_ID: GlobalPortId = GlobalPortId(1); const CHANNEL_CAPACITY: usize = 4; mod test_controller { - use embedded_usb_pd::ucsi; use type_c_interface::port::{ControllerStatus, PortRegistration}; use super::*; @@ -63,25 +62,6 @@ mod test_controller { } } - async fn process_ucsi_command(&self, command: &lpm::GlobalCommand) -> ucsi::GlobalResponse { - match command.operation() { - lpm::CommandData::ConnectorReset => { - info!("Reset for port {:#?}", command.port()); - ucsi::Response { - cci: ucsi::cci::Cci::new_cmd_complete(), - data: None, - } - } - rest => { - info!("UCSI command {:#?} for port {:#?}", rest, command.port()); - ucsi::Response { - cci: ucsi::cci::Cci::new_cmd_complete(), - data: None, - } - } - } - } - async fn process_port_command(&self, command: port::PortCommand) -> Result { info!("Port command for port {}", command.port.0); Ok(port::PortResponseData::Complete) @@ -93,7 +73,6 @@ mod test_controller { port::Command::Controller(command) => { port::Response::Controller(self.process_controller_command(command).await) } - port::Command::Lpm(command) => port::Response::Ucsi(self.process_ucsi_command(&command).await), port::Command::Port(command) => port::Response::Port(self.process_port_command(command).await), }; diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 278a6b5d9..81c6f4875 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -19,8 +19,9 @@ use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; use type_c_interface::port::{ControllerId, PortRegistration}; -use type_c_interface::service::context::Context; 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::service::config::Config; use type_c_service::service::{EventReceiver, Service}; use type_c_service::util::power_capability_from_current; @@ -91,6 +92,18 @@ async fn controller_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; @@ -99,7 +112,76 @@ async fn task(spawner: Spawner) { static CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTEXT.init(type_c_interface::service::context::Context::new()); - let (event_receiver, wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(mock_controller::ControllerState::new()); + + static PORT0_CHANNEL: Channel = Channel::new(); + + static STORAGE: StaticCell> = StaticCell::new(); + let storage = STORAGE.init(Storage::new( + controller_context, + CONTROLLER0_ID, + 0, // CFU component ID (unused) + [PortRegistration { + id: PORT0_ID, + sender: PORT0_CHANNEL.dyn_sender(), + receiver: PORT0_CHANNEL.dyn_receiver(), + }], + )); + + static POLICY_CHANNEL: StaticCell> = + StaticCell::new(); + let policy_channel = POLICY_CHANNEL.init(Channel::new()); + + let policy_sender = policy_channel.dyn_sender(); + let policy_receiver = policy_channel.dyn_receiver(); + + let (intermediate, power_event_receivers) = storage + .try_create_intermediate([("Pd0", policy_sender)]) + .expect("Failed to create intermediate storage"); + + static INTERMEDIATE: StaticCell< + type_c_service::wrapper::backing::IntermediateStorage< + 1, + GlobalRawMutex, + DynamicSender<'static, psu::event::EventData>, + >, + > = StaticCell::new(); + let intermediate = INTERMEDIATE.init(intermediate); + + static REFERENCED: StaticCell< + type_c_service::wrapper::backing::ReferencedStorage< + 1, + GlobalRawMutex, + DynamicSender<'_, psu::event::EventData>, + >, + > = StaticCell::new(); + let referenced = REFERENCED.init( + intermediate + .try_create_referenced() + .expect("Failed to create referenced storage"), + ); + + let event_receiver = ArrayPortEventReceivers::new( + state.create_interrupt_receiver(), + power_event_receivers, + &storage.cfu_device, + ); + + static CONTROLLER: StaticCell> = StaticCell::new(); + let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); + + static WRAPPER: StaticCell = StaticCell::new(); + + let wrapper = WRAPPER.init(mock_controller::Wrapper::new( + controller, + Default::default(), + referenced, + crate::mock_controller::Validator, + )); + + let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); + let bridge = Bridge::new(controller, &referenced.pd_controller); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -148,6 +230,8 @@ async fn task(spawner: Spawner) { ) .expect("Failed to create type-c service task"), ); + + spawner.spawn(bridge_task(bridge_receiver, bridge).expect("Failed to create bridge task")); spawner.spawn(controller_task(event_receiver, wrapper, controller).expect("Failed to create controller task")); Timer::after_millis(1000).await; @@ -193,90 +277,6 @@ async fn type_c_service_task( type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; } -fn create_wrapper( - context: &'static Context, -) -> ( - ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, - &'static Wrapper<'static>, - DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, - &'static Mutex>, - &'static mock_controller::ControllerState, -) { - static STATE: StaticCell = StaticCell::new(); - let state = STATE.init(mock_controller::ControllerState::new()); - - static PORT0_CHANNEL: Channel = Channel::new(); - - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - context, - CONTROLLER0_ID, - 0, // CFU component ID (unused) - [PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }], - )); - - static POLICY_CHANNEL: StaticCell> = - StaticCell::new(); - let policy_channel = POLICY_CHANNEL.init(Channel::new()); - - let policy_sender = policy_channel.dyn_sender(); - let policy_receiver = policy_channel.dyn_receiver(); - - let (intermediate, power_event_receivers) = storage - .try_create_intermediate([("Pd0", policy_sender)]) - .expect("Failed to create intermediate storage"); - - static INTERMEDIATE: StaticCell< - type_c_service::wrapper::backing::IntermediateStorage< - 1, - GlobalRawMutex, - DynamicSender<'static, psu::event::EventData>, - >, - > = StaticCell::new(); - let intermediate = INTERMEDIATE.init(intermediate); - - static REFERENCED: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, psu::event::EventData>, - >, - > = StaticCell::new(); - let referenced = REFERENCED.init( - intermediate - .try_create_referenced() - .expect("Failed to create referenced storage"), - ); - - let event_receiver = ArrayPortEventReceivers::new( - state.create_interrupt_receiver(), - power_event_receivers, - &referenced.pd_controller, - &storage.cfu_device, - ); - - static CONTROLLER: StaticCell> = StaticCell::new(); - let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - - static WRAPPER: StaticCell = StaticCell::new(); - ( - event_receiver, - WRAPPER.init(mock_controller::Wrapper::new( - controller, - Default::default(), - referenced, - crate::mock_controller::Validator, - )), - policy_receiver, - controller, - state, - ) -} - fn main() { env_logger::builder().filter_level(log::LevelFilter::Trace).init(); diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 42d7de23b..5d1c3425b 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -26,6 +26,8 @@ use std_examples::type_c::mock_controller; use type_c_interface::port::{ControllerId, PortRegistration}; use type_c_interface::service::context::Context; 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::service::config::Config; use type_c_service::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::Storage; @@ -196,6 +198,18 @@ 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 wrapper_task( mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, @@ -297,7 +311,6 @@ async fn task(spawner: Spawner) { let event_receiver0 = ArrayPortEventReceivers::new( state0.create_interrupt_receiver(), power_event_receivers0, - &referenced0.pd_controller, &storage0.cfu_device, ); static CONTROLLER0: StaticCell> = StaticCell::new(); @@ -309,6 +322,8 @@ async fn task(spawner: Spawner) { referenced0, mock_controller::Validator, )); + let bridge_receiver0 = BridgeEventReceiver::new(&referenced0.pd_controller); + let bridge0 = Bridge::new(controller0, &referenced0.pd_controller); static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); @@ -357,7 +372,6 @@ async fn task(spawner: Spawner) { let event_receiver1 = ArrayPortEventReceivers::new( state1.create_interrupt_receiver(), power_event_receivers1, - &referenced1.pd_controller, &storage1.cfu_device, ); static CONTROLLER1: StaticCell> = StaticCell::new(); @@ -369,6 +383,8 @@ async fn task(spawner: Spawner) { referenced1, mock_controller::Validator, )); + let bridge_receiver1 = BridgeEventReceiver::new(&referenced1.pd_controller); + let bridge1 = Bridge::new(controller1, &referenced1.pd_controller); // Create power policy service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -448,6 +464,8 @@ async fn task(spawner: Spawner) { ) .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(wrapper_task(event_receiver0, wrapper0).expect("Failed to create wrapper0 task")); spawner.spawn(wrapper_task(event_receiver1, wrapper1).expect("Failed to create wrapper1 task")); spawner.spawn(opm_task(controller_context, [state0, state1]).expect("Failed to create opm task")); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 8695caac7..975bdbf69 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -22,6 +22,8 @@ use std_examples::type_c::mock_controller; use type_c_interface::port::ControllerId; use type_c_interface::port::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::service::{EventReceiver, Service}; use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; @@ -68,6 +70,18 @@ type PowerPolicyServiceType = Mutex< type ServiceType = Service<'static>; +#[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 controller_task( mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, @@ -139,7 +153,6 @@ async fn task(spawner: Spawner) { let event_receiver0 = ArrayPortEventReceivers::new( state0.create_interrupt_receiver(), power_event_receivers0, - &referenced0.pd_controller, &storage0.cfu_device, ); static CONTROLLER0: StaticCell> = StaticCell::new(); @@ -151,6 +164,8 @@ async fn task(spawner: Spawner) { referenced0, crate::mock_controller::Validator, )); + let bridge_receiver0 = BridgeEventReceiver::new(&referenced0.pd_controller); + let bridge0 = Bridge::new(controller0, &referenced0.pd_controller); static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); @@ -190,7 +205,6 @@ async fn task(spawner: Spawner) { let event_receiver1 = ArrayPortEventReceivers::new( state1.create_interrupt_receiver(), power_event_receivers1, - &referenced1.pd_controller, &storage1.cfu_device, ); static CONTROLLER1: StaticCell> = StaticCell::new(); @@ -202,6 +216,8 @@ async fn task(spawner: Spawner) { referenced1, crate::mock_controller::Validator, )); + let bridge_receiver1 = BridgeEventReceiver::new(&referenced1.pd_controller); + let bridge1 = Bridge::new(controller1, &referenced1.pd_controller); static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); @@ -241,7 +257,6 @@ async fn task(spawner: Spawner) { let event_receiver2 = ArrayPortEventReceivers::new( state2.create_interrupt_receiver(), power_event_receivers2, - &referenced2.pd_controller, &storage2.cfu_device, ); static CONTROLLER2: StaticCell> = StaticCell::new(); @@ -253,6 +268,8 @@ async fn task(spawner: Spawner) { referenced2, crate::mock_controller::Validator, )); + let bridge_receiver2 = BridgeEventReceiver::new(&referenced2.pd_controller); + let bridge2 = Bridge::new(controller2, &referenced2.pd_controller); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< @@ -313,6 +330,9 @@ async fn task(spawner: Spawner) { .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(controller_task(event_receiver0, wrapper0).expect("Failed to create controller0 task")); spawner.spawn(controller_task(event_receiver1, wrapper1).expect("Failed to create controller1 task")); spawner.spawn(controller_task(event_receiver2, wrapper2).expect("Failed to create controller2 task")); diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index f4099228d..bb2c733e4 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -3,7 +3,7 @@ use core::future::Future; use core::num::NonZeroU8; use embassy_sync::channel::{DynamicReceiver, DynamicSender}; -use embedded_usb_pd::ucsi::{self, lpm}; +use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{ DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, ado::Ado, @@ -404,8 +404,6 @@ pub enum Command { Controller(InternalCommandData), /// Port command Port(PortCommand), - /// UCSI command passthrough - Lpm(lpm::GlobalCommand), } /// Controller-specific response data @@ -427,8 +425,6 @@ pub type InternalResponse<'a> = Result, PdError>; pub enum Response<'a> { /// Controller response Controller(InternalResponse<'a>), - /// UCSI response passthrough - Ucsi(ucsi::GlobalResponse), /// Port response Port(PortResponse), } diff --git a/type-c-interface/src/service/context.rs b/type-c-interface/src/service/context.rs index f5e8b4bfd..6c877f5b0 100644 --- a/type-c-interface/src/service/context.rs +++ b/type-c-interface/src/service/context.rs @@ -1,5 +1,5 @@ use embassy_time::{Duration, with_timeout}; -use embedded_usb_pd::ucsi::{self, lpm}; +use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError}; use crate::port::ControllerId; @@ -122,45 +122,6 @@ impl Context { .ok_or(PdError::InvalidPort) } - /// Send a command to the given port - pub async fn send_port_command_ucsi_no_timeout( - &self, - port_id: GlobalPortId, - command: lpm::CommandData, - ) -> Result { - let node = self.find_node_by_port(port_id)?; - - match node - .data::() - .ok_or(PdError::InvalidController)? - .execute_command(Command::Lpm(lpm::Command::new(port_id, command))) - .await - { - Response::Ucsi(response) => Ok(response), - r => { - error!("Invalid response: expected LPM, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - - /// Send a command to the given port with a timeout - pub async fn send_port_command_ucsi( - &self, - port_id: GlobalPortId, - command: lpm::CommandData, - ) -> Result { - match with_timeout( - DEFAULT_TIMEOUT, - self.send_port_command_ucsi_no_timeout(port_id, command), - ) - .await - { - Ok(response) => response, - Err(_) => Err(PdError::Timeout), - } - } - /// Send a command to the given port with no timeout pub async fn send_port_command_no_timeout( &self, diff --git a/type-c-service/src/bridge/event_receiver.rs b/type-c-service/src/bridge/event_receiver.rs new file mode 100644 index 000000000..aebad0541 --- /dev/null +++ b/type-c-service/src/bridge/event_receiver.rs @@ -0,0 +1,32 @@ +use embedded_services::{GlobalRawMutex, ipc::deferred}; +use type_c_interface::port; + +pub type ControllerCommand<'a> = deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>; + +/// Controller command output data +pub struct OutputControllerCommand<'a> { + /// Controller request + pub request: ControllerCommand<'a>, + /// Response + pub response: port::Response<'static>, +} + +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 new file mode 100644 index 000000000..dbf9f0257 --- /dev/null +++ b/type-c-service/src/bridge/mod.rs @@ -0,0 +1,221 @@ +//! Temporary bridge between a controller and the type-C service + +use embedded_services::{debug, sync::Lockable}; +use embedded_usb_pd::{Error, PdError, ucsi::lpm}; +use type_c_interface::port::{self, Controller as _, InternalResponseData, Response}; + +use crate::bridge::event_receiver::{ControllerCommand, OutputControllerCommand}; +pub mod event_receiver; + +pub struct Bridge<'device, Controller: Lockable> { + controller: &'device Controller, + registration: &'static port::Device<'static>, +} + +impl<'device, Controller: Lockable> Bridge<'device, Controller> { + pub fn new(controller: &'device Controller, 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<'static> { + 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 => { + match controller.get_rt_fw_update_status(local_port).await { + Ok(status) => Ok(port::PortResponseData::RtFwUpdateStatus(status)), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::RetimerFwUpdateSetState => { + match controller.set_rt_fw_update_state(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::RetimerFwUpdateClearState => { + match controller.clear_rt_fw_update_state(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::SetRetimerCompliance => match controller.set_rt_compliance(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::ReconfigureRetimer => match controller.reconfigure_retimer(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + // 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) => { + match controller.set_unconstrained_power(local_port, unconstrained).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::ClearDeadBatteryFlag => match controller.clear_dead_battery_flag(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::GetOtherVdm => match controller.get_other_vdm(local_port).await { + Ok(vdm) => { + debug!("Port{}: Other VDM: {:?}", local_port.0, vdm); + Ok(port::PortResponseData::OtherVdm(vdm)) + } + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::GetAttnVdm => match controller.get_attn_vdm(local_port).await { + Ok(vdm) => { + debug!("Port{}: Attention VDM: {:?}", local_port.0, vdm); + Ok(port::PortResponseData::AttnVdm(vdm)) + } + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::SendVdm(tx_vdm) => match controller.send_vdm(local_port, tx_vdm).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::SetUsbControl(config) => { + match controller.set_usb_control(local_port, config).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::GetDpStatus => match controller.get_dp_status(local_port).await { + Ok(status) => { + debug!("Port{}: DP Status: {:?}", local_port.0, status); + Ok(port::PortResponseData::DpStatus(status)) + } + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::SetDpConfig(config) => match controller.set_dp_config(local_port, config).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::ExecuteDrst => match controller.execute_drst(local_port).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::SetTbtConfig(config) => match controller.set_tbt_config(local_port, config).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + }, + port::PortCommandData::SetPdStateMachineConfig(config) => { + match controller.set_pd_state_machine_config(local_port, config).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::SetTypeCStateMachineConfig(state) => { + match controller.set_type_c_state_machine_config(local_port, state).await { + Ok(()) => Ok(port::PortResponseData::Complete), + Err(e) => match e { + Error::Bus(_) => Err(PdError::Failed), + Error::Pd(e) => Err(e), + }, + } + } + port::PortCommandData::ExecuteUcsiCommand(command_data) => Ok(port::PortResponseData::UcsiResponse( + controller + .execute_ucsi_command(lpm::Command::new(local_port, command_data)) + .await + .map_err(|e| match e { + Error::Bus(_) => PdError::Failed, + Error::Pd(e) => e, + }), + )), + }) + } + + pub async fn process_controller_command(&mut self, command: &port::InternalCommandData) -> Response<'static> { + let mut controller = self.controller.lock().await; + match command { + port::InternalCommandData::Status => { + let status = controller.get_controller_status().await; + port::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed)) + } + // This isn't sent by the type-C service, disable it for the transition + 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) + .map_err(|_| PdError::Failed), + ) + } + } + } + + /// 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/lib.rs b/type-c-service/src/lib.rs index 12d975c72..3ccaa27d6 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +pub mod bridge; pub mod driver; pub mod service; pub mod task; diff --git a/type-c-service/src/wrapper/event_receiver.rs b/type-c-service/src/wrapper/event_receiver.rs index a3f967b4a..14e265190 100644 --- a/type-c-service/src/wrapper/event_receiver.rs +++ b/type-c-service/src/wrapper/event_receiver.rs @@ -2,7 +2,7 @@ use core::array; use core::future::pending; use core::pin::pin; -use embassy_futures::select::{Either, Either5, select, select_slice, select5}; +use embassy_futures::select::{Either, Either4, select, select_slice, select4}; use embassy_time::{Instant, Ticker, Timer}; use embedded_services::{debug, trace}; use embedded_usb_pd::LocalPortId; @@ -13,7 +13,6 @@ use crate::wrapper::cfu::FwUpdateState; use crate::wrapper::message::{Event, EventCfu, LocalPortEvent, PowerPolicyCommand}; use crate::wrapper::proxy::PowerProxyReceiver; use type_c_interface::port::event::{PortEvent, PortEventBitfield, PortStatusEventBitfield}; -use type_c_interface::port::{self}; /// Trait used for receiving interrupt from the controller. pub trait InterruptReceiver { @@ -222,8 +221,6 @@ pub struct ArrayPortEventReceivers<'device, const N: usize, PortInterrupts: Inte pub port_events: PortEventReceiver, /// Power proxy event receiver pub power_proxies: ArrayPowerProxyEventReceiver<'device, N>, - /// PD controller - pub pd_controller: &'static port::Device<'static>, /// CFU event receiver pub cfu_event_receiver: CfuEventReceiver, /// Sink ready timeout event receiver @@ -237,13 +234,11 @@ impl<'device, const N: usize, PortInterrupts: InterruptReceiver> pub fn new( port_interrupts: PortInterrupts, power_proxies: [PowerProxyReceiver<'device>; N], - pd_controller: &'static port::Device<'static>, cfu_device: &'static cfu_service::component::CfuDevice, ) -> Self { Self { port_events: PortEventReceiver::new(port_interrupts), power_proxies: ArrayPowerProxyEventReceiver::new(power_proxies), - pd_controller, cfu_event_receiver: CfuEventReceiver::new(cfu_device), sink_ready_timeout: SinkReadyTimeoutEvent::new(), } @@ -252,21 +247,19 @@ impl<'device, const N: usize, PortInterrupts: InterruptReceiver> /// Wait for the next port event from any port. /// /// Returns the local port ID and the event bitfield. - pub async fn wait_event(&mut self) -> Event<'device> { - match select5( + pub async fn wait_event(&mut self) -> Event { + match select4( self.port_events.wait_next(), self.power_proxies.wait_next(), - self.pd_controller.receive(), self.cfu_event_receiver.wait_next(), self.sink_ready_timeout.wait_next(), ) .await { - Either5::First(event) => Event::PortEvent(event), - Either5::Second(command) => Event::PowerPolicyCommand(command), - Either5::Third(request) => Event::ControllerCommand(request), - Either5::Fourth(cfu_event) => Event::CfuEvent(cfu_event), - Either5::Fifth(port) => { + Either4::First(event) => Event::PortEvent(event), + Either4::Second(command) => Event::PowerPolicyCommand(command), + Either4::Third(cfu_event) => Event::CfuEvent(cfu_event), + Either4::Fourth(port) => { let mut status_event = PortStatusEventBitfield::none(); status_event.set_sink_ready(true); Event::PortEvent(LocalPortEvent { diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index b3bc32c51..2c6abba00 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -1,10 +1,9 @@ //! [`crate::wrapper::ControllerWrapper`] message types -use embedded_services::{GlobalRawMutex, ipc::deferred}; use embedded_usb_pd::{LocalPortId, ado::Ado}; use type_c_interface::{ port::event::PortStatusEventBitfield, - port::{self, DpStatus, PortStatus}, + port::{DpStatus, PortStatus}, }; /// Port event @@ -38,13 +37,11 @@ pub enum EventCfu { } /// Wrapper events -pub enum Event<'a> { +pub enum Event { /// Port status changed PortEvent(LocalPortEvent), /// Power policy command received PowerPolicyCommand(PowerPolicyCommand), - /// Command from TCPM - ControllerCommand(deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>), /// Cfu event CfuEvent(EventCfu), } @@ -79,14 +76,6 @@ pub struct OutputPowerPolicyCommand { pub response: power_policy_interface::psu::InternalResponseData, } -/// Controller command output data -pub struct OutputControllerCommand<'a> { - /// Controller request - pub request: deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>, - /// Response - pub response: port::Response<'static>, -} - pub mod vdm { //! Events and output for vendor-defined messaging. use type_c_interface::port::event::VdmData; @@ -115,7 +104,7 @@ pub struct OutputDpStatusChanged { } /// [`crate::wrapper::ControllerWrapper`] output -pub enum Output<'a> { +pub enum Output { /// No-op when nothing specific is needed Nop, /// Port status changed @@ -126,8 +115,6 @@ pub enum Output<'a> { Vdm(vdm::Output), /// Power policy command received PowerPolicyCommand(OutputPowerPolicyCommand), - /// TPCM command response - ControllerCommand(OutputControllerCommand<'a>), /// CFU recovery tick CfuRecovery, /// CFU response diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 574ab5424..97daf286e 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -192,13 +192,13 @@ where } /// Process port status changed events - async fn process_port_status_changed<'b, const N: usize>( + async fn process_port_status_changed( &self, sink_ready_timeout: &mut SinkReadyTimeoutEvent, controller: &mut D::Inner, local_port_id: LocalPortId, status_event: PortStatusEventBitfield, - ) -> Result, Error<::BusError>> { + ) -> Result::BusError>> { let global_port_id = self .registration .pd_controller @@ -300,12 +300,12 @@ where } /// Process a port notification - async fn process_port_event<'b, const N: usize>( + async fn process_port_event( &self, sink_ready_timeout: &mut SinkReadyTimeoutEvent, controller: &mut D::Inner, event: LocalPortEvent, - ) -> Result, Error<::BusError>> { + ) -> Result::BusError>> { match event.event { InterfacePortEvent::StatusChanged(status_event) => { self.process_port_status_changed(sink_ready_timeout, controller, event.port, status_event) @@ -339,12 +339,12 @@ where /// Top-level processing function /// Only call this fn from one place in a loop. Otherwise a deadlock could occur. - pub async fn process_event<'b, const N: usize>( + pub async fn process_event( &self, sink_ready_timeout: &mut SinkReadyTimeoutEvent, cfu_event_receiver: &mut CfuEventReceiver, - event: Event<'b>, - ) -> Result, Error<::BusError>> { + event: Event, + ) -> Result::BusError>> { let mut controller = self.controller.lock().await; match event { Event::PortEvent(port_event) => { @@ -357,12 +357,6 @@ where .await; Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) } - Event::ControllerCommand(request) => { - let response = self - .process_pd_command(cfu_event_receiver, &mut controller, &request.command) - .await; - Ok(Output::ControllerCommand(OutputControllerCommand { request, response })) - } Event::CfuEvent(event) => match event { EventCfu::Request(request) => { let response = self @@ -380,10 +374,10 @@ where } /// Event loop finalize - pub async fn finalize<'b, const N: usize>( + pub async fn finalize( &self, event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, - output: Output<'b>, + output: Output, ) -> Result<(), Error<::BusError>> { match output { Output::Nop => Ok(()), @@ -401,10 +395,6 @@ where .map_err(|_| Error::Pd(PdError::Failed))?; Ok(()) } - Output::ControllerCommand(OutputControllerCommand { request, response }) => { - request.respond(response); - Ok(()) - } Output::CfuRecovery => { // Nothing to do here Ok(()) @@ -421,12 +411,12 @@ where } /// Combined processing and finialization function - pub async fn process_and_finalize_event<'b, const N: usize>( + pub async fn process_and_finalize_event( &self, sink_ready_timeout: &mut SinkReadyTimeoutEvent, cfu_event_receiver: &mut CfuEventReceiver, power_event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, - event: Event<'b>, + event: Event, ) -> Result<(), Error<::BusError>> { let output = self .process_event(sink_ready_timeout, cfu_event_receiver, event) diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index 671aab1a8..70dfeaf88 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -2,10 +2,6 @@ use crate::wrapper::event_receiver::SinkReadyTimeoutEvent; use embassy_time::Duration; use embedded_services::debug; use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; -use embedded_usb_pd::ucsi::{self, lpm}; -use power_policy_interface::psu::{self, PsuState}; -use type_c_interface::port; -use type_c_interface::port::{InternalResponseData, Response}; use super::*; @@ -63,291 +59,4 @@ where } Ok(()) } - - /// Process a request to set the maximum sink voltage for a port - async fn process_set_max_sink_voltage( - &self, - controller: &mut D::Inner, - port_state: &mut PortState, - state: &psu::State, - local_port: LocalPortId, - voltage_mv: Option, - ) -> Result { - let psu_state = state.psu_state; - debug!("Port{}: Current state: {:#?}", local_port.0, psu_state); - if matches!(psu_state, PsuState::ConnectedConsumer(_)) { - debug!("Port{}: Set max sink voltage, connected consumer found", local_port.0); - if voltage_mv.is_some() && voltage_mv < state.consumer_capability.map(|c| c.capability.voltage_mv) { - // New max voltage is lower than current consumer capability which will trigger a renegociation - // So disconnect first - debug!( - "Port{}: Disconnecting consumer before setting max sink voltage", - local_port.0 - ); - port_state - .power_policy_sender - .send(power_policy_interface::psu::event::EventData::Disconnected) - .await; - } - } - - match controller.set_max_sink_voltage(local_port, voltage_mv).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - - /// Handle a port command - async fn process_port_command( - &self, - cfu_event_receiver: &mut CfuEventReceiver, - controller: &mut D::Inner, - command: &port::PortCommand, - ) -> Response<'static> { - if cfu_event_receiver.fw_update_state.in_progress() { - debug!("FW update in progress, ignoring port command"); - return port::Response::Port(Err(PdError::Busy)); - } - - let local_port = if let Ok(port) = self.registration.pd_controller.lookup_local_port(command.port) { - port - } else { - debug!("Invalid port: {:?}", command.port); - return port::Response::Port(Err(PdError::InvalidPort)); - }; - - let Some(port) = self.ports.get(local_port.0 as usize) else { - debug!("Invalid port: {:?}", command.port); - return port::Response::Port(Err(PdError::InvalidPort)); - }; - - let mut port_state = port.state.lock().await; - port::Response::Port(match command.data { - port::PortCommandData::RetimerFwUpdateGetState => { - match controller.get_rt_fw_update_status(local_port).await { - Ok(status) => Ok(port::PortResponseData::RtFwUpdateStatus(status)), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::RetimerFwUpdateSetState => { - match controller.set_rt_fw_update_state(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::RetimerFwUpdateClearState => { - match controller.clear_rt_fw_update_state(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::SetRetimerCompliance => match controller.set_rt_compliance(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::ReconfigureRetimer => match controller.reconfigure_retimer(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetMaxSinkVoltage(voltage_mv) => { - match self.registration.pd_controller.lookup_local_port(command.port) { - Ok(local_port) => { - let psu_state = port.proxy.lock().await.psu_state; - self.process_set_max_sink_voltage( - controller, - &mut port_state, - &psu_state, - local_port, - voltage_mv, - ) - .await - } - Err(e) => Err(e), - } - } - port::PortCommandData::SetUnconstrainedPower(unconstrained) => { - match controller.set_unconstrained_power(local_port, unconstrained).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::ClearDeadBatteryFlag => match controller.clear_dead_battery_flag(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::GetOtherVdm => match controller.get_other_vdm(local_port).await { - Ok(vdm) => { - debug!("Port{}: Other VDM: {:?}", local_port.0, vdm); - Ok(port::PortResponseData::OtherVdm(vdm)) - } - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::GetAttnVdm => match controller.get_attn_vdm(local_port).await { - Ok(vdm) => { - debug!("Port{}: Attention VDM: {:?}", local_port.0, vdm); - Ok(port::PortResponseData::AttnVdm(vdm)) - } - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SendVdm(tx_vdm) => match controller.send_vdm(local_port, tx_vdm).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetUsbControl(config) => { - match controller.set_usb_control(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::GetDpStatus => match controller.get_dp_status(local_port).await { - Ok(status) => { - debug!("Port{}: DP Status: {:?}", local_port.0, status); - Ok(port::PortResponseData::DpStatus(status)) - } - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetDpConfig(config) => match controller.set_dp_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::ExecuteDrst => match controller.execute_drst(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetTbtConfig(config) => match controller.set_tbt_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetPdStateMachineConfig(config) => { - match controller.set_pd_state_machine_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::SetTypeCStateMachineConfig(state) => { - match controller.set_type_c_state_machine_config(local_port, state).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::ExecuteUcsiCommand(command_data) => Ok(port::PortResponseData::UcsiResponse( - controller - .execute_ucsi_command(lpm::Command::new(local_port, command_data)) - .await - .map_err(|e| match e { - Error::Bus(_) => PdError::Failed, - Error::Pd(e) => e, - }), - )), - }) - } - - async fn process_controller_command( - &self, - cfu_event_receiver: &mut CfuEventReceiver, - controller: &mut D::Inner, - command: &port::InternalCommandData, - ) -> Response<'static> { - if cfu_event_receiver.fw_update_state.in_progress() { - debug!("FW update in progress, ignoring controller command"); - return port::Response::Controller(Err(PdError::Busy)); - } - - match command { - port::InternalCommandData::Status => { - let status = controller.get_controller_status().await; - port::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed)) - } - port::InternalCommandData::SyncState => { - let result = self.sync_state_internal(controller).await; - port::Response::Controller( - result - .map(|_| InternalResponseData::Complete) - .map_err(|_| PdError::Failed), - ) - } - port::InternalCommandData::Reset => { - let result = controller.reset_controller().await; - port::Response::Controller( - result - .map(|_| InternalResponseData::Complete) - .map_err(|_| PdError::Failed), - ) - } - } - } - - /// Handle a PD controller command - pub(super) async fn process_pd_command( - &self, - cfu_event_receiver: &mut CfuEventReceiver, - controller: &mut D::Inner, - command: &port::Command, - ) -> Response<'static> { - match command { - port::Command::Port(command) => self.process_port_command(cfu_event_receiver, controller, command).await, - port::Command::Controller(command) => { - self.process_controller_command(cfu_event_receiver, controller, command) - .await - } - port::Command::Lpm(_) => port::Response::Ucsi(ucsi::Response { - cci: ucsi::cci::Cci::new_error(), - data: None, - }), - } - } } From e831384e6df04f2ed2e27398b8024ffbf8681a2a Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Mon, 4 May 2026 13:14:53 -0700 Subject: [PATCH 72/79] type-c-service: Migrate code to use `BasicFwUpdate` trait (#808) --- Cargo.lock | 3 +- examples/rt685s-evk/Cargo.lock | 3 +- examples/rt685s-evk/src/bin/type_c.rs | 33 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 113 +++--- examples/std/Cargo.lock | 3 +- examples/std/src/bin/type_c/service.rs | 20 +- examples/std/src/bin/type_c/ucsi.rs | 22 +- examples/std/src/bin/type_c/unconstrained.rs | 28 +- .../std/src/lib/type_c/mock_controller.rs | 35 -- type-c-interface/src/port/mod.rs | 16 - type-c-service/Cargo.toml | 7 +- type-c-service/src/driver/tps6699x.rs | 286 +++++++++------ type-c-service/src/task.rs | 8 +- type-c-service/src/util.rs | 36 ++ type-c-service/src/wrapper/backing.rs | 7 - type-c-service/src/wrapper/cfu.rs | 329 ------------------ type-c-service/src/wrapper/dp.rs | 11 +- type-c-service/src/wrapper/event_receiver.rs | 77 +--- type-c-service/src/wrapper/message.rs | 18 - type-c-service/src/wrapper/mod.rs | 85 +---- type-c-service/src/wrapper/pd.rs | 9 +- type-c-service/src/wrapper/power.rs | 39 +-- type-c-service/src/wrapper/vdm.rs | 11 +- 23 files changed, 349 insertions(+), 850 deletions(-) delete mode 100644 type-c-service/src/wrapper/cfu.rs diff --git a/Cargo.lock b/Cargo.lock index cebf16175..6d5c731f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2116,15 +2116,14 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.9.4", - "cfu-service", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", - "embedded-cfu-protocol", "embedded-hal-async", "embedded-services", "embedded-usb-pd", + "fw-update-interface", "heapless 0.8.0", "log", "power-policy-interface", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 6a4d3b315..bd92534a0 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1524,15 +1524,14 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.11.1", - "cfu-service", "defmt 0.3.100", "embassy-futures", "embassy-sync", "embassy-time", - "embedded-cfu-protocol", "embedded-hal-async", "embedded-services", "embedded-usb-pd", + "fw-update-interface", "heapless 0.8.0", "power-policy-interface", "tps6699x", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 910b071d4..4f6981990 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -2,7 +2,6 @@ #![no_main] use ::tps6699x::{ADDR1, TPS66994_NUM_PORTS}; -use cfu_service::CfuClient; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_executor::Spawner; use embassy_imxrt::gpio::{Input, Inverter, Pull}; @@ -11,10 +10,8 @@ use embassy_imxrt::i2c::master::{Config, I2cMaster}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; -use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::{self as _, Delay}; -use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; use embedded_services::GlobalRawMutex; use embedded_services::event::MapSender; use embedded_services::{error, info}; @@ -52,15 +49,6 @@ bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; }); -struct Validator; - -impl type_c_service::wrapper::FwOfferValidator for Validator { - fn validate(&self, _current: FwVersion, _offer: &FwUpdateOffer) -> FwUpdateOfferResponse { - // For this example, we always accept the offer - FwUpdateOfferResponse::new_accept(HostToken::Driver) - } -} - type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; @@ -69,7 +57,6 @@ type Wrapper<'a> = ControllerWrapper< GlobalRawMutex, Tps6699xMutex<'a>, DynamicSender<'a, power_policy_interface::psu::event::EventData>, - Validator, >; type Controller<'a> = tps6699x::controller::Controller>; type InterruptProcessor<'a> = tps6699x::interrupt::InterruptProcessor<'a, GlobalRawMutex, BusDevice<'a>>; @@ -120,15 +107,13 @@ async fn pd_controller_task( let event = event_receiver.wait_event().await; let output = wrapper - .process_event( - &mut event_receiver.sink_ready_timeout, - &mut event_receiver.cfu_event_receiver, - event, - ) + .process_event(&mut event_receiver.sink_ready_timeout, event) .await; if let Err(e) = output { error!("Error processing event: {:?}", e); + continue; } + let output = output.unwrap(); if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { error!("Error finalizing output: {:?}", e); @@ -154,9 +139,8 @@ async fn type_c_service_task( service: &'static Mutex, event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - cfu_client: &'static CfuClient, ) { - type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers).await; } #[embassy_executor::main] @@ -208,7 +192,6 @@ async fn main(spawner: Spawner) { let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, - 0, // CFU component ID [ PortRegistration { id: PORT0_ID, @@ -256,6 +239,7 @@ async fn main(spawner: Spawner) { tps6699x, Default::default(), Default::default(), + "tps6699x_0", ))); static WRAPPER: StaticCell = StaticCell::new(); @@ -263,7 +247,6 @@ async fn main(spawner: Spawner) { controller_mutex, Default::default(), referenced, - Validator, )); let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); let bridge = Bridge::new(controller_mutex, &referenced.pd_controller); @@ -295,17 +278,12 @@ async fn main(spawner: Spawner) { static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - info!("Spawining type-c service task"); spawner.spawn( type_c_service_task( type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper], - cfu_client, ) .expect("Failed to create type-c service task"), ); @@ -328,7 +306,6 @@ async fn main(spawner: Spawner) { ArrayPortEventReceivers::new( InterruptReceiver::new(interrupt_receiver), power_event_receivers, - &storage.cfu_device, ), wrapper, ) diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 28affdd8c..d308229df 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -3,7 +3,7 @@ use ::tps6699x::{ADDR1, TPS66994_NUM_PORTS}; use cfu_service::CfuClient; -use cfu_service::component::{InternalResponseData, RequestData}; +use cfu_service::component::{CfuDevice, InternalResponseData, RequestData}; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_executor::Spawner; use embassy_imxrt::gpio::{Input, Inverter, Pull}; @@ -27,8 +27,7 @@ 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::port::ControllerId; -use type_c_interface::port::PortRegistration; +use type_c_interface::port::{ControllerId, 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; @@ -47,10 +46,10 @@ bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; }); -struct Validator; +struct CfuCustomization; -impl type_c_service::wrapper::FwOfferValidator for Validator { - fn validate(&self, _current: FwVersion, _offer: &FwUpdateOffer) -> FwUpdateOfferResponse { +impl cfu_service::customization::Customization for CfuCustomization { + fn validate(&mut self, _current: FwVersion, _offer: &FwUpdateOffer) -> FwUpdateOfferResponse { // For this example, we always accept the offer FwUpdateOfferResponse::new_accept(HostToken::Driver) } @@ -67,7 +66,6 @@ type Wrapper<'a> = ControllerWrapper< GlobalRawMutex, Tps6699xMutex<'a>, DynamicSender<'a, power_policy_interface::psu::event::EventData>, - Validator, >; type Controller<'a> = tps6699x::controller::Controller>; type InterruptProcessor<'a> = tps6699x::interrupt::InterruptProcessor<'a, GlobalRawMutex, BusDevice<'a>>; @@ -92,6 +90,9 @@ type PowerPolicyServiceType = Mutex< >; type ServiceType = Service<'static>; +type CfuUpdaterSharedStateType = Mutex; +type CfuUpdaterType<'a> = + cfu_service::basic::Updater<'a, Tps6699xMutex<'a>, CfuUpdaterSharedStateType, CfuCustomization>; const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); @@ -124,14 +125,11 @@ async fn pd_controller_task( let event = event_receiver.wait_event().await; let output = wrapper - .process_event( - &mut event_receiver.sink_ready_timeout, - &mut event_receiver.cfu_event_receiver, - event, - ) + .process_event(&mut event_receiver.sink_ready_timeout, event) .await; if let Err(e) = output { error!("Error processing event: {:?}", e); + continue; } let output = output.unwrap(); if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { @@ -140,20 +138,30 @@ async fn pd_controller_task( } } +#[embassy_executor::task] +async fn cfu_updater_task( + mut event_receiver: cfu_service::basic::event_receiver::EventReceiver<'static, CfuUpdaterSharedStateType>, + mut updater: CfuUpdaterType<'static>, +) -> ! { + loop { + let event = event_receiver.wait_next().await; + let output = updater.process_event(event).await; + event_receiver.finalize(output).await; + } +} + #[embassy_executor::task] async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: InterruptProcessor<'static>) { tps6699x::task::interrupt_task(&mut int_in, &mut [&mut interrupt]).await; } #[embassy_executor::task] -async fn fw_update_task() { +async fn fw_update_task(cfu_client: &'static CfuClient) { Timer::after_millis(1000).await; - let context = cfu_service::ClientContext::new(); - let device = context.get_device(CONTROLLER0_CFU_ID).unwrap(); info!("Getting FW version"); - let response = device - .execute_device_request(RequestData::FwVersionRequest) + let response = cfu_client + .route_request(CONTROLLER0_CFU_ID, RequestData::FwVersionRequest) .await .unwrap(); let prev_version = match response { @@ -165,14 +173,17 @@ async fn fw_update_task() { info!("Got version: {:#x}", prev_version); info!("Giving offer"); - let offer = device - .execute_device_request(RequestData::GiveOffer(FwUpdateOffer::new( - HostToken::Driver, + let offer = cfu_client + .route_request( CONTROLLER0_CFU_ID, - FwVersion::new(0x211), - 0, - 0, - ))) + RequestData::GiveOffer(FwUpdateOffer::new( + HostToken::Driver, + CONTROLLER0_CFU_ID, + FwVersion::new(0x211), + 0, + 0, + )), + ) .await .unwrap(); info!("Got response: {:?}", offer); @@ -202,8 +213,8 @@ async fn fw_update_task() { }; info!("Sending chunk {} of {}", i + 1, num_chunks); - let response = device - .execute_device_request(RequestData::GiveContent(request)) + let response = cfu_client + .route_request(CONTROLLER0_CFU_ID, RequestData::GiveContent(request)) .await .unwrap(); info!("Got response: {:?}", response); @@ -211,8 +222,8 @@ async fn fw_update_task() { Timer::after_millis(2000).await; info!("Getting FW version"); - let response = device - .execute_device_request(RequestData::FwVersionRequest) + let response = cfu_client + .route_request(CONTROLLER0_CFU_ID, RequestData::FwVersionRequest) .await .unwrap(); let version = match response { @@ -238,9 +249,8 @@ async fn type_c_service_task( service: &'static Mutex, event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - cfu_client: &'static CfuClient, ) { - type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers).await; } #[embassy_executor::main] @@ -292,7 +302,6 @@ async fn main(spawner: Spawner) { let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, - CONTROLLER0_CFU_ID, [ PortRegistration { id: PORT0_ID, @@ -344,18 +353,38 @@ async fn main(spawner: Spawner) { tps6699x, Default::default(), Default::default(), + "tps6699x_0", ))); - static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = WRAPPER.init(ControllerWrapper::new( + // Create controller CFU device and updater + static CFU_DEVICE: StaticCell = StaticCell::new(); + let cfu_device = CFU_DEVICE.init(CfuDevice::new(CONTROLLER0_CFU_ID)); + + static CFU_SHARED_STATE: StaticCell = StaticCell::new(); + let cfu_shared_state = CFU_SHARED_STATE.init(Mutex::new(cfu_service::basic::state::SharedState::new())); + + let cfu_event_receiver = + cfu_service::basic::event_receiver::EventReceiver::new(cfu_device, cfu_shared_state, Default::default()); + + let cfu_updater = cfu_service::basic::Updater::new( controller_mutex, + cfu_shared_state, Default::default(), - referenced, - Validator, - )); + CONTROLLER0_CFU_ID, + CfuCustomization, + ); + + // Create CFU client + static CFU_CLIENT: OnceLock = OnceLock::new(); + let cfu_client = CfuClient::new(&CFU_CLIENT).await; + cfu_client.register_device(cfu_device).unwrap(); + let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); let bridge = Bridge::new(controller_mutex, &referenced.pd_controller); + static WRAPPER: StaticCell = StaticCell::new(); + let wrapper = WRAPPER.init(ControllerWrapper::new(controller_mutex, Default::default(), referenced)); + // Create power policy service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< @@ -383,17 +412,12 @@ async fn main(spawner: Spawner) { static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - info!("Spawining type-c service task"); spawner.spawn( type_c_service_task( type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper], - cfu_client, ) .expect("Failed to spawn type-c service task"), ); @@ -416,12 +440,13 @@ async fn main(spawner: Spawner) { ArrayPortEventReceivers::new( InterruptReceiver::new(interrupt_receiver), power_event_receivers, - &storage.cfu_device, ), wrapper, ) - .expect("Failed to create pd controller task"), + .expect("Failed to create PD controller task"), ); - spawner.spawn(fw_update_task().expect("Failed to create fw update task")); + spawner.spawn(cfu_updater_task(cfu_event_receiver, cfu_updater).expect("Failed to create CFU updater task")); + + spawner.spawn(fw_update_task(cfu_client).expect("Failed to create fw update task")); } diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index ec73a321f..c28030e2b 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1485,14 +1485,13 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "bitflags 2.11.1", - "cfu-service", "embassy-futures", "embassy-sync", "embassy-time", - "embedded-cfu-protocol", "embedded-hal-async", "embedded-services", "embedded-usb-pd", + "fw-update-interface", "heapless 0.8.0", "log", "power-policy-interface", diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 81c6f4875..6d24fbe6b 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -1,8 +1,6 @@ -use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; -use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embedded_services::GlobalRawMutex; @@ -71,11 +69,7 @@ async fn controller_task( let event = event_receiver.wait_event().await; let output = wrapper - .process_event( - &mut event_receiver.sink_ready_timeout, - &mut event_receiver.cfu_event_receiver, - event, - ) + .process_event(&mut event_receiver.sink_ready_timeout, event) .await; if let Err(e) = output { error!("Error processing event: {e:?}"); @@ -121,7 +115,6 @@ async fn task(spawner: Spawner) { let storage = STORAGE.init(Storage::new( controller_context, CONTROLLER0_ID, - 0, // CFU component ID (unused) [PortRegistration { id: PORT0_ID, sender: PORT0_CHANNEL.dyn_sender(), @@ -165,7 +158,6 @@ async fn task(spawner: Spawner) { let event_receiver = ArrayPortEventReceivers::new( state.create_interrupt_receiver(), power_event_receivers, - &storage.cfu_device, ); static CONTROLLER: StaticCell> = StaticCell::new(); @@ -177,7 +169,6 @@ async fn task(spawner: Spawner) { controller, Default::default(), referenced, - crate::mock_controller::Validator, )); let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); @@ -210,10 +201,7 @@ 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))); - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - + // Spin up power policy service spawner.spawn( power_policy_psu_task( PsuEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), @@ -226,7 +214,6 @@ async fn task(spawner: Spawner) { type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper], - cfu_client, ) .expect("Failed to create type-c service task"), ); @@ -271,10 +258,9 @@ async fn type_c_service_task( service: &'static Mutex, event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers).await; } fn main() { diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 5d1c3425b..2e7ad82a1 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -40,8 +40,6 @@ const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const CFU0_ID: u8 = 0x00; -const CFU1_ID: u8 = 0x01; type DeviceType = Mutex>; @@ -219,11 +217,7 @@ async fn wrapper_task( let event = event_receiver.wait_event().await; let output = wrapper - .process_event( - &mut event_receiver.sink_ready_timeout, - &mut event_receiver.cfu_event_receiver, - event, - ) + .process_event(&mut event_receiver.sink_ready_timeout, event) .await; if let Err(e) = output { error!("Error processing event: {e:?}"); @@ -248,10 +242,9 @@ async fn type_c_service_task( service: &'static Mutex, event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers).await; } #[embassy_executor::task] @@ -268,7 +261,6 @@ async fn task(spawner: Spawner) { let storage0 = STORAGE0.init(Storage::new( controller_context, CONTROLLER0_ID, - CFU0_ID, [PortRegistration { id: PORT0_ID, sender: PORT0_CHANNEL.dyn_sender(), @@ -311,7 +303,6 @@ async fn task(spawner: Spawner) { let event_receiver0 = ArrayPortEventReceivers::new( state0.create_interrupt_receiver(), power_event_receivers0, - &storage0.cfu_device, ); static CONTROLLER0: StaticCell> = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); @@ -320,7 +311,6 @@ async fn task(spawner: Spawner) { controller0, Default::default(), referenced0, - mock_controller::Validator, )); let bridge_receiver0 = BridgeEventReceiver::new(&referenced0.pd_controller); let bridge0 = Bridge::new(controller0, &referenced0.pd_controller); @@ -335,7 +325,6 @@ async fn task(spawner: Spawner) { let storage1 = STORAGE1.init(Storage::new( controller_context, CONTROLLER1_ID, - CFU1_ID, [PortRegistration { id: PORT1_ID, sender: PORT1_CHANNEL.dyn_sender(), @@ -372,7 +361,6 @@ async fn task(spawner: Spawner) { let event_receiver1 = ArrayPortEventReceivers::new( state1.create_interrupt_receiver(), power_event_receivers1, - &storage1.cfu_device, ); static CONTROLLER1: StaticCell> = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); @@ -381,7 +369,6 @@ async fn task(spawner: Spawner) { controller1, Default::default(), referenced1, - mock_controller::Validator, )); let bridge_receiver1 = BridgeEventReceiver::new(&referenced1.pd_controller); let bridge1 = Bridge::new(controller1, &referenced1.pd_controller); @@ -440,10 +427,6 @@ async fn task(spawner: Spawner) { controller_context, ))); - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.spawn( power_policy_task( PsuEventReceivers::new( @@ -460,7 +443,6 @@ async fn task(spawner: Spawner) { type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper0, wrapper1], - cfu_client, ) .expect("Failed to create type-c service task"), ); diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 975bdbf69..c83d15bec 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,11 +1,9 @@ use crate::mock_controller::Wrapper; -use cfu_service::CfuClient; 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; -use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; use embassy_time::Timer; use embedded_services::GlobalRawMutex; @@ -35,15 +33,12 @@ const NUM_PD_CONTROLLERS: usize = 3; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); -const CFU0_ID: u8 = 0x00; const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT1_ID: GlobalPortId = GlobalPortId(1); -const CFU1_ID: u8 = 0x01; const CONTROLLER2_ID: ControllerId = ControllerId(2); const PORT2_ID: GlobalPortId = GlobalPortId(2); -const CFU2_ID: u8 = 0x02; const DELAY_MS: u64 = 1000; @@ -91,11 +86,7 @@ async fn controller_task( let event = event_receiver.wait_event().await; let output = wrapper - .process_event( - &mut event_receiver.sink_ready_timeout, - &mut event_receiver.cfu_event_receiver, - event, - ) + .process_event(&mut event_receiver.sink_ready_timeout, event) .await; if let Err(e) = output { error!("Error processing event: {e:?}"); @@ -125,7 +116,6 @@ async fn task(spawner: Spawner) { let storage0 = STORAGE0.init(Storage::new( controller_context, CONTROLLER0_ID, - CFU0_ID, [PortRegistration { id: PORT0_ID, sender: PORT0_CHANNEL.dyn_sender(), @@ -153,7 +143,6 @@ async fn task(spawner: Spawner) { let event_receiver0 = ArrayPortEventReceivers::new( state0.create_interrupt_receiver(), power_event_receivers0, - &storage0.cfu_device, ); static CONTROLLER0: StaticCell> = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); @@ -162,7 +151,6 @@ async fn task(spawner: Spawner) { controller0, Default::default(), referenced0, - crate::mock_controller::Validator, )); let bridge_receiver0 = BridgeEventReceiver::new(&referenced0.pd_controller); let bridge0 = Bridge::new(controller0, &referenced0.pd_controller); @@ -177,7 +165,6 @@ async fn task(spawner: Spawner) { let storage1 = STORAGE1.init(Storage::new( controller_context, CONTROLLER1_ID, - CFU1_ID, [PortRegistration { id: PORT1_ID, sender: PORT1_CHANNEL.dyn_sender(), @@ -205,7 +192,6 @@ async fn task(spawner: Spawner) { let event_receiver1 = ArrayPortEventReceivers::new( state1.create_interrupt_receiver(), power_event_receivers1, - &storage1.cfu_device, ); static CONTROLLER1: StaticCell> = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); @@ -214,7 +200,6 @@ async fn task(spawner: Spawner) { controller1, Default::default(), referenced1, - crate::mock_controller::Validator, )); let bridge_receiver1 = BridgeEventReceiver::new(&referenced1.pd_controller); let bridge1 = Bridge::new(controller1, &referenced1.pd_controller); @@ -229,7 +214,6 @@ async fn task(spawner: Spawner) { let storage2 = STORAGE2.init(Storage::new( controller_context, CONTROLLER2_ID, - CFU2_ID, [PortRegistration { id: PORT2_ID, sender: PORT2_CHANNEL.dyn_sender(), @@ -257,7 +241,6 @@ async fn task(spawner: Spawner) { let event_receiver2 = ArrayPortEventReceivers::new( state2.create_interrupt_receiver(), power_event_receivers2, - &storage2.cfu_device, ); static CONTROLLER2: StaticCell> = StaticCell::new(); let controller2 = CONTROLLER2.init(Mutex::new(mock_controller::Controller::new(state2))); @@ -266,7 +249,6 @@ async fn task(spawner: Spawner) { controller2, Default::default(), referenced2, - crate::mock_controller::Validator, )); let bridge_receiver2 = BridgeEventReceiver::new(&referenced2.pd_controller); let bridge2 = Bridge::new(controller2, &referenced2.pd_controller); @@ -302,10 +284,6 @@ async fn task(spawner: Spawner) { static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create(Default::default(), controller_context))); - // Spin up CFU service - static CFU_CLIENT: OnceLock = OnceLock::new(); - let cfu_client = CfuClient::new(&CFU_CLIENT).await; - spawner.spawn( power_policy_task( PsuEventReceivers::new( @@ -325,7 +303,6 @@ async fn task(spawner: Spawner) { type_c_service, EventReceiver::new(controller_context, power_policy_subscriber), [wrapper0, wrapper1, wrapper2], - cfu_client, ) .expect("Failed to create type-c service task"), ); @@ -399,10 +376,9 @@ async fn type_c_service_task( service: &'static Mutex, event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], - cfu_client: &'static CfuClient, ) { info!("Starting type-c task"); - type_c_service::task::task(service, event_receiver, wrappers, cfu_client).await; + type_c_service::task::task(service, event_receiver, wrappers).await; } fn main() { diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index cc97c19bd..28c6a6556 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -1,7 +1,6 @@ use std::num::NonZeroU8; use embassy_sync::{channel, mutex::Mutex, signal::Signal}; -use embedded_cfu_protocol::protocol_definitions::{FwUpdateOfferResponse, HostToken}; use embedded_services::GlobalRawMutex; use embedded_usb_pd::{Error, ado::Ado}; use embedded_usb_pd::{LocalPortId, PdError}; @@ -200,26 +199,6 @@ impl type_c_interface::port::Controller for Controller<'_> { Ok(()) } - async fn get_active_fw_version(&mut self) -> Result> { - Ok(0) - } - - async fn start_fw_update(&mut self) -> Result<(), Error> { - Ok(()) - } - - async fn abort_fw_update(&mut self) -> Result<(), Error> { - Ok(()) - } - - async fn finalize_fw_update(&mut self) -> Result<(), Error> { - Ok(()) - } - - async fn write_fw_contents(&mut self, _offset: usize, _data: &[u8]) -> Result<(), Error> { - Ok(()) - } - async fn set_max_sink_voltage( &mut self, port: LocalPortId, @@ -342,23 +321,9 @@ impl type_c_interface::port::Controller for Controller<'_> { } } -pub struct Validator; - -impl type_c_service::wrapper::FwOfferValidator for Validator { - fn validate( - &self, - _current: embedded_cfu_protocol::protocol_definitions::FwVersion, - _offer: &embedded_cfu_protocol::protocol_definitions::FwUpdateOffer, - ) -> embedded_cfu_protocol::protocol_definitions::FwUpdateOfferResponse { - // For this example, we always accept the new version - FwUpdateOfferResponse::new_accept(HostToken::Driver) - } -} - pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< 'a, GlobalRawMutex, Mutex>, channel::DynamicSender<'a, power_policy_interface::psu::event::EventData>, - Validator, >; diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index bb2c733e4..cd4877660 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -583,22 +583,6 @@ pub trait Controller { unconstrained: bool, ) -> impl Future>>; - // TODO: remove all these once we migrate to a generic FW update trait - // https://github.com/OpenDevicePartnership/embedded-services/issues/242 - /// Get current FW version - fn get_active_fw_version(&mut self) -> impl Future>>; - /// Start a firmware update - fn start_fw_update(&mut self) -> impl Future>>; - /// Abort a firmware update - fn abort_fw_update(&mut self) -> impl Future>>; - /// Finalize a firmware update - fn finalize_fw_update(&mut self) -> impl Future>>; - /// Write firmware update contents - fn write_fw_contents( - &mut self, - offset: usize, - data: &[u8], - ) -> impl Future>>; /// Get the Rx Other VDM data for the given port fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>>; /// Get the Rx Attention VDM data for the given port diff --git a/type-c-service/Cargo.toml b/type-c-service/Cargo.toml index 006618e8b..0582d28aa 100644 --- a/type-c-service/Cargo.toml +++ b/type-c-service/Cargo.toml @@ -17,17 +17,16 @@ workspace = true bitfield.workspace = true bitflags = { workspace = true } defmt = { workspace = true, optional = true } -embedded-cfu-protocol.workspace = true embassy-futures.workspace = true embassy-sync.workspace = true embassy-time.workspace = true embedded-hal-async.workspace = true embedded-services.workspace = true embedded-usb-pd.workspace = true +fw-update-interface.workspace = true heapless.workspace = true log = { workspace = true, optional = true } tps6699x = { workspace = true, features = ["embassy"] } -cfu-service.workspace = true power-policy-interface.workspace = true type-c-interface.workspace = true @@ -46,9 +45,9 @@ defmt = [ "embassy-sync/defmt", "tps6699x/defmt", "embedded-usb-pd/defmt", - "cfu-service/defmt", "power-policy-interface/defmt", "type-c-interface/defmt", + "fw-update-interface/defmt", ] log = [ "dep:log", @@ -56,7 +55,7 @@ log = [ "embassy-time/log", "embassy-sync/log", "tps6699x/log", - "cfu-service/log", "power-policy-interface/log", "type-c-interface/log", + "fw-update-interface/log", ] diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index 35d9faa25..6a17a43df 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -2,12 +2,12 @@ use ::tps6699x::registers::{PdCcPullUp, PpExtVbusSw, PpIntVbusSw}; use ::tps6699x::{PORT0, PORT1, TPS66993_NUM_PORTS, TPS66994_NUM_PORTS}; use bitfield::bitfield; use bitflags::bitflags; -use core::future::Future; use core::iter::zip; use core::num::NonZeroU8; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_time::Delay; use embedded_hal_async::i2c::I2c; +use embedded_services::named::Named; use embedded_services::{debug, error, trace, warn}; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::pdinfo::PowerPathStatus; @@ -15,6 +15,7 @@ use embedded_usb_pd::pdo::{Common, Contract, Rdo, sink, source}; use embedded_usb_pd::type_c::Current as TypecCurrent; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{DataRole, Error, LocalPortId, PdError, PlugOrientation, PowerRole}; +use fw_update_interface::basic::{Error as BasicFwUpdateError, FwUpdate as BasicFwUpdate}; use tps6699x::MAX_SUPPORTED_PORTS; use tps6699x::asynchronous::embassy::{self as tps6699x_drv, interrupt}; use tps6699x::asynchronous::fw_update::UpdateTarget; @@ -37,8 +38,8 @@ use type_c_interface::port::{ TypeCStateMachineState, UsbControlConfig, }; -use crate::util::power_capability_from_current; use crate::util::power_capability_try_from_contract; +use crate::util::{basic_fw_update_error_from_pd_bus_error, power_capability_from_current}; type Updater<'a, M, B> = BorrowedUpdaterInProgress>; @@ -87,6 +88,7 @@ pub struct Tps6699x<'a, M: RawMutex, B: I2c> { /// Firmware update configuration fw_update_config: FwUpdateConfig, config: Config, + name: &'static str, } impl<'a, M: RawMutex, B: I2c> Tps6699x<'a, M, B> { @@ -98,6 +100,7 @@ impl<'a, M: RawMutex, B: I2c> Tps6699x<'a, M, B> { num_ports: usize, fw_update_config: FwUpdateConfig, config: Config, + name: &'static str, ) -> Option { if num_ports == 0 || num_ports > MAX_SUPPORTED_PORTS { None @@ -108,9 +111,27 @@ impl<'a, M: RawMutex, B: I2c> Tps6699x<'a, M, B> { update_state: None, fw_update_config, config, + name, }) } } + + fn log_error(&self, e: Error) -> Error { + match e { + Error::Bus(_) => error!("({}): Bus error", self.name()), + Error::Pd(pd_error) => error!("({}): PD error: {:#?}", self.name(), pd_error), + } + e + } + + /// Returns a busy error if a FW update is currently in progress + fn guard_no_fw_update_active(&self) -> Result<(), Error> { + if self.update_state.is_some() { + Err(Error::Pd(PdError::Busy)) + } else { + Ok(()) + } + } } bitfield! { @@ -190,11 +211,136 @@ bitfield! { pub u16, reserved1, set_reserved1: 31, 16; } +impl Named for Tps6699x<'_, M, B> { + fn name(&self) -> &'static str { + self.name + } +} + +impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { + async fn get_active_fw_version(&mut self) -> Result { + let customer_use = CustomerUse( + self.tps6699x + .get_customer_use() + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?, + ); + Ok(customer_use.custom_fw_version()) + } + + async fn start_fw_update(&mut self) -> Result<(), BasicFwUpdateError> { + let mut delay = Delay; + let mut updater: BorrowedUpdater> = + BorrowedUpdater::with_config(self.fw_update_config.clone()); + + // Abandon any previous in-progress update + if let Some(update) = self.update_state.take() { + warn!("Abandoning in-progress update"); + update + .updater + .abort_fw_update(&mut [&mut self.tps6699x], &mut delay) + .await; + } + + let mut guards = [const { None }; MAX_SUPPORTED_PORTS]; + // Disable all interrupts on both ports, use guards[1] to ensure that this set of guards is dropped last + disable_all_interrupts::>(&mut [&mut self.tps6699x], &mut guards[1..]) + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?; + let in_progress = updater + .start_fw_update(&mut [&mut self.tps6699x], &mut delay) + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?; + + // Re-enable interrupts on port 0 only + if let Err(e) = + enable_port0_interrupts::>(&mut [&mut self.tps6699x], &mut guards[0..1]) + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e))) + { + error!("Failed to enable port 0 interrupts, aborting update: {:#?}", e); + in_progress.abort_fw_update(&mut [&mut self.tps6699x], &mut delay).await; + return Err(e); + } + + self.update_state = Some(FwUpdateState { + updater: in_progress, + guards, + }); + Ok(()) + } + + /// Aborts the firmware update in progress + /// + /// This can reset the controller + async fn abort_fw_update(&mut self) -> Result<(), BasicFwUpdateError> { + // Check if we're still in firmware update mode + if self + .tps6699x + .get_mode() + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))? + == tps6699x::Mode::F211 + { + let mut delay = Delay; + + if let Some(update) = self.update_state.take() { + // Attempt to abort the firmware update by consuming our update object + update + .updater + .abort_fw_update(&mut [&mut self.tps6699x], &mut delay) + .await; + Ok(()) + } else { + // Bypass our update object since we've gotten into a state where we don't have one + self.tps6699x + .fw_update_mode_exit(&mut delay) + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e))) + } + } else { + // Not in FW update mode, don't need to do anything + Ok(()) + } + } + + /// Finalize the firmware update + /// + /// This will reset the controller + async fn finalize_fw_update(&mut self) -> Result<(), BasicFwUpdateError> { + if let Some(update) = self.update_state.take() { + let mut delay = Delay; + update + .updater + .complete_fw_update(&mut [&mut self.tps6699x], &mut delay) + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e))) + } else { + Err(BasicFwUpdateError::NeedsActiveUpdate) + } + } + + async fn write_fw_contents(&mut self, _offset: usize, data: &[u8]) -> Result<(), BasicFwUpdateError> { + if let Some(update) = &mut self.update_state { + let mut delay = Delay; + update + .updater + .write_bytes(&mut [&mut self.tps6699x], &mut delay, data) + .await + .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?; + Ok(()) + } else { + Err(BasicFwUpdateError::NeedsActiveUpdate) + } + } +} + impl Controller for Tps6699x<'_, M, B> { type BusError = B::Error; /// Controller reset async fn reset_controller(&mut self) -> Result<(), Error> { + self.guard_no_fw_update_active()?; let mut delay = Delay; self.tps6699x.reset(&mut delay).await?; @@ -203,6 +349,7 @@ impl Controller for Tps6699x<'_, M, B> { /// Returns the current status of the port async fn get_port_status(&mut self, port: LocalPortId) -> Result> { + self.guard_no_fw_update_active()?; let status = self.tps6699x.get_port_status(port).await?; debug!("Port{} status: {:#?}", port.0, status); @@ -329,6 +476,7 @@ impl Controller for Tps6699x<'_, M, B> { &mut self, port: LocalPortId, ) -> Result> { + self.guard_no_fw_update_active()?; match self.tps6699x.get_rt_fw_update_status(port).await { Ok(true) => Ok(RetimerFwUpdateState::Active), Ok(false) => Ok(RetimerFwUpdateState::Inactive), @@ -337,21 +485,22 @@ impl Controller for Tps6699x<'_, M, B> { } async fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> Result<(), Error> { + self.guard_no_fw_update_active()?; self.tps6699x.set_rt_fw_update_state(port).await } - fn clear_rt_fw_update_state( - &mut self, - port: LocalPortId, - ) -> impl Future>> { - self.tps6699x.clear_rt_fw_update_state(port) + async fn clear_rt_fw_update_state(&mut self, port: LocalPortId) -> Result<(), Error> { + self.guard_no_fw_update_active()?; + self.tps6699x.clear_rt_fw_update_state(port).await } async fn set_rt_compliance(&mut self, port: LocalPortId) -> Result<(), Error> { + self.guard_no_fw_update_active()?; self.tps6699x.set_rt_compliance(port).await } async fn reconfigure_retimer(&mut self, port: LocalPortId) -> Result<(), Error> { + self.guard_no_fw_update_active()?; let input = { let mut input = tps6699x::command::muxr::Input(0); input.set_en_retry_on_target_addr_tbt(true); @@ -368,6 +517,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> Result<(), Error> { + self.guard_no_fw_update_active()?; match self.tps6699x.execute_dbfg(port).await? { ReturnValue::Success => Ok(()), r => { @@ -378,15 +528,18 @@ impl Controller for Tps6699x<'_, M, B> { } async fn enable_sink_path(&mut self, port: LocalPortId, enable: bool) -> Result<(), Error> { + self.guard_no_fw_update_active()?; debug!("Port{} enable sink path: {}", port.0, enable); self.tps6699x.enable_sink_path(port, enable).await } async fn get_pd_alert(&mut self, port: LocalPortId) -> Result, Error> { + self.guard_no_fw_update_active()?; self.tps6699x.get_rx_ado(port).await.map_err(Error::from) } async fn get_controller_status(&mut self) -> Result, Error> { + self.guard_no_fw_update_active()?; let boot_flags = self.tps6699x.get_boot_flags().await?; let customer_use = CustomerUse(self.tps6699x.get_customer_use().await?); @@ -399,109 +552,26 @@ impl Controller for Tps6699x<'_, M, B> { }) } - fn set_unconstrained_power( + async fn set_unconstrained_power( &mut self, port: LocalPortId, unconstrained: bool, - ) -> impl Future>> { - self.tps6699x.set_unconstrained_power(port, unconstrained) - } - - async fn get_active_fw_version(&mut self) -> Result> { - let customer_use = CustomerUse(self.tps6699x.get_customer_use().await?); - Ok(customer_use.custom_fw_version()) - } - - async fn start_fw_update(&mut self) -> Result<(), Error> { - let mut delay = Delay; - let mut updater: BorrowedUpdater> = - BorrowedUpdater::with_config(self.fw_update_config.clone()); - - // Abandon any previous in-progress update - if let Some(update) = self.update_state.take() { - warn!("Abandoning in-progress update"); - update - .updater - .abort_fw_update(&mut [&mut self.tps6699x], &mut delay) - .await; - } - - let mut guards = [const { None }; MAX_SUPPORTED_PORTS]; - // Disable all interrupts on both ports, use guards[1] to ensure that this set of guards is dropped last - disable_all_interrupts::>(&mut [&mut self.tps6699x], &mut guards[1..]).await?; - let in_progress = updater.start_fw_update(&mut [&mut self.tps6699x], &mut delay).await?; - // Re-enable interrupts on port 0 only - enable_port0_interrupts::>(&mut [&mut self.tps6699x], &mut guards[0..1]) - .await?; - self.update_state = Some(FwUpdateState { - updater: in_progress, - guards, - }); - Ok(()) - } - - /// Aborts the firmware update in progress - /// - /// This can reset the controller - async fn abort_fw_update(&mut self) -> Result<(), Error> { - // Check if we're still in firmware update mode - if self.tps6699x.get_mode().await? == tps6699x::Mode::F211 { - let mut delay = Delay; - - if let Some(update) = self.update_state.take() { - // Attempt to abort the firmware update by consuming our update object - update - .updater - .abort_fw_update(&mut [&mut self.tps6699x], &mut delay) - .await; - Ok(()) - } else { - // Bypass our update object since we've gotten into a state where we don't have one - self.tps6699x.fw_update_mode_exit(&mut delay).await - } - } else { - // Not in FW update mode, don't need to do anything - Ok(()) - } - } - - /// Finalize the firmware update - /// - /// This will reset the controller - async fn finalize_fw_update(&mut self) -> Result<(), Error> { - if let Some(update) = self.update_state.take() { - let mut delay = Delay; - update - .updater - .complete_fw_update(&mut [&mut self.tps6699x], &mut delay) - .await - } else { - Err(PdError::InvalidMode.into()) - } - } - - async fn write_fw_contents(&mut self, _offset: usize, data: &[u8]) -> Result<(), Error> { - if let Some(update) = &mut self.update_state { - let mut delay = Delay; - update - .updater - .write_bytes(&mut [&mut self.tps6699x], &mut delay, data) - .await?; - Ok(()) - } else { - Err(PdError::InvalidMode.into()) - } + ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; + self.tps6699x.set_unconstrained_power(port, unconstrained).await } - fn set_max_sink_voltage( + async fn set_max_sink_voltage( &mut self, port: LocalPortId, voltage_mv: Option, - ) -> impl Future>> { - self.tps6699x.set_autonegotiate_sink_max_voltage(port, voltage_mv) + ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; + self.tps6699x.set_autonegotiate_sink_max_voltage(port, voltage_mv).await } async fn get_other_vdm(&mut self, port: LocalPortId) -> Result> { + self.guard_no_fw_update_active()?; match self.tps6699x.get_rx_other_vdm(port).await { Ok(vdm) => Ok((*vdm.as_bytes()).into()), Err(e) => Err(e), @@ -509,6 +579,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn get_attn_vdm(&mut self, port: LocalPortId) -> Result> { + self.guard_no_fw_update_active()?; match self.tps6699x.get_rx_attn_vdm(port).await { Ok(vdm) => { let buf: [u8; ATTN_VDM_LEN] = vdm.into(); @@ -520,6 +591,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> Result<(), Error> { + self.guard_no_fw_update_active()?; let input = { let mut input = tps6699x::command::vdms::Input::default(); input.set_num_vdo(tx_vdm.vdo_count); @@ -554,6 +626,7 @@ impl Controller for Tps6699x<'_, M, B> { port: LocalPortId, config: UsbControlConfig, ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; match self.config.usb_control_method { UsbControlMethod::TxIdentity => { let tx_identity_value = @@ -606,6 +679,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { + self.guard_no_fw_update_active()?; let dp_status = self.tps6699x.get_dp_status(port).await?; debug!("Port{} DP status: {:#?}", port.0, dp_status); @@ -622,6 +696,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), Error> { + self.guard_no_fw_update_active()?; debug!("Port{} setting DP config: {:#?}", port.0, config); let mut dp_config_reg = self.tps6699x.get_dp_config(port).await?; @@ -637,6 +712,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn execute_drst(&mut self, port: LocalPortId) -> Result<(), Error> { + self.guard_no_fw_update_active()?; match self.tps6699x.execute_drst(port).await? { ReturnValue::Success => Ok(()), r => { @@ -647,6 +723,7 @@ impl Controller for Tps6699x<'_, M, B> { } async fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> Result<(), Error> { + self.guard_no_fw_update_active()?; debug!("Port{} setting TBT config: {:#?}", port.0, config); let mut config_reg = self.tps6699x.lock_inner().await.get_tbt_config(port).await?; @@ -662,6 +739,7 @@ impl Controller for Tps6699x<'_, M, B> { port: LocalPortId, config: PdStateMachineConfig, ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; debug!("Port{} setting PD state machine config: {:#?}", port.0, config); let mut config_reg = self.tps6699x.lock_inner().await.get_port_config(port).await?; @@ -676,6 +754,7 @@ impl Controller for Tps6699x<'_, M, B> { port: LocalPortId, state: TypeCStateMachineState, ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; debug!("Port{} setting Type-C state machine state: {:#?}", port.0, state); let mut config_reg = self.tps6699x.lock_inner().await.get_port_config(port).await?; @@ -694,6 +773,7 @@ impl Controller for Tps6699x<'_, M, B> { &mut self, command: lpm::LocalCommand, ) -> Result, Error> { + self.guard_no_fw_update_active()?; self.tps6699x.execute_ucsi_command(&command).await } @@ -702,6 +782,7 @@ impl Controller for Tps6699x<'_, M, B> { port: LocalPortId, reconnect_time_s: Option, ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; let reconnect_time_s = reconnect_time_s.map(|t| t.get()); match self.tps6699x.execute_disc(port, reconnect_time_s).await? { ReturnValue::Success => Ok(()), @@ -717,6 +798,7 @@ impl Controller for Tps6699x<'_, M, B> { port: LocalPortId, state: SystemPowerState, ) -> Result<(), Error> { + self.guard_no_fw_update_active()?; use tps6699x::registers::SystemPowerState as DriverSystemPowerState; let driver_state = match state { @@ -748,6 +830,7 @@ pub fn tps66994<'a, M: RawMutex, BUS: I2c>( controller: tps6699x_drv::Tps6699x<'a, M, BUS>, fw_update_config: FwUpdateConfig, config: Config, + name: &'static str, ) -> Tps6699x<'a, M, BUS> { const _: () = assert!( TPS66994_NUM_PORTS > 0 && TPS66994_NUM_PORTS <= MAX_SUPPORTED_PORTS, @@ -756,7 +839,7 @@ pub fn tps66994<'a, M: RawMutex, BUS: I2c>( // Panic safety: statically checked above #[allow(clippy::unwrap_used)] - Tps6699x::try_new(controller, TPS66994_NUM_PORTS, fw_update_config, config).unwrap() + Tps6699x::try_new(controller, TPS66994_NUM_PORTS, fw_update_config, config, name).unwrap() } /// Create a TPS66993 object mutex @@ -764,6 +847,7 @@ pub fn tps66993<'a, M: RawMutex, BUS: I2c>( controller: tps6699x_drv::Tps6699x<'a, M, BUS>, fw_update_config: FwUpdateConfig, config: Config, + name: &'static str, ) -> Tps6699x<'a, M, BUS> { const _: () = assert!( TPS66993_NUM_PORTS > 0 && TPS66993_NUM_PORTS <= MAX_SUPPORTED_PORTS, @@ -772,7 +856,7 @@ pub fn tps66993<'a, M: RawMutex, BUS: I2c>( // Panic safety: statically checked above #[allow(clippy::unwrap_used)] - Tps6699x::try_new(controller, TPS66993_NUM_PORTS, fw_update_config, config).unwrap() + Tps6699x::try_new(controller, TPS66993_NUM_PORTS, fw_update_config, config, name).unwrap() } bitfield! { diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index a1ad9330a..cd1ed7dd9 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -12,22 +12,20 @@ use crate::{ }; /// Task to run the Type-C service, running the default event loop -pub async fn task, const N: usize>( +pub async fn task, const N: usize>( service: &'static impl Lockable>, mut event_receiver: EventReceiver<'static, PowerReceiver>, - wrappers: [&'static ControllerWrapper<'static, M, D, S, V>; N], - cfu_client: &'static cfu_service::CfuClient, + wrappers: [&'static ControllerWrapper<'static, M, D, S>; N], ) where M: embassy_sync::blocking_mutex::raw::RawMutex, D: embedded_services::sync::Lockable, S: event::Sender, - V: crate::wrapper::FwOfferValidator, ::Inner: type_c_interface::port::Controller, { info!("Starting type-c task"); for controller_wrapper in wrappers { - if controller_wrapper.register(cfu_client).is_err() { + if controller_wrapper.register().is_err() { error!("Failed to register a controller"); return; } diff --git a/type-c-service/src/util.rs b/type-c-service/src/util.rs index 9eaadeaf7..c9203d9d7 100644 --- a/type-c-service/src/util.rs +++ b/type-c-service/src/util.rs @@ -1,6 +1,9 @@ //! Type-C service utility functions and constants. use embedded_usb_pd::pdo::{Common, Contract}; use embedded_usb_pd::type_c; +use embedded_usb_pd::{Error as PdBusError, PdError}; +use fw_update_interface::basic::Error as BasicFwError; +use power_policy_interface::psu::Error as PowerPolicyError; pub fn power_capability_try_from_contract( contract: Contract, @@ -46,3 +49,36 @@ pub const POWER_CAPABILITY_5V_3A0: power_policy_interface::capability::PowerCapa voltage_mv: 5000, current_ma: 3000, }; + +/// Converts a PD error into a basic FW update error +pub fn basic_fw_update_error_from_pd_error(pd_error: PdError) -> BasicFwError { + match pd_error { + PdError::Busy => BasicFwError::Busy, + _ => BasicFwError::Failed, + } +} + +/// Converts a PD error into a basic FW update error +pub fn basic_fw_update_error_from_pd_bus_error(pd_error: PdBusError) -> BasicFwError { + match pd_error { + PdBusError::Pd(pd_error) => basic_fw_update_error_from_pd_error(pd_error), + PdBusError::Bus(_) => BasicFwError::Bus, + } +} + +/// Converts a PD error into a power policy error +pub fn power_policy_error_from_pd_error(pd_error: PdError) -> PowerPolicyError { + match pd_error { + PdError::Busy => PowerPolicyError::Busy, + PdError::Timeout => PowerPolicyError::Timeout, + _ => PowerPolicyError::Failed, + } +} + +/// Converts a PD bus error into a power policy error +pub fn power_policy_error_from_pd_bus_error(pd_error: PdBusError) -> PowerPolicyError { + match pd_error { + PdBusError::Pd(pd_error) => power_policy_error_from_pd_error(pd_error), + PdBusError::Bus(_) => PowerPolicyError::Bus, + } +} diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs index 24e613598..4d287ebf6 100644 --- a/type-c-service/src/wrapper/backing.rs +++ b/type-c-service/src/wrapper/backing.rs @@ -4,10 +4,8 @@ //! use core::array::from_fn; -use cfu_service::component::CfuDevice; use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; -use embedded_cfu_protocol::protocol_definitions::ComponentId; use embedded_services::event; use type_c_interface::port::{ControllerId, PortRegistration, PortStatus, event::PortStatusEventBitfield}; @@ -18,7 +16,6 @@ use crate::wrapper::proxy::{PowerProxyChannel, PowerProxyDevice, PowerProxyRecei pub struct Registration<'a, M: RawMutex> { pub context: &'a type_c_interface::service::context::Context, pub pd_controller: &'a type_c_interface::port::Device<'a>, - pub cfu_device: &'a CfuDevice, pub power_devices: &'a [&'a Mutex>], } @@ -34,7 +31,6 @@ pub struct Storage<'a, const N: usize, M: RawMutex> { context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, pd_ports: [PortRegistration; N], - pub cfu_device: CfuDevice, power_proxy_channels: [PowerProxyChannel; N], } @@ -42,14 +38,12 @@ impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { pub fn new( context: &'a type_c_interface::service::context::Context, controller_id: ControllerId, - cfu_id: ComponentId, pd_ports: [PortRegistration; N], ) -> Self { Self { context, controller_id, pd_ports, - cfu_device: CfuDevice::new(cfu_id), power_proxy_channels: from_fn(|_| PowerProxyChannel::new()), } } @@ -181,7 +175,6 @@ impl<'a, const N: usize, M: RawMutex, S: event::Sender bool { - matches!(self, FwUpdateState::InProgress(_) | FwUpdateState::Recovery) - } -} - -impl< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, - V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, V> -where - D::Inner: Controller, -{ - /// Create a new invalid FW version response - fn create_invalid_fw_version_response(&self) -> InternalResponseData { - let dev_inf = FwVerComponentInfo::new(FwVersion::new(0xffffffff), self.registration.cfu_device.component_id()); - let comp_info: [FwVerComponentInfo; MAX_CMPT_COUNT] = [dev_inf; MAX_CMPT_COUNT]; - InternalResponseData::FwVersionResponse(GetFwVersionResponse { - header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), - component_info: comp_info, - }) - } - - /// Process a GetFwVersion command - async fn process_get_fw_version(&self, target: &mut D::Inner) -> InternalResponseData { - let version = match target.get_active_fw_version().await { - Ok(v) => v, - Err(Error::Pd(e)) => { - error!("Failed to get active firmware version: {:?}", e); - return self.create_invalid_fw_version_response(); - } - Err(Error::Bus(_)) => { - error!("Failed to get active firmware version, bus error"); - return self.create_invalid_fw_version_response(); - } - }; - - let dev_inf = FwVerComponentInfo::new(FwVersion::new(version), self.registration.cfu_device.component_id()); - let comp_info: [FwVerComponentInfo; MAX_CMPT_COUNT] = [dev_inf; MAX_CMPT_COUNT]; - InternalResponseData::FwVersionResponse(GetFwVersionResponse { - header: GetFwVersionResponseHeader::new(1, GetFwVerRespHeaderByte3::NoSpecialFlags), - component_info: comp_info, - }) - } - - /// Create an offer rejection response - fn create_offer_rejection() -> InternalResponseData { - InternalResponseData::OfferResponse(FwUpdateOfferResponse::new_with_failure( - HostToken::Driver, - OfferRejectReason::InvalidComponent, - OfferStatus::Reject, - )) - } - - /// Process a GiveOffer command - async fn process_give_offer(&self, target: &mut D::Inner, offer: &FwUpdateOffer) -> InternalResponseData { - if offer.component_info.component_id != self.registration.cfu_device.component_id() { - return Self::create_offer_rejection(); - } - - let version = match target.get_active_fw_version().await { - Ok(v) => v, - Err(Error::Pd(e)) => { - error!("Failed to get active firmware version: {:?}", e); - return Self::create_offer_rejection(); - } - Err(Error::Bus(_)) => { - error!("Failed to get active firmware version, bus error"); - return Self::create_offer_rejection(); - } - }; - - InternalResponseData::OfferResponse(self.fw_version_validator.validate(FwVersion::new(version), offer)) - } - - async fn process_abort_update( - &self, - event_receiver: &mut CfuEventReceiver, - controller: &mut D::Inner, - ) -> InternalResponseData { - // abort the update process - match controller.abort_fw_update().await { - Ok(_) => { - debug!("FW update aborted successfully"); - event_receiver.fw_update_state = FwUpdateState::Idle; - } - Err(Error::Pd(e)) => { - error!("Failed to abort FW update: {:?}", e); - event_receiver.fw_update_state = FwUpdateState::Recovery; - } - Err(Error::Bus(_)) => { - error!("Failed to abort FW update, bus error"); - event_receiver.fw_update_state = FwUpdateState::Recovery; - } - } - - InternalResponseData::ComponentPrepared - } - - /// Process a GiveContent command - async fn process_give_content( - &self, - event_receiver: &mut CfuEventReceiver, - controller: &mut D::Inner, - content: &FwUpdateContentCommand, - ) -> InternalResponseData { - let data = if let Some(data) = content.data.get(0..content.header.data_length as usize) { - data - } else { - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorPrepare, - )); - }; - - debug!("Got content {:#?}", content); - if content.header.flags & FW_UPDATE_FLAG_FIRST_BLOCK != 0 { - debug!("Got first block"); - - // Detach from the power policy so it doesn't attempt to do anything while we are updating - let controller_id = self.registration.pd_controller.id(); - for port in self.ports { - let psu_state = port.proxy.lock().await.psu_state.psu_state; - info!("Controller{}: checking power device", controller_id.0); - if psu_state != power_policy_interface::psu::PsuState::Detached { - info!("Controller{}: Detaching power device", controller_id.0); - port.state - .lock() - .await - .power_policy_sender - .send(power_policy_interface::psu::event::EventData::Detached) - .await; - } - } - - // Need to start the update - event_receiver.reset_ticker(); - match controller.start_fw_update().await { - Ok(_) => { - debug!("FW update started successfully"); - } - Err(Error::Pd(e)) => { - error!("Failed to start FW update: {:?}", e); - event_receiver.fw_update_state = FwUpdateState::Recovery; - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorPrepare, - )); - } - Err(Error::Bus(_)) => { - error!("Failed to start FW update, bus error"); - event_receiver.fw_update_state = FwUpdateState::Recovery; - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorPrepare, - )); - } - } - - event_receiver.fw_update_state = FwUpdateState::InProgress(0); - } - - match controller - .write_fw_contents(content.header.firmware_address as usize, data) - .await - { - Ok(_) => { - debug!("Block written successfully"); - } - Err(Error::Pd(e)) => { - error!("Failed to write block: {:?}", e); - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorWrite, - )); - } - Err(Error::Bus(_)) => { - error!("Failed to write block, bus error"); - return InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::ErrorWrite, - )); - } - } - - if content.header.flags & FW_UPDATE_FLAG_LAST_BLOCK != 0 { - match controller.finalize_fw_update().await { - Ok(_) => { - debug!("FW update finalized successfully"); - event_receiver.fw_update_state = FwUpdateState::Idle; - } - Err(Error::Pd(e)) => { - error!("Failed to finalize FW update: {:?}", e); - event_receiver.fw_update_state = FwUpdateState::Recovery; - return Self::create_offer_rejection(); - } - Err(Error::Bus(_)) => { - error!("Failed to finalize FW update, bus error"); - event_receiver.fw_update_state = FwUpdateState::Recovery; - return Self::create_offer_rejection(); - } - } - } - - InternalResponseData::ContentResponse(FwUpdateContentResponse::new( - content.header.sequence_num, - CfuUpdateContentResponseStatus::Success, - )) - } - - /// Process a CFU tick - pub async fn process_cfu_tick(&self, event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner) { - match event_receiver.fw_update_state { - FwUpdateState::Idle => { - // No FW update in progress, nothing to do - return; - } - FwUpdateState::InProgress(ticks) => { - if ticks + 1 < DEFAULT_FW_UPDATE_TIMEOUT_TICKS { - trace!("CFU tick: {}", ticks); - event_receiver.fw_update_state = FwUpdateState::InProgress(ticks + 1); - return; - } else { - error!("FW update timed out after {} ticks", ticks); - } - } - FwUpdateState::Recovery => { - // Continue recovery process - } - }; - - // Update timed out, attempt to exit the FW update - event_receiver.fw_update_state = FwUpdateState::Recovery; - match controller.abort_fw_update().await { - Ok(_) => { - debug!("FW update aborted successfully"); - } - Err(Error::Pd(e)) => { - error!("Failed to abort FW update: {:?}", e); - return; - } - Err(Error::Bus(_)) => { - error!("Failed to abort FW update, bus error"); - return; - } - } - - event_receiver.fw_update_state = FwUpdateState::Idle; - } - - /// Process a CFU command - pub async fn process_cfu_command( - &self, - event_receiver: &mut CfuEventReceiver, - controller: &mut D::Inner, - command: &RequestData, - ) -> InternalResponseData { - if event_receiver.fw_update_state == FwUpdateState::Recovery { - debug!("FW update in recovery state, rejecting command"); - return InternalResponseData::ComponentBusy; - } - - match command { - RequestData::FwVersionRequest => { - debug!("Got FwVersionRequest"); - self.process_get_fw_version(controller).await - } - RequestData::GiveOffer(offer) => { - debug!("Got GiveOffer"); - self.process_give_offer(controller, offer).await - } - RequestData::GiveContent(content) => { - debug!("Got GiveContent"); - self.process_give_content(event_receiver, controller, content).await - } - RequestData::AbortUpdate => { - debug!("Got AbortUpdate"); - self.process_abort_update(event_receiver, controller).await - } - RequestData::FinalizeUpdate => { - debug!("Got FinalizeUpdate"); - InternalResponseData::ComponentPrepared - } - RequestData::PrepareComponentForUpdate => { - debug!("Got PrepareComponentForUpdate"); - InternalResponseData::ComponentPrepared - } - RequestData::GiveOfferExtended(_) => { - debug!("Got GiveExtendedOffer, rejecting"); - Self::create_offer_rejection() - } - RequestData::GiveOfferInformation(_) => { - debug!("Got GiveOfferInformation, rejecting"); - Self::create_offer_rejection() - } - } - } - - /// Sends a CFU response to the command - pub async fn send_cfu_response(&self, response: InternalResponseData) { - self.registration.cfu_device.send_response(response).await; - } -} diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs index 1ca250685..a5c6740f7 100644 --- a/type-c-service/src/wrapper/dp.rs +++ b/type-c-service/src/wrapper/dp.rs @@ -1,17 +1,12 @@ -use super::{ControllerWrapper, FwOfferValidator}; +use super::ControllerWrapper; use crate::wrapper::message::OutputDpStatusChanged; use embassy_sync::blocking_mutex::raw::RawMutex; use embedded_services::{event, sync::Lockable, trace}; use embedded_usb_pd::{Error, LocalPortId}; use type_c_interface::port::Controller; -impl< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, - V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, V> +impl<'device, M: RawMutex, D: Lockable, S: event::Sender> + ControllerWrapper<'device, M, D, S> where D::Inner: Controller, { diff --git a/type-c-service/src/wrapper/event_receiver.rs b/type-c-service/src/wrapper/event_receiver.rs index 14e265190..dff283dea 100644 --- a/type-c-service/src/wrapper/event_receiver.rs +++ b/type-c-service/src/wrapper/event_receiver.rs @@ -2,15 +2,13 @@ use core::array; use core::future::pending; use core::pin::pin; -use embassy_futures::select::{Either, Either4, select, select_slice, select4}; -use embassy_time::{Instant, Ticker, Timer}; +use embassy_futures::select::{Either3, select_slice, select3}; +use embassy_time::{Instant, Timer}; use embedded_services::{debug, trace}; use embedded_usb_pd::LocalPortId; use crate::PortEventStreamer; -use crate::wrapper::DEFAULT_FW_UPDATE_TICK_INTERVAL_MS; -use crate::wrapper::cfu::FwUpdateState; -use crate::wrapper::message::{Event, EventCfu, LocalPortEvent, PowerPolicyCommand}; +use crate::wrapper::message::{Event, LocalPortEvent, PowerPolicyCommand}; use crate::wrapper::proxy::PowerProxyReceiver; use type_c_interface::port::event::{PortEvent, PortEventBitfield, PortStatusEventBitfield}; @@ -100,56 +98,6 @@ impl<'device, const N: usize> ArrayPowerProxyEventReceiver<'device, N> { } } -/// Struct to receive CFU events. -pub struct CfuEventReceiver { - /// FW update ticker used to check for timeouts and recovery attempts - fw_update_ticker: Ticker, - /// CFU device used for firmware updates - cfu_device: &'static cfu_service::component::CfuDevice, - pub fw_update_state: FwUpdateState, -} - -impl CfuEventReceiver { - /// Create a new CFU event receiver - pub fn new(cfu_device: &'static cfu_service::component::CfuDevice) -> Self { - Self { - fw_update_ticker: Ticker::every(embassy_time::Duration::from_millis(DEFAULT_FW_UPDATE_TICK_INTERVAL_MS)), - cfu_device, - fw_update_state: FwUpdateState::Idle, - } - } - - /// Wait for the next CFU event - pub async fn wait_next(&mut self) -> EventCfu { - match self.fw_update_state { - FwUpdateState::Idle => { - // No FW update in progress, just wait for a command - EventCfu::Request(self.cfu_device.wait_request().await) - } - FwUpdateState::InProgress(_) => { - match select(self.cfu_device.wait_request(), self.fw_update_ticker.next()).await { - Either::First(command) => EventCfu::Request(command), - Either::Second(_) => { - debug!("FW update ticker ticked"); - EventCfu::RecoveryTick - } - } - } - FwUpdateState::Recovery => { - // Recovery state, wait for the next attempt to recover the device - self.fw_update_ticker.next().await; - debug!("FW update ticker ticked"); - EventCfu::RecoveryTick - } - } - } - - /// Reset the firmware update ticker - pub fn reset_ticker(&mut self) { - self.fw_update_ticker.reset(); - } -} - /// Struct to receive sink ready timeout events. pub struct SinkReadyTimeoutEvent { timeouts: [Option; N], @@ -221,8 +169,6 @@ pub struct ArrayPortEventReceivers<'device, const N: usize, PortInterrupts: Inte pub port_events: PortEventReceiver, /// Power proxy event receiver pub power_proxies: ArrayPowerProxyEventReceiver<'device, N>, - /// CFU event receiver - pub cfu_event_receiver: CfuEventReceiver, /// Sink ready timeout event receiver pub sink_ready_timeout: SinkReadyTimeoutEvent, } @@ -231,15 +177,10 @@ impl<'device, const N: usize, PortInterrupts: InterruptReceiver> ArrayPortEventReceivers<'device, N, PortInterrupts> { /// Create a new instance - pub fn new( - port_interrupts: PortInterrupts, - power_proxies: [PowerProxyReceiver<'device>; N], - cfu_device: &'static cfu_service::component::CfuDevice, - ) -> Self { + pub fn new(port_interrupts: PortInterrupts, power_proxies: [PowerProxyReceiver<'device>; N]) -> Self { Self { port_events: PortEventReceiver::new(port_interrupts), power_proxies: ArrayPowerProxyEventReceiver::new(power_proxies), - cfu_event_receiver: CfuEventReceiver::new(cfu_device), sink_ready_timeout: SinkReadyTimeoutEvent::new(), } } @@ -248,18 +189,16 @@ impl<'device, const N: usize, PortInterrupts: InterruptReceiver> /// /// Returns the local port ID and the event bitfield. pub async fn wait_event(&mut self) -> Event { - match select4( + match select3( self.port_events.wait_next(), self.power_proxies.wait_next(), - self.cfu_event_receiver.wait_next(), self.sink_ready_timeout.wait_next(), ) .await { - Either4::First(event) => Event::PortEvent(event), - Either4::Second(command) => Event::PowerPolicyCommand(command), - Either4::Third(cfu_event) => Event::CfuEvent(cfu_event), - Either4::Fourth(port) => { + Either3::First(event) => Event::PortEvent(event), + Either3::Second(command) => Event::PowerPolicyCommand(command), + Either3::Third(port) => { let mut status_event = PortStatusEventBitfield::none(); status_event.set_sink_ready(true); Event::PortEvent(LocalPortEvent { diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs index 2c6abba00..f0c8152b7 100644 --- a/type-c-service/src/wrapper/message.rs +++ b/type-c-service/src/wrapper/message.rs @@ -24,26 +24,12 @@ pub struct PowerPolicyCommand { pub request: power_policy_interface::psu::CommandData, } -/// CFU events -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum EventCfu { - /// CFU request - Request(cfu_service::component::RequestData), - /// Recovery tick - /// - /// Occurs when the FW update has timed out to abort the update and return hardware to its normal state - RecoveryTick, -} - /// Wrapper events pub enum Event { /// Port status changed PortEvent(LocalPortEvent), /// Power policy command received PowerPolicyCommand(PowerPolicyCommand), - /// Cfu event - CfuEvent(EventCfu), } /// Port status changed output data @@ -115,10 +101,6 @@ pub enum Output { Vdm(vdm::Output), /// Power policy command received PowerPolicyCommand(OutputPowerPolicyCommand), - /// CFU recovery tick - CfuRecovery, - /// CFU response - CfuResponse(cfu_service::component::InternalResponseData), /// Dp status update DpStatusUpdate(OutputDpStatusChanged), } diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs index 97daf286e..9a757059d 100644 --- a/type-c-service/src/wrapper/mod.rs +++ b/type-c-service/src/wrapper/mod.rs @@ -3,7 +3,6 @@ //! # Supported service messaging //! This struct currently supports messages from the following services: //! * Type-C: [`type_c_interface::port::Command`] -//! * CFU: [`cfu_service::Request`] //! # Event loop //! This struct follows a standard process/finalize event loop. //! @@ -15,12 +14,10 @@ use core::ops::DerefMut; use crate::wrapper::backing::PortState; -use crate::wrapper::event_receiver::{ArrayPowerProxyEventReceiver, CfuEventReceiver, SinkReadyTimeoutEvent}; -use cfu_service::CfuClient; +use crate::wrapper::event_receiver::{ArrayPowerProxyEventReceiver, SinkReadyTimeoutEvent}; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::signal::Signal; use embassy_time::Instant; -use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; use embedded_services::event; use embedded_services::sync::Lockable; use embedded_services::{error, info, trace}; @@ -32,7 +29,6 @@ use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventD use crate::wrapper::message::*; pub mod backing; -mod cfu; pub mod config; mod dp; pub mod event_receiver; @@ -45,20 +41,6 @@ mod vdm; use type_c_interface::port::event::PortStatusEventBitfield; use type_c_interface::port::{Controller, PortStatus}; -/// Base interval for checking for FW update timeouts and recovery attempts -pub const DEFAULT_FW_UPDATE_TICK_INTERVAL_MS: u64 = 5000; -/// Default number of ticks before we consider a firmware update to have timed out -/// 300 seconds at 5 seconds per tick -pub const DEFAULT_FW_UPDATE_TIMEOUT_TICKS: u8 = 60; - -/// Trait for validating firmware versions before applying an update -// TODO: remove this once we have a better framework for OEM customization -// See https://github.com/OpenDevicePartnership/embedded-services/issues/326 -pub trait FwOfferValidator { - /// Determine if we are accepting the firmware update offer, returns a CFU offer response - fn validate(&self, current: FwVersion, offer: &FwUpdateOffer) -> FwUpdateOfferResponse; -} - /// Maximum number of supported ports pub const MAX_SUPPORTED_PORTS: usize = 2; @@ -68,13 +50,10 @@ pub struct ControllerWrapper< M: RawMutex, D: Lockable, S: event::Sender, - V: FwOfferValidator, > where D::Inner: Controller, { controller: &'device D, - /// Trait object for validating firmware versions - fw_version_validator: V, /// Registration information for services pub registration: backing::Registration<'device, M>, /// SW port status event signal @@ -85,13 +64,8 @@ pub struct ControllerWrapper< pub ports: &'device [backing::Port<'device, M, S>], } -impl< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, - V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, V> +impl<'device, M: RawMutex, D: Lockable, S: event::Sender> + ControllerWrapper<'device, M, D, S> where D::Inner: Controller, { @@ -100,7 +74,6 @@ where controller: &'device D, config: config::Config, storage: &'device backing::ReferencedStorage<'device, N, M, S>, - fw_version_validator: V, ) -> Self { const { assert!(N > 0 && N <= MAX_SUPPORTED_PORTS, "Invalid number of ports"); @@ -110,7 +83,6 @@ where Self { controller, config, - fw_version_validator, registration: backing.registration, sw_status_event: Signal::new(), ports: backing.ports, @@ -342,7 +314,6 @@ where pub async fn process_event( &self, sink_ready_timeout: &mut SinkReadyTimeoutEvent, - cfu_event_receiver: &mut CfuEventReceiver, event: Event, ) -> Result::BusError>> { let mut controller = self.controller.lock().await; @@ -352,24 +323,9 @@ where .await } Event::PowerPolicyCommand(PowerPolicyCommand { port, request }) => { - let response = self - .process_power_command(cfu_event_receiver, &mut controller, port, &request) - .await; + let response = self.process_power_command(&mut controller, port, &request).await; Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) } - Event::CfuEvent(event) => match event { - EventCfu::Request(request) => { - let response = self - .process_cfu_command(cfu_event_receiver, &mut controller, &request) - .await; - Ok(Output::CfuResponse(response)) - } - EventCfu::RecoveryTick => { - // FW Update tick, process timeouts and recovery attempts - self.process_cfu_tick(cfu_event_receiver, &mut controller).await; - Ok(Output::CfuRecovery) - } - }, } } @@ -395,14 +351,6 @@ where .map_err(|_| Error::Pd(PdError::Failed))?; Ok(()) } - Output::CfuRecovery => { - // Nothing to do here - Ok(()) - } - Output::CfuResponse(response) => { - self.send_cfu_response(response).await; - Ok(()) - } Output::DpStatusUpdate(_) => { // Nothing to do here Ok(()) @@ -414,18 +362,15 @@ where pub async fn process_and_finalize_event( &self, sink_ready_timeout: &mut SinkReadyTimeoutEvent, - cfu_event_receiver: &mut CfuEventReceiver, power_event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, event: Event, ) -> Result<(), Error<::BusError>> { - let output = self - .process_event(sink_ready_timeout, cfu_event_receiver, event) - .await?; + let output = self.process_event(sink_ready_timeout, event).await?; self.finalize(power_event_receiver, output).await } /// Register all devices with their respective services - pub fn register(&'static self, cfu_client: &CfuClient) -> Result<(), Error<::BusError>> { + pub fn register(&'static self) -> Result<(), Error<::BusError>> { self.registration .context .register_controller(self.registration.pd_controller) @@ -436,26 +381,12 @@ where ); Error::Pd(PdError::Failed) })?; - - //TODO: Remove when we have a more general framework in place - cfu_client.register_device(self.registration.cfu_device).map_err(|_| { - error!( - "Controller{}: Failed to register CFU device", - self.registration.pd_controller.id().0 - ); - Error::Pd(PdError::Failed) - })?; Ok(()) } } -impl< - 'device, - M: RawMutex, - C: Lockable, - S: event::Sender, - V: FwOfferValidator, -> Lockable for ControllerWrapper<'device, M, C, S, V> +impl<'device, M: RawMutex, C: Lockable, S: event::Sender> Lockable + for ControllerWrapper<'device, M, C, S> where ::Inner: Controller, { diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs index 70dfeaf88..641608cd2 100644 --- a/type-c-service/src/wrapper/pd.rs +++ b/type-c-service/src/wrapper/pd.rs @@ -5,13 +5,8 @@ use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS} use super::*; -impl< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, - V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, V> +impl<'device, M: RawMutex, D: Lockable, S: event::Sender> + ControllerWrapper<'device, M, D, S> where D::Inner: Controller, { diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs index 8e2022fab..b52b7942c 100644 --- a/type-c-service/src/wrapper/power.rs +++ b/type-c-service/src/wrapper/power.rs @@ -1,21 +1,14 @@ //! Module contain power-policy related message handling -use embedded_services::debug; - +use crate::util::power_policy_error_from_pd_bus_error; use crate::wrapper::config::UnconstrainedSink; use power_policy_interface::capability::{ConsumerPowerCapability, ProviderPowerCapability, PsuType}; use power_policy_interface::psu::CommandData as PowerCommand; -use power_policy_interface::psu::Error as PowerError; use power_policy_interface::psu::{CommandData, InternalResponseData, ResponseData}; use super::*; -impl< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, - V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, V> +impl<'device, M: RawMutex, D: Lockable, S: event::Sender> + ControllerWrapper<'device, M, D, S> where D::Inner: Controller, { @@ -96,16 +89,11 @@ where /// Returns no error because this is a top-level function pub(super) async fn process_power_command( &self, - cfu_event_receiver: &mut CfuEventReceiver, controller: &mut D::Inner, port: LocalPortId, command: &CommandData, ) -> InternalResponseData { trace!("Processing power command: device{} {:#?}", port.0, command); - if cfu_event_receiver.fw_update_state.in_progress() { - debug!("Port{}: Firmware update in progress", port.0); - return Err(PowerError::Busy); - } match command { PowerCommand::ConnectAsConsumer(capability) => { @@ -113,22 +101,23 @@ where "Port{}: Connect as consumer: {:?}, enable input switch", port.0, capability ); - if controller.enable_sink_path(port, true).await.is_err() { + controller.enable_sink_path(port, true).await.map_err(|e| { error!("Error enabling sink path"); - return Err(PowerError::Failed); - } + power_policy_error_from_pd_bus_error(e) + })?; } PowerCommand::ConnectAsProvider(capability) => { - if self.process_connect_as_provider(port, *capability, controller).is_err() { - error!("Error processing connect provider"); - return Err(PowerError::Failed); - } + self.process_connect_as_provider(port, *capability, controller) + .map_err(|e| { + error!("Error processing connect provider"); + power_policy_error_from_pd_bus_error(e) + })?; } PowerCommand::Disconnect => { - if self.process_disconnect(port, controller).await.is_err() { + self.process_disconnect(port, controller).await.map_err(|e| { error!("Error processing disconnect"); - return Err(PowerError::Failed); - } + power_policy_error_from_pd_bus_error(e) + })?; } } diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs index 5cedbf044..2f6baebfa 100644 --- a/type-c-service/src/wrapper/vdm.rs +++ b/type-c-service/src/wrapper/vdm.rs @@ -6,15 +6,10 @@ use type_c_interface::port::event::VdmNotification; use type_c_interface::port::{Controller, event::VdmData}; use type_c_interface::service::event::{PortEvent, PortEventData}; -use super::{ControllerWrapper, FwOfferValidator, message::vdm::Output}; +use super::{ControllerWrapper, message::vdm::Output}; -impl< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, - V: FwOfferValidator, -> ControllerWrapper<'device, M, D, S, V> +impl<'device, M: RawMutex, D: Lockable, S: event::Sender> + ControllerWrapper<'device, M, D, S> where D::Inner: Controller, { From 1b4ed11d31429c7eeff1673791f0f1cd2e59d28e Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Wed, 6 May 2026 10:45:54 -0700 Subject: [PATCH 73/79] Add mctp-rs as in-tree workspace member (port of dymk/mctp-rs @ 3d941ba) (#823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Imports `dymk/mctp-rs` as an in-tree workspace member at `mctp-rs/` and flips the existing `[workspace.dependencies]` git pin to a path dep. The imported source is at SHA `3d941ba5205ca7781bf37e3dc7c5dfdc99a082d6`, which matches the pre-edit `Cargo.lock` pin exactly — so all four `mctp-rs` consumers (`uart-service`, `espi-service`, `embedded-service`, plus its macro re-exports) compile and test unchanged. ## What changed - New `mctp-rs/` directory: snapshot copy of `dymk/mctp-rs` `main @ 3d941ba` source. No git history preserved. - Root `Cargo.toml`: `"mctp-rs"` appended to `[workspace] members`; `mctp-rs = { git = "..." }` replaced with `mctp-rs = { path = "./mctp-rs" }`. - `Cargo.lock` regenerated. - `mctp-rs/Cargo.toml`: dropped `cargo-husky` from `[dev-dependencies]` (its build script searches for `.cargo-husky/hooks/` at the workspace root, which is now this repo, not the crate). - `mctp-rs/rustfmt.toml` removed; source reformatted under this repo's `rustfmt.toml` (`max_width = 120`). - Excluded from the copy: `.cargo-husky/`, `.github/`, `.vscode/`, source `Cargo.lock`, source `.gitignore`. A follow-up PR will add the dedicated `mctp-rs` CI workflow. ## Why `mctp-rs` lived in a personal fork and was already consumed here via a git pin. Bringing it in-tree as a sibling of `uart-service` etc. removes a layer of cross-repo indirection and lets future changes flow through normal workspace PR review. SHA `3d941ba` was chosen specifically because it matches the consumer pin — so this PR has zero behavioral surface. The mctp-rs repo holds further commits beyond `3d941ba` that change the `MctpMedium` API. Those, together with the corresponding consumer updates and the eventual archival of the personal fork, land in a separate PR after this one. ## Verifying locally ```bash git fetch origin dymk/phase-31-mctp-rs-in-tree git switch dymk/phase-31-mctp-rs-in-tree cargo build --workspace cargo test --workspace # 178 pass / 0 fail cargo fmt --all --check # clean cargo clippy --workspace --all-targets -- -D warnings # clean ``` Spot-check the source is byte-identical to upstream: ```bash git -C show 3d941ba:src/lib.rs | diff - mctp-rs/src/lib.rs git -C show 3d941ba:src/medium/smbus_espi.rs | diff - mctp-rs/src/medium/smbus_espi.rs ``` (Both empty diffs, modulo the rustfmt-style reflow noted above for the second one.) ## Notes for reviewer - `cargo test --workspace` count went from 128 → 178. The 50-test delta is `mctp-rs`'s own test suite, which now runs as part of the workspace. Every consumer test result line from before is preserved exactly. - `Cargo.lock` picks up `mctp-rs`'s dev-dep chain (`rstest`, `tokio`, `pretty_assertions`, etc.) for the same reason — these are dev-only and don't affect runtime. The two production-tree changes are `hashbrown 0.15.5 → 0.17.0` and `indexmap 2.11.0 → 2.14.0`; both are pulled in only by the `partition-manager-generation` build script (via `toml`), so no runtime impact. - The PR is intentionally split across four atomic commits: source import, workspace bootstrap, cargo-husky drop, rustfmt.toml drop + reformat. Each is reviewable in isolation. --- Cargo.lock | 194 +++- Cargo.toml | 3 +- examples/pico-de-gallo/Cargo.lock | 3 +- examples/rt685s-evk/Cargo.lock | 1 - examples/std/Cargo.lock | 1 - mctp-rs/Cargo.toml | 29 + mctp-rs/LICENSE.md | 7 + mctp-rs/README.md | 40 + mctp-rs/src/deserialize.rs | 49 ++ mctp-rs/src/endpoint_id.rs | 45 + mctp-rs/src/error.rs | 41 + mctp-rs/src/lib.rs | 718 +++++++++++++++ mctp-rs/src/mctp_command_code.rs | 52 ++ mctp-rs/src/mctp_completion_code.rs | 94 ++ mctp-rs/src/mctp_message_tag.rs | 31 + mctp-rs/src/mctp_packet_context.rs | 186 ++++ mctp-rs/src/mctp_sequence_number.rs | 42 + mctp-rs/src/mctp_transport_header.rs | 51 ++ mctp-rs/src/medium/mod.rs | 37 + mctp-rs/src/medium/smbus_espi.rs | 826 ++++++++++++++++++ mctp-rs/src/medium/util.rs | 37 + mctp-rs/src/message_type/mctp_control.rs | 227 +++++ mctp-rs/src/message_type/mod.rs | 22 + .../src/message_type/vendor_defined_pci.rs | 48 + mctp-rs/src/serialize.rs | 84 ++ mctp-rs/src/test_util.rs | 97 ++ supply-chain/config.toml | 64 ++ 27 files changed, 3013 insertions(+), 16 deletions(-) create mode 100644 mctp-rs/Cargo.toml create mode 100644 mctp-rs/LICENSE.md create mode 100644 mctp-rs/README.md create mode 100644 mctp-rs/src/deserialize.rs create mode 100644 mctp-rs/src/endpoint_id.rs create mode 100644 mctp-rs/src/error.rs create mode 100644 mctp-rs/src/lib.rs create mode 100644 mctp-rs/src/mctp_command_code.rs create mode 100644 mctp-rs/src/mctp_completion_code.rs create mode 100644 mctp-rs/src/mctp_message_tag.rs create mode 100644 mctp-rs/src/mctp_packet_context.rs create mode 100644 mctp-rs/src/mctp_sequence_number.rs create mode 100644 mctp-rs/src/mctp_transport_header.rs create mode 100644 mctp-rs/src/medium/mod.rs create mode 100644 mctp-rs/src/medium/smbus_espi.rs create mode 100644 mctp-rs/src/medium/util.rs create mode 100644 mctp-rs/src/message_type/mctp_control.rs create mode 100644 mctp-rs/src/message_type/mod.rs create mode 100644 mctp-rs/src/message_type/vendor_defined_pci.rs create mode 100644 mctp-rs/src/serialize.rs create mode 100644 mctp-rs/src/test_util.rs diff --git a/Cargo.lock b/Cargo.lock index 6d5c731f3..e9877c6e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,7 +153,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version", + "rustc_version 0.2.3", ] [[package]] @@ -504,6 +504,12 @@ dependencies = [ "embedded-io-async 0.6.1", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "document-features" version = "0.2.11" @@ -941,12 +947,49 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fw-update-interface" version = "0.1.0" @@ -986,6 +1029,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "half" version = "2.6.0" @@ -1007,9 +1056,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heapless" @@ -1071,9 +1120,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -1251,15 +1300,18 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#3d941ba5205ca7781bf37e3dc7c5dfdc99a082d6" dependencies = [ "bit-register", "defmt 0.3.100", "embedded-batteries", "espi-device", "num_enum", + "pretty_assertions", + "rstest", "smbus-pec", "thiserror", + "tokio", + "uuid", ] [[package]] @@ -1485,6 +1537,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "platform-service" version = "0.1.0" @@ -1557,6 +1615,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1656,6 +1733,41 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.1", + "syn 2.0.106", + "unicode-ident", +] + [[package]] name = "rtt-target" version = "0.6.1" @@ -1679,7 +1791,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", ] [[package]] @@ -1703,6 +1824,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "semver-parser" version = "0.7.0" @@ -1989,8 +2116,8 @@ dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -2002,6 +2129,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -2011,8 +2147,29 @@ dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime", - "winnow", + "toml_datetime 0.6.11", + "winnow 0.7.13", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.2", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.2", ] [[package]] @@ -2452,6 +2609,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index f3fd5d7c5..56845b038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "type-c-interface", "fw-update-interface", "fw-update-interface-mocks", + "mctp-rs", ] exclude = ["examples/*"] @@ -95,7 +96,7 @@ embedded-storage-async = "0.4.1" embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb-pd", default-features = false } fw-update-interface = { path = "./fw-update-interface" } fw-update-interface-mocks = { path = "./fw-update-interface-mocks" } -mctp-rs = { git = "https://github.com/dymk/mctp-rs" } +mctp-rs = { path = "./mctp-rs" } num_enum = { version = "0.7.5", default-features = false } portable-atomic = { version = "1.11", default-features = false } power-policy-interface = { path = "./power-policy-interface" } diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 270a48222..8ac8715f9 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -619,7 +619,7 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#9805f13c044b0e314d415410c57a8a59a40eabeb" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#0d21aa34fd8691c6544533e6c72fe41824dc2fa8" dependencies = [ "bit-register", "bitflags", @@ -896,7 +896,6 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#f3121512468e4776c4b1d2d648b54c7271b97bd9" dependencies = [ "bit-register", "espi-device", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index bd92534a0..34391866e 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -904,7 +904,6 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#3d941ba5205ca7781bf37e3dc7c5dfdc99a082d6" dependencies = [ "bit-register", "defmt 0.3.100", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index c28030e2b..65e837717 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -904,7 +904,6 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/dymk/mctp-rs#3d941ba5205ca7781bf37e3dc7c5dfdc99a082d6" dependencies = [ "bit-register", "espi-device", diff --git a/mctp-rs/Cargo.toml b/mctp-rs/Cargo.toml new file mode 100644 index 000000000..7e5d382b7 --- /dev/null +++ b/mctp-rs/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "mctp-rs" +version = "0.1.0" +edition = "2024" + +[package.metadata.cargo-machete] +# Optional deps gated by features — cargo-machete sees them as unused at +# default features but they ARE consumed when the relevant feature is on. +ignored = ["embedded-batteries", "espi-device", "uuid"] + +[features] +default = [] +espi = ["dep:espi-device"] +defmt = ["dep:defmt", "embedded-batteries/defmt"] + +[dependencies] +espi-device = { git = "https://github.com/OpenDevicePartnership/haf-ec-service", optional = true } +bit-register = { git = "https://github.com/OpenDevicePartnership/odp-utilities", package = "bit-register" } +num_enum = { version = "0.7.4", default-features = false } +smbus-pec = "1.0.1" +thiserror = { version = "2.0.16", default-features = false } +uuid = { version = "=1.17.0", default-features = false, optional = true } +embedded-batteries = { version = "0.3", features = ["defmt"], optional = true } +defmt = { version = "0.3", optional = true } + +[dev-dependencies] +pretty_assertions = "1.4.1" +tokio = { version = "1.0", features = ["macros", "rt"] } +rstest = "0.26.1" diff --git a/mctp-rs/LICENSE.md b/mctp-rs/LICENSE.md new file mode 100644 index 000000000..d4fa98b78 --- /dev/null +++ b/mctp-rs/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2025 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/mctp-rs/README.md b/mctp-rs/README.md new file mode 100644 index 000000000..1abf79165 --- /dev/null +++ b/mctp-rs/README.md @@ -0,0 +1,40 @@ +# mctp-rs + +A `no_std` Rust implementation of the Management Component Transport Protocol (MCTP) as defined in the [DMTF DSP0236 specification](https://www.dmtf.org/sites/default/files/standards/documents/DSP0236_1.3.3.pdf). + +## Overview + +MCTP is a communication protocol designed for platform management subsystems in computer systems. It facilitates communication between management controllers (like BMCs) and managed devices across various bus types. This library provides: + +- **Protocol Implementation**: Complete MCTP transport layer with packet assembly/disassembly +- **Medium Abstraction**: Support for different physical transport layers (SMBus/eSPI included) +- **No-std Compatible**: Suitable for embedded and resource-constrained environments + +## Features + +- `espi` - Enables eSPI device support via the `espi-device` crate + +## Documentation & Usage + +See the crate documentation for up-to-date usage and examples: [Rendered Docs](https://dymk.github.io/mctp-rs/) + +## Architecture + +The library is structured around: + +- **`MctpPacketContext`**: Main entry point for handling MCTP packets +- **`MctpMedium`**: Trait for implementing transport-specific packet handling +- **`MctpMessage`**: Represents a complete MCTP message with reply context +- **Control Commands**: Type-safe implementation of MCTP control protocol + + +## License + +MIT License - see [LICENSE.md](LICENSE.md) for details. + +## Contributing + +1. Ensure `cargo check` and `cargo test` pass +2. Test with all feature combinations using `cargo hack --feature-powerset check` +3. Maintain `no_std` compatibility +4. Follow the existing code patterns for protocol message handling diff --git a/mctp-rs/src/deserialize.rs b/mctp-rs/src/deserialize.rs new file mode 100644 index 000000000..0a0ddd630 --- /dev/null +++ b/mctp-rs/src/deserialize.rs @@ -0,0 +1,49 @@ +use crate::{ + MctpMessageBuffer, MctpPacketError, error::MctpPacketResult, mctp_transport_header::MctpTransportHeader, + medium::MctpMedium, +}; + +pub(crate) fn parse_transport_header( + packet: &[u8], +) -> MctpPacketResult<(MctpTransportHeader, &[u8]), M> { + if packet.len() < 4 { + return Err(MctpPacketError::HeaderParseError( + "Packet is too small, cannot parse transport header", + )); + } + let transport_header_value = u32::from_be_bytes( + packet[0..4] + .try_into() + .map_err(|_| MctpPacketError::HeaderParseError("Packet is too small, cannot parse transport header"))?, + ); + let transport_header = MctpTransportHeader::try_from(transport_header_value) + .map_err(|_| MctpPacketError::HeaderParseError("Invalid transport header"))?; + let packet = &packet[4..]; + Ok((transport_header, packet)) +} + +pub(crate) fn parse_message_body( + packet: &[u8], +) -> MctpPacketResult<(MctpMessageBuffer<'_>, Option), M> { + // first four bytes are the message header, parse with MctpMessageHeader + // to figure out the type, then based on that, parse the type specific header + if packet.is_empty() { + return Err(MctpPacketError::HeaderParseError( + "packet too small to extract message type from header", + )); + } + + let integrity_check = packet[0] & 0b1000_0000; + let message_type = packet[0] & 0b0111_1111; + let packet = &packet[1..]; + + // TODO - compute message integrity check if header.integrity_check is set + Ok(( + MctpMessageBuffer { + integrity_check, + message_type, + rest: packet, + }, + None, + )) +} diff --git a/mctp-rs/src/endpoint_id.rs b/mctp-rs/src/endpoint_id.rs new file mode 100644 index 000000000..03c2f6e58 --- /dev/null +++ b/mctp-rs/src/endpoint_id.rs @@ -0,0 +1,45 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointId { + /// 0x00 + #[default] + Null, + /// 0xFF + Broadcast, + /// 0x08 - 0x7F + Id(u8), +} + +impl TryFrom for EndpointId { + type Error = &'static str; + fn try_from(value: u8) -> Result { + Self::try_from_bits(value as u32) + } +} + +impl TryFromBits for EndpointId { + fn try_from_bits(bits: u32) -> Result { + match bits { + 0x00 => Ok(EndpointId::Null), + 0xFF => Ok(EndpointId::Broadcast), + 0x08..=0xFE => Ok(EndpointId::Id(bits as u8)), + _ => Err("Invalid endpoint ID"), + } + } +} + +impl TryIntoBits for EndpointId { + fn try_into_bits(self) -> Result { + match self { + EndpointId::Null => Ok(0x00), + EndpointId::Broadcast => Ok(0xFF), + EndpointId::Id(id) => Ok(id as u32), + } + } +} + +impl NumBytes for EndpointId { + const NUM_BYTES: usize = 1; +} diff --git a/mctp-rs/src/error.rs b/mctp-rs/src/error.rs new file mode 100644 index 000000000..c2dc147b3 --- /dev/null +++ b/mctp-rs/src/error.rs @@ -0,0 +1,41 @@ +use crate::{ + endpoint_id::EndpointId, mctp_completion_code::MctpCompletionCode, mctp_message_tag::MctpMessageTag, + mctp_sequence_number::MctpSequenceNumber, medium::MctpMedium, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ProtocolError { + #[error("Expected start of message")] + ExpectedStartOfMessage, + #[error("Unexpected start of message")] + UnexpectedStartOfMessage, + #[error("Message tag mismatch")] + MessageTagMismatch(MctpMessageTag, MctpMessageTag), + #[error("Tag owner mismatch")] + TagOwnerMismatch(u8, u8), + #[error("Source endpoint id mismatch")] + SourceEndpointIdMismatch(EndpointId, EndpointId), + #[error("Unexpected packet sequence number")] + UnexpectedPacketSequenceNumber(MctpSequenceNumber, MctpSequenceNumber), + #[error("Received non-success completion code on request message")] + CompletionCodeOnRequestMessage(MctpCompletionCode), + #[error("Cannot send message while assembling")] + SendMessageWhileAssembling, + #[error("Cannot send message while receiving")] + SendingMessageWhileReceiving, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MctpPacketError { + HeaderParseError(&'static str), + CommandParseError(&'static str), + SerializeError(&'static str), + UnsupportedMessageType(u8), + ProtocolError(#[from] ProtocolError), + MediumError(M::Error), +} + +// TODO - MctpPacketResult type alias +pub type MctpPacketResult = Result>; diff --git a/mctp-rs/src/lib.rs b/mctp-rs/src/lib.rs new file mode 100644 index 000000000..718fc22ec --- /dev/null +++ b/mctp-rs/src/lib.rs @@ -0,0 +1,718 @@ +#![no_std] +#![allow(dead_code)] +// extern crate std; +//! # mctp-rs +//! +//! A `no_std` Rust implementation of the [DMTF Management Component Transport Protocol (MCTP)](https://www.dmtf.org/sites/default/files/standards/documents/DSP0236_1.3.3.pdf) +//! transport. +//! +//! ## Receiving and parsing messages +//! +//! Use `MctpPacketContext` with your medium to assemble messages from packets. +//! +//! ```rust,no_run +//! # use mctp_rs::*; +//! # #[derive(Debug, Clone, Copy)] struct MyMedium { mtu: usize } +//! # #[derive(Debug, Clone, Copy)] struct MyMediumFrame { packet_size: usize } +//! # impl MctpMedium for MyMedium { type Frame=MyMediumFrame; type Error=&'static str; type ReplyContext=(); +//! # fn max_message_body_size(&self)->usize{self.mtu} +//! # fn deserialize<'b>(&self,p:&'b [u8])->MctpPacketResult<(Self::Frame,&'b [u8]),Self>{Ok((MyMediumFrame{packet_size:p.len()},p))} +//! # fn serialize<'b,F>(&self,_:Self::ReplyContext,b:&'b mut [u8],w:F)->MctpPacketResult<&'b [u8],Self> where F: for<'a> FnOnce(&'a mut [u8])->MctpPacketResult{let n=w(b)?;Ok(&b[..n])}} +//! # impl MctpMediumFrame for MyMediumFrame { fn packet_size(&self)->usize{self.packet_size} fn reply_context(&self)->(){()}} +//! let mut assembly_buffer = [0u8; 1024]; +//! let medium = MyMedium { mtu: 256 }; +//! let mut context = MctpPacketContext::new(medium, &mut assembly_buffer); +//! +//! // Typically obtained from your bus +//! let raw_packet_data: &[u8] = &[0x01, 0x02, 0x03, 0x83]; +//! +//! match context.deserialize_packet(raw_packet_data) { +//! Ok(Some(message)) => { +//! // We received a complete MCTP message +//! if let Ok((header, control)) = message.parse_as::() { +//! match control { +//! MctpControl::GetEndpointIdRequest => { +//! // Handle control request +//! let _instance = header.instance_id; +//! } +//! MctpControl::GetEndpointIdResponse(bytes3) => { +//! // Use response payload (3 bytes as per spec) +//! let _eid = bytes3[0]; +//! } +//! _ => {} +//! } +//! } +//! } +//! Ok(None) => { /* partial message; wait for more packets */ } +//! Err(e) => { +//! // handle protocol/medium error +//! let _ = e; +//! } +//! } +//! ``` +//! ## Sending messages +//! +//! Construct a header + body pair implementing `MctpMessageTrait` and serialize to one or +//! more packets using `serialize_packet`. +//! +//! ```rust,no_run +//! # use mctp_rs::*; +//! # #[derive(Debug, Clone, Copy)] struct MyMedium { mtu: usize } +//! # #[derive(Debug, Clone, Copy)] struct MyMediumFrame { packet_size: usize } +//! # impl MctpMedium for MyMedium { type Frame=MyMediumFrame; type Error=&'static str; type ReplyContext=(); fn max_message_body_size(&self)->usize{self.mtu} +//! # fn deserialize<'b>(&self,p:&'b [u8])->MctpPacketResult<(Self::Frame,&'b [u8]),Self>{Ok((MyMediumFrame{packet_size:p.len()},p))} +//! # fn serialize<'b,F>(&self,_:Self::ReplyContext,b:&'b mut [u8],w:F)->MctpPacketResult<&'b [u8],Self> where F: for<'a> FnOnce(&'a mut [u8])->MctpPacketResult{let n=w(b)?;Ok(&b[..n])}} +//! # impl MctpMediumFrame for MyMediumFrame { fn packet_size(&self)->usize{self.packet_size} fn reply_context(&self)->(){()}} +//! let mut buf = [0u8; 1024]; +//! let mut ctx = MctpPacketContext::new(MyMedium { mtu: 64 }, &mut buf); +//! +//! let reply = MctpReplyContext { +//! destination_endpoint_id: EndpointId::try_from(0x20).unwrap(), +//! source_endpoint_id: EndpointId::try_from(0x21).unwrap(), +//! packet_sequence_number: MctpSequenceNumber::new(0), +//! message_tag: MctpMessageTag::try_from(1).unwrap(), +//! medium_context: (), +//! }; +//! +//! let message = ( +//! VendorDefinedPciHeader(0x1234), +//! VendorDefinedPci(&[0xDE, 0xAD, 0xBE, 0xEF]), +//! ); +//! +//! let mut packets = ctx.serialize_packet(reply, message).unwrap(); +//! while let Some(packet_result) = packets.next() { +//! let packet_bytes = packet_result.unwrap(); +//! // send `packet_bytes` via your bus +//! let _ = packet_bytes; +//! } +//! ``` +//! +//! ## Implementing a custom medium +//! +//! The crate is transport-agnostic via the `MctpMedium` trait. Implement it for your bus +//! (e.g., SMBus, eSPI) and provide a frame type implementing `MctpMediumFrame`. +//! +//! ```rust,no_run +//! use mctp_rs::*; +//! +//! #[derive(Debug, Clone, Copy)] +//! struct MyMedium { +//! mtu: usize, +//! } +//! +//! #[derive(Debug, Clone, Copy)] +//! struct MyMediumFrame { +//! packet_size: usize, +//! } +//! +//! impl MctpMedium for MyMedium { +//! type Frame = MyMediumFrame; +//! type Error = &'static str; +//! type ReplyContext = (); +//! +//! fn max_message_body_size(&self) -> usize { +//! self.mtu +//! } +//! +//! fn deserialize<'buf>( +//! &self, +//! packet: &'buf [u8], +//! ) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { +//! // Strip/validate transport headers as needed for your bus and return MCTP payload slice +//! Ok(( +//! MyMediumFrame { +//! packet_size: packet.len(), +//! }, +//! packet, +//! )) +//! } +//! +//! fn serialize<'buf, F>( +//! &self, +//! _reply_context: Self::ReplyContext, +//! buffer: &'buf mut [u8], +//! message_writer: F, +//! ) -> MctpPacketResult<&'buf [u8], Self> +//! where +//! F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, +//! { +//! // Prepend transport headers as needed, then ask the writer to write MCTP payload +//! let message_len = message_writer(buffer)?; +//! Ok(&buffer[..message_len]) +//! } +//! } +//! +//! impl MctpMediumFrame for MyMediumFrame { +//! fn packet_size(&self) -> usize { +//! self.packet_size +//! } +//! fn reply_context(&self) -> ::ReplyContext { +//! () +//! } +//! } +//! ``` + +mod deserialize; +mod endpoint_id; +pub mod error; +mod mctp_command_code; +pub mod mctp_completion_code; +mod mctp_message_tag; +mod mctp_packet_context; +mod mctp_sequence_number; +mod mctp_transport_header; +mod medium; +mod message_type; +mod serialize; +#[cfg(test)] +mod test_util; + +pub use endpoint_id::EndpointId; +pub use error::{MctpPacketError, MctpPacketResult}; +pub use mctp_message_tag::MctpMessageTag; +pub use mctp_packet_context::{MctpPacketContext, MctpReplyContext}; +pub use mctp_sequence_number::MctpSequenceNumber; +pub use medium::*; +pub use message_type::*; + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpMessage<'buffer, M: MctpMedium> { + pub reply_context: MctpReplyContext, + pub message_buffer: MctpMessageBuffer<'buffer>, + pub message_integrity_check: Option, +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpMessageBuffer<'buffer> { + integrity_check: u8, + message_type: u8, + rest: &'buffer [u8], +} + +impl<'buffer, M: MctpMedium> MctpMessage<'buffer, M> { + pub fn can_parse_as>(&self) -> bool { + self.message_buffer.message_type == P::MESSAGE_TYPE + } + pub fn parse_as>(&self) -> MctpPacketResult<(P::Header, P), M> { + if !self.can_parse_as::

() { + return Err(MctpPacketError::HeaderParseError("message type mismatch")); + } + let (header, rest) = P::Header::deserialize(self.message_buffer.rest)?; + let message = P::deserialize(&header, rest)?; + Ok((header, message)) + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + use crate::{ + error::ProtocolError, mctp_command_code::MctpControlCommandCode, mctp_packet_context::MctpPacketContext, + test_util::*, + }; + + struct Packet(&'static [u8]); + const GET_ENDPOINT_ID_PACKET_NO_EOM: Packet = Packet(&[ + // test medium frame (header + trailer): 0 bytes + // transport header: + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (22) + 0b1000_0011, // som, eom, seq (0), to, tag (3) + // message header: + 0b0000_0000, // integrity check (off) / message type (MessageType::MctpControl) + 0b0000_0000, // rq, d, rsvd, instance id + 0b0000_0010, // command code (2: get endpoint id) + 0b0000_0000, // completion code + // message body: + 0b0000_1111, // endpoint id (15) + 0b0000_0001, /* endpoint type (simple = 0b00) / endpoint id type (static eid supported = + * 0b01) */ + 0b1111_0000, // medium specific + ]); + + const EMPTY_PACKET_EOM: Packet = Packet(&[ + // transport header: + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (14) + 0b0101_0011, // som, eom, seq (1), to, tag (3) + ]); + + #[test] + fn split_over_two_packets() { + let mut buffer = [0; 1024]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut buffer); + + assert_eq!( + context.deserialize_packet(GET_ENDPOINT_ID_PACKET_NO_EOM.0).unwrap(), + None + ); + + let message = context.deserialize_packet(EMPTY_PACKET_EOM.0).unwrap().unwrap(); + + assert_eq!(message.can_parse_as::(), true); + assert_eq!(message.message_integrity_check, None); + assert_eq!( + message.reply_context, + MctpReplyContext { + destination_endpoint_id: EndpointId::Id(9), + source_endpoint_id: EndpointId::Id(22), + packet_sequence_number: MctpSequenceNumber::new(1), + message_tag: MctpMessageTag::try_from(3).unwrap(), + medium_context: (), + } + ); + assert_eq!( + message.parse_as().unwrap(), + ( + MctpControlHeader { + command_code: MctpControlCommandCode::GetEndpointId, + ..Default::default() + }, + MctpControl::GetEndpointIdResponse([ + 0b0000_1111, // endpoint id (15) + 0b0000_0001, /* endpoint type (simple = 0b00) / endpoint id type (static eid + * supported = 0b01) */ + 0b1111_0000, // medium specific + ]), + ) + ); + } + + #[test] + fn lacking_start_of_message() { + let mut buffer = [0; 1024]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut buffer); + + assert_eq!( + context.deserialize_packet(&[ + // transport header: + 0b0000_0000, // mctp reserved, header version + 0b0000_0000, // destination endpoint id + 0b0000_0000, // source endpoint id + 0b0000_0000, // som, eom, seq (0), to, tag + ]), + Err(MctpPacketError::ProtocolError(ProtocolError::ExpectedStartOfMessage,)) + ); + } + + #[test] + fn repeated_start_of_message() { + let mut buffer = [0; 1024]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut buffer); + + context.deserialize_packet(GET_ENDPOINT_ID_PACKET_NO_EOM.0).unwrap(); + + assert_eq!( + context.deserialize_packet(&[ + // transport header: + 0b0000_0000, // mctp reserved, header version + 0b0000_0000, // destination endpoint id + 0b0000_0000, // source endpoint id + 0b1000_0000, // som, eom, seq (0), to, tag + ]), + Err(MctpPacketError::ProtocolError(ProtocolError::UnexpectedStartOfMessage,)) + ); + } + + #[test] + fn message_tag_mismatch() { + let mut buffer = [0; 1024]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut buffer); + + // message tag = 0 + context.deserialize_packet(GET_ENDPOINT_ID_PACKET_NO_EOM.0).unwrap(); + + // message tag = 1 + assert_eq!( + context.deserialize_packet(&[ + // transport header: + 0b0000_0000, // mctp reserved, header version + 0b0000_0000, // destination endpoint id + 0b0000_0000, // source endpoint id + 0b0101_0010, // som, eom, seq (1), to, tag (2) + ]), + Err(MctpPacketError::ProtocolError(ProtocolError::MessageTagMismatch( + MctpMessageTag::try_from(3).unwrap(), + MctpMessageTag::try_from(2).unwrap(), + ),)) + ); + } + + #[test] + fn test_send_packet() { + let mut buffer = [0; 1024]; + let mut context = + MctpPacketContext::::new(TestMedium::new().with_headers(&[0xA, 0xB], &[0xC, 0xD]), &mut buffer); + + let reply_context = MctpReplyContext { + destination_endpoint_id: EndpointId::try_from(236).unwrap(), + source_endpoint_id: EndpointId::try_from(192).unwrap(), + packet_sequence_number: MctpSequenceNumber::new(1), + message_tag: MctpMessageTag::try_from(3).unwrap(), + medium_context: (), + }; + + let message = (VendorDefinedPciHeader(0x1234), VendorDefinedPci(&[0xA5, 0xB6])); + + let mut state = context.serialize_packet(reply_context, message).unwrap(); + + let packet = state.next().unwrap().unwrap(); + assert_eq!( + &[ + // test header - 2 bytes + 0xA, + 0xB, + // mctp transport header + 0b0000_0001, // mctp reserved, header version + 192, // destination endpoint id + 236, // source endpoint id + 0b1110_0011, // som (1), eom (1), seq (2), tag owner (0), message tag (3) + // mctp message header - 3 bytes + 0x7E, // integrity check (0), message type (vendor defined pci) + 0x12, // pci vendor id - low byte + 0x34, // pci vendor id - high byte + // mctp message body - 1 byte + 0xA5, + 0xB6, + // test trailer - 2 bytes + 0xC, + 0xD, + ], + packet + ); + } + + #[test] + fn test_send_packet_multi() { + const MTU_SIZE: usize = 14; + let mut buffer = [0; 1024]; + let mut context = MctpPacketContext::::new( + TestMedium::new() + .with_headers(&[0xA, 0xB], &[0xC, 0xD]) + // 4 bytes transport header + 4 bytes of data + .with_mtu(MTU_SIZE), + &mut buffer, + ); + + let reply_context = MctpReplyContext { + destination_endpoint_id: EndpointId::try_from(236).unwrap(), + source_endpoint_id: EndpointId::try_from(192).unwrap(), + packet_sequence_number: MctpSequenceNumber::new(1), + message_tag: MctpMessageTag::try_from(3).unwrap(), + medium_context: (), + }; + + // 10 byte to send over 3 packets + let data_to_send = [0xA5, 0xB6, 0xC7, 0xD8, 0xE9, 0xFA, 0x0B, 0x1C, 0x2D, 0x3E]; + let message = (VendorDefinedPciHeader(0x1234), VendorDefinedPci(&data_to_send)); + + let mut state = context.serialize_packet(reply_context, message).unwrap(); + + // First packet + let packet1 = state.next().unwrap().unwrap(); + let expected: [u8; MTU_SIZE] = [ + // test header - 2 bytes + 0xA, + 0xB, + // mctp transport header - 4 bytes + 0b0000_0001, // mctp reserved, header version + 192, // destination endpoint id + 236, // source endpoint id + 0b1010_0011, // som (1), eom (0), seq (2), tag owner (0), message tag (3) + // mctp message header - 3 bytes + 0x7E, // integrity check (0), message type (vendor defined pci) + 0x12, // pci vendor id - low byte + 0x34, // pci vendor id - high byte + // mctp message body data - 1 bytes + 0xA5, + 0xB6, + 0xC7, + // test trailer - 2 bytes + 0xC, + 0xD, + ]; + assert_eq!(packet1, &expected[..MTU_SIZE]); + + // Second packet (middle packet with 4 bytes of data) + let packet2 = state.next().unwrap().unwrap(); + let expected: [u8; MTU_SIZE] = [ + // test header - 2 bytes + 0xA, + 0xB, + // mctp transport header - 4 bytes + 0b0000_0001, // mctp reserved, header version + 192, // destination endpoint id + 236, // source endpoint id + 0b0011_0011, // som (0), eom (0), seq (3), tag owner (0), message tag (3) + // mctp body data - 4 bytes + 0xD8, + 0xE9, + 0xFA, + 0x0B, + 0x1C, + 0x2D, + // test trailer - 2 bytes + 0xC, + 0xD, + ]; + assert_eq!(packet2, &expected[..]); + + // Third packet (final packet with 2 bytes of data) + let packet3 = state.next().unwrap().unwrap(); + let expected: [u8; MTU_SIZE] = [ + // test header - 2 bytes + 0xA, + 0xB, + // mctp transport header - 4 bytes + 0b0000_0001, // mctp reserved, header version + 192, // destination endpoint id + 236, // source endpoint id + 0b0100_0011, // som (0), eom (1), seq (0), tag owner (0), message tag (3) + // mctp body data - 1 bytes + 0x3E, + // test trailer - 2 bytes + 0xC, + 0xD, + // remainder is not populated + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]; + assert_eq!(packet3, &expected[..9]); + + // Verify no more packets + let next = state.next(); + assert!(next.is_none(), "Expected exactly 3 packets: {next:x?}"); + } + + #[test] + fn test_buffer_overflow_protection() { + // Test that buffer overflow is properly prevented + let mut small_buffer = [0u8; 16]; // Very small buffer + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut small_buffer); + + // Create a packet that would cause overflow without protection + let large_packet = [ + // transport header: + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (22) + 0b1000_0011, // som, eom, seq (0), to, tag (3) + // message header: + 0b0000_0000, // integrity check (off) / message type (mctp control message) + 0b0000_0000, // rq, d, rsvd, instance id + 0b0000_0010, // command code (2: get endpoint id) + 0b0000_0000, // completion code + // Large message body that would overflow small buffer + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + ]; + + // This should return an error instead of panicking + let result = context.deserialize_packet(&large_packet); + assert!(result.is_err()); + + if let Err(MctpPacketError::HeaderParseError(msg)) = result { + assert!(msg.contains("buffer overflow")); + } else { + panic!("Expected HeaderParseError with buffer overflow message"); + } + } + + #[test] + fn test_multi_packet_buffer_overflow() { + // Test buffer overflow with multiple packets + let mut small_buffer = [0u8; 20]; // Small buffer that can fit first packet but not second + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut small_buffer); + + // First packet - fits in buffer + let first_packet = [ + // transport header: + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (22) + 0b1000_0011, // som (1), eom (0), seq (0), to, tag (3) + // message header: + 0b0000_0000, // integrity check (off) / message type (mctp control message) + 0b0000_0000, // rq, d, rsvd, instance id + 0b0000_0010, // command code (2: get endpoint id) + 0b0000_0000, // completion code + // Small message body + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + ]; + + // First packet should succeed + let result1 = context.deserialize_packet(&first_packet); + assert!(result1.is_ok()); + assert!(result1.unwrap().is_none()); // No complete message yet + + // Second packet - would cause overflow + let second_packet = [ + // transport header: + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (22) + 0b0101_0011, // som (0), eom (1), seq (1), to, tag (3) - correct sequence number + // Large continuation that would overflow + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + ]; + + // Second packet should fail with buffer overflow + let result2 = context.deserialize_packet(&second_packet); + assert!(result2.is_err()); + + if let Err(MctpPacketError::HeaderParseError(msg)) = result2 { + assert!(msg.contains("buffer overflow")); + } else { + panic!("Expected HeaderParseError with buffer overflow message"); + } + } + + #[test] + fn test_transport_header_underflow() { + // Test transport header parsing with insufficient bytes + let mut buffer = [0u8; 1024]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut buffer); + + // Packet too short for transport header (only 3 bytes) + let short_packet = [0x01, 0x02, 0x03]; + + let result = context.deserialize_packet(&short_packet); + assert!(result.is_err()); + + if let Err(MctpPacketError::HeaderParseError(msg)) = result { + assert!(msg.contains("cannot parse transport header")); + } else { + panic!("Expected HeaderParseError for short transport header"); + } + } + + #[test] + fn test_message_header_underflow() { + // Test message body parsing with insufficient bytes for message header + let mut buffer = [0u8; 1024]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut buffer); + + // Packet with transport header but no message header + let incomplete_packet = [ + // transport header only (4 bytes) + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (22) + 0b1110_0011, /* som (1), eom (1), seq (0), to, tag (3) + * No message header (need 4 more bytes) */ + ]; + + let result = context.deserialize_packet(&incomplete_packet); + assert!(result.is_err()); + + if let Err(MctpPacketError::HeaderParseError(msg)) = result { + assert!(msg.contains("packet too small"), "msg: {msg}"); + } else { + panic!("Expected HeaderParseError for short message header"); + } + } + + #[test] + fn test_serialize_buffer_underflow() { + // Test serialization with buffer too small for serializing the packet and having enough + // buffer for assembling packets + let mut tiny_buffer = [0u8; 4]; // Too small for 4-byte transport header + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut tiny_buffer); + + let reply_context = MctpReplyContext { + destination_endpoint_id: EndpointId::try_from(236).unwrap(), + source_endpoint_id: EndpointId::try_from(192).unwrap(), + packet_sequence_number: MctpSequenceNumber::new(1), + message_tag: MctpMessageTag::try_from(3).unwrap(), + medium_context: (), + }; + + let message = (VendorDefinedPciHeader(0x1234), VendorDefinedPci(&[0xA5])); + let state_result = context.serialize_packet(reply_context, message); + assert!(state_result.is_ok(), "{state_result:?}"); + + let mut state = state_result.unwrap(); + let packet_result = state.next().unwrap(); + + // Should fail because buffer is too small for transport header + assert!(packet_result.is_err()); + if let Err(MctpPacketError::SerializeError(msg)) = packet_result { + assert!(msg.contains("assembly buffer too small")); + } else { + panic!("Expected SerializeError for small buffer"); + } + } + + #[test] + fn test_zero_size_assembly_buffer() { + // Test with zero-size assembly buffer + let mut empty_buffer = [0u8; 0]; + let mut context = MctpPacketContext::::new(TestMedium::new(), &mut empty_buffer); + + let packet = [ + 0b0000_0001, // mctp reserved, header version + 0b0000_1001, // destination endpoint id (9) + 0b0001_0110, // source endpoint id (22) + 0b1110_0011, // som (1), eom (1), seq (0), to, tag (3) + 0x7F, // message header - 3 bytes (vendor defined pci) + 0x12, // pci vendor id - low byte + 0x34, // pci vendor id - high byte + 0b0000_0010, + 0b0000_0000, + ]; + + let result = context.deserialize_packet(&packet); + assert!(result.is_err()); + + if let Err(MctpPacketError::HeaderParseError(msg)) = result { + assert!(msg.contains("buffer overflow")); + } else { + panic!("Expected buffer overflow error for zero-size buffer"); + } + } +} diff --git a/mctp-rs/src/mctp_command_code.rs b/mctp-rs/src/mctp_command_code.rs new file mode 100644 index 000000000..c312c99ea --- /dev/null +++ b/mctp-rs/src/mctp_command_code.rs @@ -0,0 +1,52 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits}; + +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MctpControlCommandCode { + #[default] + Reserved = 0x00, + SetEndpointId = 0x01, + GetEndpointId = 0x02, + GetEndpointUuid = 0x03, + GetMctpVersionSupport = 0x04, + GetMessageTypeSupport = 0x05, + GetVendorDefinedMessageSupport = 0x06, + ResolveEndpointId = 0x07, + AllocateEndpointIds = 0x08, + RoutingInformationUpdate = 0x09, + GetRoutingTableEntries = 0x0A, + PrepareForEndpointDiscovery = 0x0B, + EndpointDiscovery = 0x0C, + DiscoveryNotify = 0x0D, + GetNetworkId = 0x0E, + QueryHop = 0x0F, + ResolveUuid = 0x10, + QueryRateLimit = 0x11, + RequestTxRateLimit = 0x12, + UpdateRateLimit = 0x13, + QuerySupportedInterfaces = 0x14, + // 0x15-0xEF are reserved for future use + // 0xF0-0xFF are transport specific commands +} + +impl TryFromBits for MctpControlCommandCode { + fn try_from_bits(bits: u32) -> Result { + if bits > 0xFF { + return Err("Out of range value for MCTP command code"); + } + (bits as u8) + .try_into() + .map_err(|_| "Invalid value for MCTP command code") + } +} + +impl TryIntoBits for MctpControlCommandCode { + fn try_into_bits(self) -> Result { + Ok(Into::::into(self) as u32) + } +} + +impl NumBytes for MctpControlCommandCode { + const NUM_BYTES: usize = 1; +} diff --git a/mctp-rs/src/mctp_completion_code.rs b/mctp-rs/src/mctp_completion_code.rs new file mode 100644 index 000000000..4645c9f66 --- /dev/null +++ b/mctp-rs/src/mctp_completion_code.rs @@ -0,0 +1,94 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MctpCompletionCode { + #[default] + Success, + Error, + ErrorInvalidData, + ErrorInvalidLength, + ErrorNotReady, + ErrorUnsupportedCmd, + CommandSpecific(u8), // 0x80-0xFF are command specific +} + +impl From for u8 { + fn from(value: MctpCompletionCode) -> Self { + match value { + MctpCompletionCode::Success => 0x00, + MctpCompletionCode::Error => 0x01, + MctpCompletionCode::ErrorInvalidData => 0x02, + MctpCompletionCode::ErrorInvalidLength => 0x03, + MctpCompletionCode::ErrorNotReady => 0x04, + MctpCompletionCode::ErrorUnsupportedCmd => 0x05, + MctpCompletionCode::CommandSpecific(code) => code, + } + } +} +impl TryFrom for MctpCompletionCode { + type Error = &'static str; + fn try_from(value: u8) -> Result { + Ok(match value { + 0x00 => MctpCompletionCode::Success, + 0x01 => MctpCompletionCode::Error, + 0x02 => MctpCompletionCode::ErrorInvalidData, + 0x03 => MctpCompletionCode::ErrorInvalidLength, + 0x04 => MctpCompletionCode::ErrorNotReady, + 0x05 => MctpCompletionCode::ErrorUnsupportedCmd, + 0x06..=0x7F => return Err("Invalid value for MCTP completion code - reserved range"), + 0x80..=0xFF => MctpCompletionCode::CommandSpecific(value), + }) + } +} + +impl TryFromBits for MctpCompletionCode { + fn try_from_bits(bits: u32) -> Result { + if bits > 0xFF { + return Err("Out of range value for MCTP completion code"); + } + (bits as u8).try_into() + } +} + +impl TryIntoBits for MctpCompletionCode { + fn try_into_bits(self) -> Result { + Ok(Into::::into(self) as u32) + } +} + +impl NumBytes for MctpCompletionCode { + const NUM_BYTES: usize = 1; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_completion_code_reserved_range() { + // Test that reserved range 0x06-0x7F is properly rejected + for code in 0x06..=0x7F { + let result = MctpCompletionCode::try_from(code); + assert!(result.is_err(), "Code 0x{:02X} should be rejected", code); + if let Err(msg) = result { + assert!(msg.contains("reserved range")); + } + } + + // Test valid ranges still work + assert_eq!(MctpCompletionCode::try_from(0x00).unwrap(), MctpCompletionCode::Success); + assert_eq!( + MctpCompletionCode::try_from(0x05).unwrap(), + MctpCompletionCode::ErrorUnsupportedCmd + ); + assert_eq!( + MctpCompletionCode::try_from(0x80).unwrap(), + MctpCompletionCode::CommandSpecific(0x80) + ); + assert_eq!( + MctpCompletionCode::try_from(0xFF).unwrap(), + MctpCompletionCode::CommandSpecific(0xFF) + ); + } +} diff --git a/mctp-rs/src/mctp_message_tag.rs b/mctp-rs/src/mctp_message_tag.rs new file mode 100644 index 000000000..5795c263c --- /dev/null +++ b/mctp-rs/src/mctp_message_tag.rs @@ -0,0 +1,31 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits}; + +#[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpMessageTag(u8); + +impl TryFrom for MctpMessageTag { + type Error = &'static str; + fn try_from(value: u8) -> Result { + if value > 0b111 { + return Err("Invalid message tag"); + } + Ok(Self(value)) + } +} + +impl NumBytes for MctpMessageTag { + const NUM_BYTES: usize = 1; +} + +impl TryFromBits for MctpMessageTag { + fn try_from_bits(bits: u32) -> Result { + Self::try_from(bits as u8) + } +} + +impl TryIntoBits for MctpMessageTag { + fn try_into_bits(self) -> Result { + Ok(self.0 as u32) + } +} diff --git a/mctp-rs/src/mctp_packet_context.rs b/mctp-rs/src/mctp_packet_context.rs new file mode 100644 index 000000000..f22af6fd2 --- /dev/null +++ b/mctp-rs/src/mctp_packet_context.rs @@ -0,0 +1,186 @@ +use crate::{ + MctpMessage, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, + deserialize::{parse_message_body, parse_transport_header}, + endpoint_id::EndpointId, + error::{MctpPacketResult, ProtocolError}, + mctp_message_tag::MctpMessageTag, + mctp_sequence_number::MctpSequenceNumber, + medium::{MctpMedium, MctpMediumFrame}, + serialize::SerializePacketState, +}; + +/// Represents the state needed to construct a repsonse to a request: +/// the MCTP transport source/destination, the sequence number to use for +/// the reply, and the medium-specific context that came with the request. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpReplyContext { + pub destination_endpoint_id: EndpointId, + pub source_endpoint_id: EndpointId, + pub packet_sequence_number: MctpSequenceNumber, + pub message_tag: MctpMessageTag, + pub medium_context: M::ReplyContext, +} + +/// Context for serializing and deserializing an MCTP message, which may be split among multiple +/// packets. +pub struct MctpPacketContext<'buf, M: MctpMedium> { + assembly_state: AssemblyState, + medium: M, + packet_assembly_buffer: &'buf mut [u8], +} + +impl<'buf, M: MctpMedium> MctpPacketContext<'buf, M> { + pub fn new(medium: M, packet_assembly_buffer: &'buf mut [u8]) -> Self { + Self { + medium, + assembly_state: AssemblyState::Idle, + packet_assembly_buffer, + } + } + + pub fn deserialize_packet(&mut self, packet: &[u8]) -> MctpPacketResult>, M> { + let (medium_frame, packet) = self.medium.deserialize(packet)?; + let (transport_header, packet) = parse_transport_header::(packet)?; + + let mut state = match self.assembly_state { + AssemblyState::Idle => { + if transport_header.start_of_message == 0 { + return Err(MctpPacketError::ProtocolError(ProtocolError::ExpectedStartOfMessage)); + } + + AssemblingState { + message_tag: transport_header.message_tag, + tag_owner: transport_header.tag_owner, + source_endpoint_id: transport_header.source_endpoint_id, + packet_sequence_number: transport_header.packet_sequence_number, + packet_assembly_buffer_index: 0, + } + } + AssemblyState::Receiving(assembling_state) => { + if transport_header.start_of_message != 0 { + return Err(MctpPacketError::ProtocolError(ProtocolError::UnexpectedStartOfMessage)); + } + if assembling_state.message_tag != transport_header.message_tag { + return Err(MctpPacketError::ProtocolError(ProtocolError::MessageTagMismatch( + assembling_state.message_tag, + transport_header.message_tag, + ))); + } + if assembling_state.tag_owner != transport_header.tag_owner { + return Err(MctpPacketError::ProtocolError(ProtocolError::TagOwnerMismatch( + assembling_state.tag_owner, + transport_header.tag_owner, + ))); + } + if assembling_state.source_endpoint_id != transport_header.source_endpoint_id { + return Err(MctpPacketError::ProtocolError(ProtocolError::SourceEndpointIdMismatch( + assembling_state.source_endpoint_id, + transport_header.source_endpoint_id, + ))); + } + let expected_sequence_number = assembling_state.packet_sequence_number.next(); + if expected_sequence_number != transport_header.packet_sequence_number { + return Err(MctpPacketError::ProtocolError( + ProtocolError::UnexpectedPacketSequenceNumber( + expected_sequence_number, + transport_header.packet_sequence_number, + ), + )); + } + assembling_state + } + }; + + let buffer_idx = state.packet_assembly_buffer_index; + let packet_size = medium_frame.packet_size(); + if packet_size < 4 { + return Err(MctpPacketError::HeaderParseError( + "transport frame indicated packet length < 4", + )); + } + let packet_size = packet_size - 4; // to account for the transport header + if packet.len() < packet_size { + return Err(MctpPacketError::HeaderParseError("packet.len() < packet_size")); + } + // Check bounds to prevent buffer overflow + if buffer_idx + packet_size > self.packet_assembly_buffer.len() { + return Err(MctpPacketError::HeaderParseError( + "packet assembly buffer overflow - insufficient space", + )); + } + self.packet_assembly_buffer[buffer_idx..buffer_idx + packet_size].copy_from_slice(&packet[..packet_size]); + state.packet_assembly_buffer_index += packet_size; + + let message = if transport_header.end_of_message == 1 { + self.assembly_state = AssemblyState::Idle; + let (message_body, message_integrity_check) = + parse_message_body::(&self.packet_assembly_buffer[..state.packet_assembly_buffer_index])?; + Some(MctpMessage { + reply_context: MctpReplyContext { + destination_endpoint_id: transport_header.destination_endpoint_id, + source_endpoint_id: transport_header.source_endpoint_id, + packet_sequence_number: transport_header.packet_sequence_number, + message_tag: transport_header.message_tag, + medium_context: medium_frame.reply_context(), + }, + message_buffer: message_body, + message_integrity_check, + }) + } else { + self.assembly_state = AssemblyState::Receiving(state); + None + }; + + Ok(message) + } + + pub fn serialize_packet>( + &'buf mut self, + reply_context: MctpReplyContext, + message: (P::Header, P), + ) -> MctpPacketResult, M> { + match self.assembly_state { + AssemblyState::Idle => {} + _ => { + return Err(MctpPacketError::ProtocolError( + ProtocolError::SendMessageWhileAssembling, + )); + } + }; + + self.packet_assembly_buffer[0] = P::MESSAGE_TYPE; + let header_size = message.0.serialize(&mut self.packet_assembly_buffer[1..])?; + let body_size = message + .1 + .serialize(&mut self.packet_assembly_buffer[header_size + 1..])?; + + let (message, rest) = self.packet_assembly_buffer.split_at_mut(header_size + body_size + 1); + + Ok(SerializePacketState { + medium: &self.medium, + reply_context, + current_packet_num: 0, + serialized_message_header: false, + message_buffer: message, + assembly_buffer: rest, + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum AssemblyState { + Idle, + Receiving(AssemblingState), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct AssemblingState { + message_tag: MctpMessageTag, + tag_owner: u8, + source_endpoint_id: EndpointId, + packet_sequence_number: MctpSequenceNumber, + packet_assembly_buffer_index: usize, +} diff --git a/mctp-rs/src/mctp_sequence_number.rs b/mctp-rs/src/mctp_sequence_number.rs new file mode 100644 index 000000000..a79404b96 --- /dev/null +++ b/mctp-rs/src/mctp_sequence_number.rs @@ -0,0 +1,42 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits}; + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpSequenceNumber(u8); + +impl MctpSequenceNumber { + const MAX: u8 = 4; + + pub fn new(value: u8) -> Self { + Self(value) + } + + pub fn inc(&mut self) -> Self { + *self = self.next(); + *self + } + + pub fn next(&self) -> Self { + Self((self.0 + 1) % Self::MAX) + } +} + +impl NumBytes for MctpSequenceNumber { + const NUM_BYTES: usize = 1; +} + +impl TryIntoBits for MctpSequenceNumber { + fn try_into_bits(self) -> Result { + Ok(self.0 as u32) + } +} + +impl TryFromBits for MctpSequenceNumber { + fn try_from_bits(bits: u32) -> Result { + if bits >= Self::MAX as u32 { + Err("sequence number out of range") + } else { + Ok(Self(bits as u8)) + } + } +} diff --git a/mctp-rs/src/mctp_transport_header.rs b/mctp-rs/src/mctp_transport_header.rs new file mode 100644 index 000000000..e9fe5915b --- /dev/null +++ b/mctp-rs/src/mctp_transport_header.rs @@ -0,0 +1,51 @@ +use bit_register::bit_register; + +use crate::{endpoint_id::EndpointId, mctp_message_tag::MctpMessageTag, mctp_sequence_number::MctpSequenceNumber}; + +bit_register! { + #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct MctpTransportHeader: little_endian u32 { + pub reserved: u8 => [28:31], + pub header_version: u8 => [24:27], + pub destination_endpoint_id: EndpointId => [16:23], + pub source_endpoint_id: EndpointId => [8:15], + pub start_of_message: u8 => [7], + pub end_of_message: u8 => [6], + pub packet_sequence_number: MctpSequenceNumber => [4:5], + pub tag_owner: u8 => [3], + pub message_tag: MctpMessageTag => [0:2], + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mctp_transport_header_bit_register() { + let header = MctpTransportHeader::try_from(u32::from_be_bytes([ + 0b0000_0001, // reserved, header version (1) + 0b0000_1001, // destination endpoint id (9) + 0b0001_0010, // source endpoint id (18) + 0b0000_0101, /* start of message, end of message, packet sequence number (0), tag + * owner, message tag */ + ])) + .unwrap(); + + assert_eq!( + header, + MctpTransportHeader { + reserved: 0b0000, + header_version: 0b0001, + destination_endpoint_id: EndpointId::Id(9), + source_endpoint_id: EndpointId::Id(18), + start_of_message: 0b0000, + end_of_message: 0b0000, + packet_sequence_number: MctpSequenceNumber::new(0), + tag_owner: 0b0000, + message_tag: MctpMessageTag::try_from(0b101).unwrap(), + } + ); + } +} diff --git a/mctp-rs/src/medium/mod.rs b/mctp-rs/src/medium/mod.rs new file mode 100644 index 000000000..fb49a9adc --- /dev/null +++ b/mctp-rs/src/medium/mod.rs @@ -0,0 +1,37 @@ +use crate::error::MctpPacketResult; + +pub mod smbus_espi; +mod util; + +pub trait MctpMedium: Sized { + /// the medium specific header and trailer for the packet + type Frame: MctpMediumFrame; + + /// the error type for deserialization of the medium specific header + type Error: core::fmt::Debug + Copy + Clone + PartialEq + Eq; + + // the type used for the data needed to send a reply to a request + type ReplyContext: core::fmt::Debug + Copy + Clone + PartialEq + Eq; + + /// the maximum transmission unit for the medium + fn max_message_body_size(&self) -> usize; + + /// deserialize the packet into the medium specific header and remainder of the packet - + /// this includes the mctp transport header, and mctp packet payload + fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self>; + + /// serialize the packet into the medium specific header and the payload + fn serialize<'buf, F>( + &self, + reply_context: Self::ReplyContext, + buffer: &'buf mut [u8], + message_writer: F, + ) -> MctpPacketResult<&'buf [u8], Self> + where + F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult; +} + +pub trait MctpMediumFrame: Clone + Copy { + fn packet_size(&self) -> usize; + fn reply_context(&self) -> M::ReplyContext; +} diff --git a/mctp-rs/src/medium/smbus_espi.rs b/mctp-rs/src/medium/smbus_espi.rs new file mode 100644 index 000000000..a54b9378a --- /dev/null +++ b/mctp-rs/src/medium/smbus_espi.rs @@ -0,0 +1,826 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits, bit_register}; + +use crate::{ + MctpPacketError, + error::MctpPacketResult, + medium::{ + MctpMedium, MctpMediumFrame, + util::{One, Zero}, + }, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SmbusEspiMedium; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SmbusEspiReplyContext { + pub destination_slave_address: u8, + pub source_slave_address: u8, +} + +impl MctpMedium for SmbusEspiMedium { + type Frame = SmbusEspiMediumFrame; + type Error = &'static str; + type ReplyContext = SmbusEspiReplyContext; + + fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { + // Check if packet has enough bytes for header + if packet.len() < 4 { + return Err(MctpPacketError::MediumError("Packet too short to parse smbus header")); + } + + let header_value = u32::from_be_bytes( + packet[0..4] + .try_into() + .map_err(|_| MctpPacketError::MediumError("Packet too short to parse smbus header"))?, + ); + // strip off the smbus header + let packet = &packet[4..]; + let header = SmbusEspiMediumHeader::try_from(header_value) + .map_err(|_| MctpPacketError::MediumError("Invalid smbus header"))?; + if header.byte_count as usize + 1 > packet.len() { + return Err(MctpPacketError::MediumError( + "Packet too short to parse smbus body and PEC", + )); + } + let pec = packet[header.byte_count as usize]; + // strip off the PEC byte + let packet = &packet[..header.byte_count as usize]; + Ok((SmbusEspiMediumFrame { header, pec }, packet)) + } + + fn serialize<'buf, F>( + &self, + reply_context: Self::ReplyContext, + buffer: &'buf mut [u8], + message_writer: F, + ) -> MctpPacketResult<&'buf [u8], Self> + where + F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, + { + // Reserve space for header (4 bytes) and PEC (1 byte) + if buffer.len() < 5 { + return Err(MctpPacketError::MediumError("Buffer too small for smbus frame")); + } + + // split off a buffer where we will write the header, the rest is for body + PEC + let (header_slice, body) = buffer.split_at_mut(4); + + // Write the body first, but ensure we leave space for PEC + if body.is_empty() { + return Err(MctpPacketError::MediumError("No space for PEC byte")); + } + let available_body_len = body.len() - 1; // Reserve 1 byte for PEC + let body_len = message_writer(&mut body[..available_body_len])?; + + // with the body has been written, construct the header + let header = SmbusEspiMediumHeader { + destination_slave_address: reply_context.source_slave_address, + source_slave_address: reply_context.destination_slave_address, + byte_count: body_len as u8, + command_code: SmbusCommandCode::Mctp, + ..Default::default() + }; + let header_value = TryInto::::try_into(header).map_err(MctpPacketError::MediumError)?; + header_slice.copy_from_slice(&header_value.to_be_bytes()); + + // with the header written, compute the PEC byte + let pec_value = smbus_pec::pec(&buffer[0..4 + body_len]); + buffer[4 + body_len] = pec_value; + + // add 4 for frame header, add 1 for PEC byte + Ok(&buffer[0..4 + body_len + 1]) + } + + // TODO - this is a guess, need to find the actual value from spec + fn max_message_body_size(&self) -> usize { + 32 + } +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum SmbusCommandCode { + #[default] + Mctp = 0x0F, +} +impl TryFromBits for SmbusCommandCode { + fn try_from_bits(bits: u32) -> Result { + if bits > 0xFF { + Err("Command code out of range") + } else { + SmbusCommandCode::try_from(bits as u8).map_err(|_| "Invalid command code") + } + } +} +impl TryIntoBits for SmbusCommandCode { + fn try_into_bits(self) -> Result { + Ok(Into::::into(self) as u32) + } +} +impl NumBytes for SmbusCommandCode { + const NUM_BYTES: usize = 1; +} + +// SMBus header per documentation in eSPI spec: https://cdrdv2-public.intel.com/841685/841685_ESPI_IBS_TS_Rev_1_6.pdf +// See figure 46 on page 74. This struct corresponds to bytes 3..=6 of the sample OOB MCTP packet +// frame. +bit_register! { + #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct SmbusEspiMediumHeader: little_endian u32 { + pub destination_slave_address: u8 => [25:31], + pub _reserved1: Zero => [24], + pub command_code: SmbusCommandCode => [16:23], + pub byte_count: u8 => [8:15], + pub source_slave_address: u8 => [1:7], + pub _reserved2: One => [0], + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SmbusEspiMediumFrame { + header: SmbusEspiMediumHeader, + pec: u8, +} + +impl SmbusEspiReplyContext { + fn new(frame: SmbusEspiMediumFrame) -> Self { + Self { + destination_slave_address: frame.header.destination_slave_address, + source_slave_address: frame.header.source_slave_address, + } + } +} + +impl MctpMediumFrame for SmbusEspiMediumFrame { + fn packet_size(&self) -> usize { + self.header.byte_count as usize + } + + fn reply_context(&self) -> SmbusEspiReplyContext { + SmbusEspiReplyContext::new(*self) + } +} + +#[cfg(test)] +mod tests { + extern crate std; + use super::*; + + #[test] + fn test_deserialize_valid_packet() { + let medium = SmbusEspiMedium; + + // Create a valid SMBus packet with little-endian header + // destination_slave_address: 0x20, source_slave_address: 0x10, command: 0x0F, byte_count: 4 + let header = SmbusEspiMediumHeader { + destination_slave_address: 0x20, + source_slave_address: 0x10, + command_code: SmbusCommandCode::Mctp, + byte_count: 4, + ..Default::default() + }; + let header_value: u32 = header.try_into().unwrap(); + let header_bytes = header_value.to_be_bytes(); + + let payload = [0xAA, 0xBB, 0xCC, 0xDD]; // 4 bytes as specified by byte_count + let mut combined = [0u8; 8]; + combined[0..4].copy_from_slice(&header_bytes); + combined[4..8].copy_from_slice(&payload); + let pec = smbus_pec::pec(&combined); + + let mut packet = [0u8; 9]; + packet[0..4].copy_from_slice(&header_bytes); + packet[4..8].copy_from_slice(&payload); + packet[8] = pec; + + let result = medium.deserialize(&packet).unwrap(); + let (frame, body) = result; + + assert_eq!(frame.header.destination_slave_address, 0x20); + assert_eq!(frame.header.source_slave_address, 0x10); + assert_eq!(frame.header.command_code, SmbusCommandCode::Mctp); + assert_eq!(frame.header.byte_count, 4); + assert_eq!(frame.pec, pec); + assert_eq!(body, &payload); + } + + #[test] + fn test_deserialize_packet_too_short_header() { + let medium = SmbusEspiMedium; + let short_packet = [0x01, 0x02]; // Only 2 bytes, need at least 4 for header + + let result = medium.deserialize(&short_packet); + assert_eq!( + result, + Err(MctpPacketError::MediumError("Packet too short to parse smbus header")) + ); + } + + #[test] + fn test_deserialize_packet_too_short_body() { + let medium = SmbusEspiMedium; + + // Header indicates 10 bytes of data but we only provide 2 + let header_bytes = [ + 0x20, // destination_slave_address + 0x0F, // command_code (MCTP) + 0x0A, // byte_count: 10 bytes + 0x21, // source_slave_address + ]; + + let short_payload = [0xAA, 0xBB]; // Only 2 bytes, but header says 10 + + let mut packet = [0u8; 6]; + packet[0..4].copy_from_slice(&header_bytes); + packet[4..6].copy_from_slice(&short_payload); + + let result = medium.deserialize(&packet); + assert_eq!( + result, + Err(MctpPacketError::MediumError( + "Packet too short to parse smbus body and PEC" + )) + ); + } + + #[test] + fn test_deserialize_invalid_header() { + let medium = SmbusEspiMedium; + + // Create invalid header with command code that's not MCTP + let invalid_header_bytes = [ + 0x20, // destination_slave_address + 0xFF, // invalid command_code (not 0x0F) + 0x04, // byte_count + 0x20, // source_slave_address + ]; + + let payload = [0xAA, 0xBB, 0xCC, 0xDD]; + let pec = 0x00; // PEC doesn't matter for this test + + let mut packet = [0u8; 9]; + packet[0..4].copy_from_slice(&invalid_header_bytes); + packet[4..8].copy_from_slice(&payload); + packet[8] = pec; + + let result = medium.deserialize(&packet); + assert_eq!(result, Err(MctpPacketError::MediumError("Invalid smbus header"))); + } + + #[test] + fn test_deserialize_zero_byte_count() { + let medium = SmbusEspiMedium; + + let header_bytes = [ + 0x20, // destination_slave_address + 0x0F, // command_code (MCTP) + 0x00, // byte_count: 0 bytes + 0x21, // source_slave_address + ]; + + let pec = smbus_pec::pec(&header_bytes); + + let mut packet = [0u8; 5]; + packet[0..4].copy_from_slice(&header_bytes); + packet[4] = pec; + + let result = medium.deserialize(&packet).unwrap(); + let (frame, body) = result; + + assert_eq!(frame.header.byte_count, 0); + assert_eq!(frame.pec, pec); + assert_eq!(body.len(), 0); + } + + #[test] + fn test_serialize_valid_packet() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + let mut buffer = [0u8; 64]; + let test_payload = [0xAA, 0xBB, 0xCC, 0xDD]; + + let result = medium + .serialize(reply_context, &mut buffer, |buf| { + buf[..test_payload.len()].copy_from_slice(&test_payload); + Ok(test_payload.len()) + }) + .unwrap(); + + // Verify the serialized packet structure + // Header: 4 bytes + payload: 4 bytes + PEC: 1 byte = 9 bytes total + assert_eq!(result.len(), 9); + + // Parse the header to verify correctness + let header_value = u32::from_be_bytes([result[0], result[1], result[2], result[3]]); + let header = SmbusEspiMediumHeader::try_from(header_value).unwrap(); + + // Note: destination and source are swapped in reply + assert_eq!(header.destination_slave_address, 0x10); // reply_context.source + assert_eq!(header.source_slave_address, 0x20); // reply_context.destination + assert_eq!(header.command_code, SmbusCommandCode::Mctp); + assert_eq!(header.byte_count, 4); + + // Verify payload + assert_eq!(&result[4..8], &test_payload); + + // Verify PEC byte + let expected_pec = smbus_pec::pec(&result[0..8]); + assert_eq!(result[8], expected_pec); + } + + #[test] + fn test_serialize_buffer_too_small() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + let mut small_buffer = [0u8; 4]; // Only 4 bytes, need at least 5 (header + PEC) + + let result = medium.serialize(reply_context, &mut small_buffer, |_| Ok(0)); + + assert_eq!( + result, + Err(MctpPacketError::MediumError("Buffer too small for smbus frame")) + ); + } + + #[test] + fn test_serialize_minimal_buffer() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + let mut minimal_buffer = [0u8; 5]; // Exactly 5 bytes (4 header + 1 PEC) + + let result = medium + .serialize( + reply_context, + &mut minimal_buffer, + |_| Ok(0), // No payload data + ) + .unwrap(); + + assert_eq!(result.len(), 5); + + // Verify header + let header_value = u32::from_be_bytes([result[0], result[1], result[2], result[3]]); + let header = SmbusEspiMediumHeader::try_from(header_value).unwrap(); + assert_eq!(header.byte_count, 0); + + // Verify PEC + let expected_pec = smbus_pec::pec(&result[0..4]); + assert_eq!(result[4], expected_pec); + } + + #[test] + fn test_serialize_max_payload() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + // Test with maximum payload size (255 bytes as byte_count is u8) + let max_payload = [0x55u8; 255]; + let mut buffer = [0u8; 260]; // 4 + 255 + 1 = header + max payload + PEC + + let result = medium + .serialize(reply_context, &mut buffer, |buf| { + let copy_len = max_payload.len().min(buf.len()); + buf[..copy_len].copy_from_slice(&max_payload[..copy_len]); + Ok(copy_len) + }) + .unwrap(); + + assert_eq!(result.len(), 260); // 4 + 255 + 1 + + // Verify header + let header_value = u32::from_be_bytes([result[0], result[1], result[2], result[3]]); + let header = SmbusEspiMediumHeader::try_from(header_value).unwrap(); + assert_eq!(header.byte_count, 255); + + // Verify payload + assert_eq!(&result[4..259], &max_payload[..]); + + // Verify PEC + let expected_pec = smbus_pec::pec(&result[0..259]); + assert_eq!(result[259], expected_pec); + } + + #[test] + fn test_serialize_message_writer_error() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + let mut buffer = [0u8; 64]; + + let result = medium.serialize(reply_context, &mut buffer, |_| { + Err(MctpPacketError::MediumError("Test error")) + }); + + assert_eq!(result, Err(MctpPacketError::MediumError("Test error"))); + } + + #[test] + fn test_roundtrip_serialization_deserialization() { + let medium = SmbusEspiMedium; + let original_context = SmbusEspiReplyContext { + destination_slave_address: 0x42, + source_slave_address: 0x24, + }; + + let original_payload = [0x11, 0x22, 0x33, 0x44, 0x55]; + let mut buffer = [0u8; 64]; + + // Serialize + let serialized = medium + .serialize(original_context, &mut buffer, |buf| { + buf[..original_payload.len()].copy_from_slice(&original_payload); + Ok(original_payload.len()) + }) + .unwrap(); + + // Deserialize + let (frame, deserialized_payload) = medium.deserialize(serialized).unwrap(); + + // Verify roundtrip correctness + assert_eq!(deserialized_payload, &original_payload); + assert_eq!(frame.header.destination_slave_address, 0x24); // swapped + assert_eq!(frame.header.source_slave_address, 0x42); // swapped + assert_eq!(frame.header.command_code, SmbusCommandCode::Mctp); + assert_eq!(frame.header.byte_count, original_payload.len() as u8); + + // Verify PEC is correct + let expected_pec = smbus_pec::pec(&serialized[0..serialized.len() - 1]); + assert_eq!(frame.pec, expected_pec); + } + + #[test] + fn test_frame_packet_size() { + let frame = SmbusEspiMediumFrame { + header: SmbusEspiMediumHeader { + byte_count: 42, + ..Default::default() + }, + pec: 0, + }; + + assert_eq!(frame.packet_size(), 42); + } + + #[test] + fn test_frame_reply_context() { + let frame = SmbusEspiMediumFrame { + header: SmbusEspiMediumHeader { + destination_slave_address: 0x30, + source_slave_address: 0x40, + ..Default::default() + }, + pec: 0, + }; + + let context = frame.reply_context(); + assert_eq!(context.destination_slave_address, 0x30); + assert_eq!(context.source_slave_address, 0x40); + } + + #[test] + fn test_smbus_command_code_conversion() { + // Test valid command code + assert_eq!(SmbusCommandCode::try_from_bits(0x0F).unwrap(), SmbusCommandCode::Mctp); + + // Test out of range (> 0xFF) + assert_eq!(SmbusCommandCode::try_from_bits(0x100), Err("Command code out of range")); + + // Test invalid command code + assert_eq!(SmbusCommandCode::try_from_bits(0x10), Err("Invalid command code")); + + // Test conversion to bits + assert_eq!(SmbusCommandCode::Mctp.try_into_bits().unwrap(), 0x0F); + } + + #[test] + fn test_header_bit_register_edge_cases() { + // Test all zeros - this should use default command code + let header = SmbusEspiMediumHeader::default(); + assert_eq!(header.destination_slave_address, 0); + assert_eq!(header.source_slave_address, 0); + assert_eq!(header.byte_count, 0); + assert_eq!(header.command_code, SmbusCommandCode::Mctp); // default + + // Test valid maximum values within bit ranges + let header = SmbusEspiMediumHeader { + destination_slave_address: 0x7F, // 7 bits max (bits 25-31) + source_slave_address: 0x3F, // 6 bits max (bits 1-7, bit 0 reserved) + byte_count: 0xFF, // 8 bits max (bits 8-15) + command_code: SmbusCommandCode::Mctp, + ..Default::default() + }; + + // Verify we can convert to u32 and back + let header_value: u32 = header.try_into().unwrap(); + let reconstructed = SmbusEspiMediumHeader::try_from(header_value).unwrap(); + assert_eq!(reconstructed, header); + } + + #[test] + fn test_pec_calculation_accuracy() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x50, + source_slave_address: 0x30, + }; + + // Test with known data to verify PEC calculation + let test_data = [0x01, 0x02, 0x03]; + let mut buffer = [0u8; 32]; + + let result = medium + .serialize(reply_context, &mut buffer, |buf| { + buf[..test_data.len()].copy_from_slice(&test_data); + Ok(test_data.len()) + }) + .unwrap(); + + // Manually calculate expected PEC and compare + let data_for_pec = &result[0..result.len() - 1]; + let expected_pec = smbus_pec::pec(data_for_pec); + let actual_pec = result[result.len() - 1]; + + assert_eq!(actual_pec, expected_pec); + } + + #[test] + fn test_serialize_with_empty_payload() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x60, + source_slave_address: 0x70, + }; + + let mut buffer = [0u8; 16]; + + let result = medium + .serialize( + reply_context, + &mut buffer, + |_| Ok(0), // Empty payload + ) + .unwrap(); + + assert_eq!(result.len(), 5); // 4 bytes header + 1 byte PEC + + // Verify header + let header_value = u32::from_be_bytes([result[0], result[1], result[2], result[3]]); + let header = SmbusEspiMediumHeader::try_from(header_value).unwrap(); + assert_eq!(header.byte_count, 0); + assert_eq!(header.destination_slave_address, 0x70); // swapped + assert_eq!(header.source_slave_address, 0x60); // swapped + + // Verify PEC + let expected_pec = smbus_pec::pec(&result[0..4]); + assert_eq!(result[4], expected_pec); + } + + #[test] + fn test_max_message_body_size() { + let medium = SmbusEspiMedium; + assert_eq!(medium.max_message_body_size(), 32); + } + + #[test] + fn test_address_swapping_in_reply_context() { + // Test that addresses are properly swapped when creating reply context + let original_frame = SmbusEspiMediumFrame { + header: SmbusEspiMediumHeader { + destination_slave_address: 0x2A, // Valid 7-bit address + source_slave_address: 0x3B, // Valid 6-bit address + ..Default::default() + }, + pec: 0, + }; + + let reply_context = SmbusEspiReplyContext::new(original_frame); + assert_eq!(reply_context.destination_slave_address, 0x2A); + assert_eq!(reply_context.source_slave_address, 0x3B); + + // Now test that when we serialize with this context, addresses are swapped back + let medium = SmbusEspiMedium; + let mut buffer = [0u8; 16]; + + let result = medium.serialize(reply_context, &mut buffer, |_| Ok(0)).unwrap(); + + let header_value = u32::from_be_bytes([result[0], result[1], result[2], result[3]]); + let response_header = SmbusEspiMediumHeader::try_from(header_value).unwrap(); + + // In the response, source becomes destination and vice versa + assert_eq!(response_header.destination_slave_address, 0x3B); + assert_eq!(response_header.source_slave_address, 0x2A); + } + + #[test] + fn test_deserialize_with_different_byte_counts() { + let medium = SmbusEspiMedium; + + for byte_count in [1, 16, 32, 64, 128, 255] { + let header_bytes = [ + 0x20, // destination_slave_address + 0x0F, // command_code (MCTP) + byte_count, // byte_count + 0x21, // source_slave_address + ]; + + let payload = [0x42u8; 255]; + let payload_slice = &payload[..byte_count as usize]; + + let mut combined = [0u8; 259]; // 4 header + 255 max payload + combined[0..4].copy_from_slice(&header_bytes); + combined[4..4 + byte_count as usize].copy_from_slice(payload_slice); + let pec = smbus_pec::pec(&combined[0..4 + byte_count as usize]); + + let mut packet = [0u8; 260]; // 4 + 255 + 1 + packet[0..4].copy_from_slice(&header_bytes); + packet[4..4 + byte_count as usize].copy_from_slice(payload_slice); + packet[4 + byte_count as usize] = pec; + + let packet_slice = &packet[0..4 + byte_count as usize + 1]; + let result = medium.deserialize(packet_slice).unwrap(); + let (frame, body) = result; + + assert_eq!(frame.header.byte_count, byte_count); + assert_eq!(body.len(), byte_count as usize); + assert_eq!(frame.pec, pec); + } + } + + #[test] + fn test_smbus_buffer_overflow_protection() { + let medium = SmbusEspiMedium; + + // Test packet with byte_count that would cause overflow + let header_bytes = [ + 0x20, // destination_slave_address + 0x0F, // command_code (MCTP) + 0xFF, // byte_count: 255 bytes (maximum) + 0x21, // source_slave_address + ]; + + // Provide a packet that's too short for the claimed byte_count + let short_payload = [0xAA, 0xBB]; // Only 2 bytes, but header claims 255 + let mut packet = [0u8; 7]; // 4 header + 2 payload + 1 PEC = 7 total + packet[0..4].copy_from_slice(&header_bytes); + packet[4..6].copy_from_slice(&short_payload); + packet[6] = 0x00; // PEC (doesn't matter for this test) + + let result = medium.deserialize(&packet); + assert_eq!( + result, + Err(MctpPacketError::MediumError( + "Packet too short to parse smbus body and PEC" + )) + ); + } + + #[test] + fn test_smbus_serialize_buffer_underflow() { + let medium = SmbusEspiMedium; + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + // Test with buffer smaller than minimum required (4 header + 1 PEC = 5 bytes) + let mut tiny_buffer = [0u8; 4]; // Only 4 bytes, need at least 5 + + let result = medium.serialize(reply_context, &mut tiny_buffer, |_| { + Ok(0) // No payload + }); + + assert_eq!( + result, + Err(MctpPacketError::MediumError("Buffer too small for smbus frame")) + ); + } + + #[test] + fn test_smbus_header_bounds_checking() { + let medium = SmbusEspiMedium; + + // Test with packet shorter than header size (4 bytes) + for packet_size in 0..4 { + let short_packet = [0u8; 4]; + let result = medium.deserialize(&short_packet[..packet_size]); + assert_eq!( + result, + Err(MctpPacketError::MediumError("Packet too short to parse smbus header")) + ); + } + } + + #[test] + fn test_smbus_pec_bounds_checking() { + let medium = SmbusEspiMedium; + + // Test with packet that has header but claims more data than available for PEC + let header_bytes = [ + 0x20, // destination_slave_address + 0x0F, // command_code (MCTP) + 0x05, // byte_count: 5 bytes + 0x21, // source_slave_address + ]; + + // Provide exactly enough bytes for the data but no PEC byte + let payload = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE]; // 5 bytes as claimed + let mut packet = [0u8; 9]; // 4 header + 5 payload = 9 total (missing PEC) + packet[0..4].copy_from_slice(&header_bytes); + packet[4..9].copy_from_slice(&payload); + + let result = medium.deserialize(&packet); + assert_eq!( + result, + Err(MctpPacketError::MediumError( + "Packet too short to parse smbus body and PEC" + )) + ); + } + + #[test] + fn test_smbus_zero_byte_count_edge_case() { + let medium = SmbusEspiMedium; + + // Test with zero byte count but packet shorter than header + PEC + let header_bytes = [ + 0x20, // destination_slave_address + 0x0F, // command_code (MCTP) + 0x00, // byte_count: 0 bytes + 0x21, // source_slave_address + ]; + + // Test with packet missing PEC byte + let mut short_packet = [0u8; 4]; // Only header, no PEC + short_packet.copy_from_slice(&header_bytes); + + let result = medium.deserialize(&short_packet); + assert_eq!( + result, + Err(MctpPacketError::MediumError( + "Packet too short to parse smbus body and PEC" + )) + ); + } + + #[test] + fn test_smbus_maximum_payload_boundary() { + let medium = SmbusEspiMedium; + + // Test serialization at the boundary of maximum payload (255 bytes) + let reply_context = SmbusEspiReplyContext { + destination_slave_address: 0x20, + source_slave_address: 0x10, + }; + + let max_payload = [0x55u8; 255]; + let mut buffer = [0u8; 260]; // 4 + 255 + 1 = exactly enough + + let result = medium.serialize(reply_context, &mut buffer, |buf| { + let copy_len = max_payload.len().min(buf.len()); + buf[..copy_len].copy_from_slice(&max_payload[..copy_len]); + Ok(copy_len) + }); + + assert!(result.is_ok()); + let serialized = result.unwrap(); + assert_eq!(serialized.len(), 260); // Should use exactly all available space + + // Test with buffer one byte too small for maximum payload + let mut small_buffer = [0u8; 259]; // One byte short for max payload + let result_small = medium.serialize(reply_context, &mut small_buffer, |buf| { + // Try to write max payload but buffer is too small + let copy_len = max_payload.len().min(buf.len()); + buf[..copy_len].copy_from_slice(&max_payload[..copy_len]); + Ok(copy_len) + }); + + // Should still work but with truncated payload (254 bytes payload + 4 header + 1 PEC = 259) + assert!(result_small.is_ok()); + let serialized_small = result_small.unwrap(); + assert_eq!(serialized_small.len(), 259); // Uses all available space + } +} diff --git a/mctp-rs/src/medium/util.rs b/mctp-rs/src/medium/util.rs new file mode 100644 index 000000000..c97089e7a --- /dev/null +++ b/mctp-rs/src/medium/util.rs @@ -0,0 +1,37 @@ +use bit_register::{NumBytes, TryFromBits, TryIntoBits}; + +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Zero; + +impl TryFromBits for Zero { + fn try_from_bits(bits: u32) -> Result { + if bits != 0 { Err("Bits must be 0") } else { Ok(Zero) } + } +} +impl TryIntoBits for Zero { + fn try_into_bits(self) -> Result { + Ok(0) + } +} +impl NumBytes for Zero { + const NUM_BYTES: usize = 4; +} + +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct One; + +impl TryFromBits for One { + fn try_from_bits(bits: u32) -> Result { + if bits != 1 { Err("Value must be 1") } else { Ok(One) } + } +} +impl TryIntoBits for One { + fn try_into_bits(self) -> Result { + Ok(1) + } +} +impl NumBytes for One { + const NUM_BYTES: usize = 4; +} diff --git a/mctp-rs/src/message_type/mctp_control.rs b/mctp-rs/src/message_type/mctp_control.rs new file mode 100644 index 000000000..c85b94de0 --- /dev/null +++ b/mctp-rs/src/message_type/mctp_control.rs @@ -0,0 +1,227 @@ +use crate::{ + MctpMedium, MctpMessageHeaderTrait, MctpMessageTrait, + MctpPacketError::{self, HeaderParseError}, + error::{MctpPacketResult, ProtocolError}, + mctp_command_code::MctpControlCommandCode, + mctp_completion_code::MctpCompletionCode, +}; + +#[derive(Debug, Default, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpControlHeader { + pub request_bit: bool, // bit 7 + pub datagram_bit: bool, // bit 6 + pub instance_id: u8, // bits 4-0 + pub command_code: MctpControlCommandCode, + pub completion_code: MctpCompletionCode, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MctpControl { + SetEndpointIdRequest([u8; 2]), + SetEndpointIdResponse([u8; 3]), + GetEndpointIdRequest, + GetEndpointIdResponse([u8; 3]), +} + +impl MctpMessageHeaderTrait for MctpControlHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + if buffer.len() < 3 { + return Err(crate::MctpPacketError::SerializeError( + "buffer too small for mctp control header", + )); + } + + check_request_and_completion_code(self.request_bit, self.completion_code)?; + + buffer[0] = (self.request_bit as u8) << 7 | (self.datagram_bit as u8) << 6 | (self.instance_id & 0b0001_1111); + buffer[1] = self.command_code as u8; + buffer[2] = self.completion_code.into(); + Ok(3) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + if buffer.len() < 3 { + return Err(HeaderParseError("buffer too small for mctp control header")); + } + + let request_bit = buffer[0] & 0b1000_0000 != 0; + let datagram_bit = buffer[0] & 0b0100_0000 != 0; + let instance_id = buffer[0] & 0b0001_1111; + let command_code = + MctpControlCommandCode::try_from(buffer[1]).map_err(|_| HeaderParseError("invalid mctp command code"))?; + let completion_code = + MctpCompletionCode::try_from(buffer[2]).map_err(|_| HeaderParseError("invalid mctp completion code"))?; + + check_request_and_completion_code(request_bit, completion_code)?; + + Ok(( + MctpControlHeader { + request_bit, + datagram_bit, + instance_id, + command_code, + completion_code, + }, + &buffer[3..], + )) + } +} + +fn check_request_and_completion_code( + request_bit: bool, + completion_code: MctpCompletionCode, +) -> MctpPacketResult<(), M> { + if request_bit && completion_code != MctpCompletionCode::Success { + return Err(MctpPacketError::ProtocolError( + ProtocolError::CompletionCodeOnRequestMessage(completion_code), + )); + } + Ok(()) +} + +impl<'buf> MctpMessageTrait<'buf> for MctpControl { + type Header = MctpControlHeader; + const MESSAGE_TYPE: u8 = 0x00; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + match self { + Self::SetEndpointIdRequest(data) => copy_and_check_len(buffer, data), + Self::SetEndpointIdResponse(data) => copy_and_check_len(buffer, data), + Self::GetEndpointIdRequest => copy_and_check_len(buffer, []), + Self::GetEndpointIdResponse(data) => copy_and_check_len(buffer, data), + } + } + + fn deserialize(header: &Self::Header, buffer: &'buf [u8]) -> MctpPacketResult { + let message = match (header.request_bit, header.command_code) { + (true, MctpControlCommandCode::SetEndpointId) => Self::SetEndpointIdRequest(try_into_array(buffer)?), + (true, MctpControlCommandCode::GetEndpointId) => Self::GetEndpointIdRequest, + (false, MctpControlCommandCode::SetEndpointId) => Self::SetEndpointIdResponse(try_into_array(buffer)?), + (false, MctpControlCommandCode::GetEndpointId) => Self::GetEndpointIdResponse(try_into_array(buffer)?), + _ => { + return Err(HeaderParseError("invalid mctp control command code")); + } + }; + Ok(message) + } +} + +fn copy_and_check_len(buffer: &mut [u8], data: [u8; N]) -> MctpPacketResult { + if buffer.len() < N { + return Err(crate::MctpPacketError::SerializeError( + "buffer too small for mctp control message", + )); + } + buffer[..N].copy_from_slice(&data); + Ok(N) +} + +fn try_into_array(buffer: &[u8]) -> MctpPacketResult<[u8; N], M> { + if buffer.len() < N { + return Err(HeaderParseError("buffer too small for mctp control message")); + } + Ok(buffer[..N].try_into().unwrap()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{error::ProtocolError, test_util::TestMedium}; + + #[test] + fn header_serialize_deserialize_happy_path() { + let header = MctpControlHeader { + request_bit: true, + datagram_bit: false, + instance_id: 0b1_1111, + command_code: MctpControlCommandCode::GetEndpointId, + completion_code: MctpCompletionCode::Success, + }; + + let mut buf = [0u8; 3]; + let size = header.clone().serialize::(&mut buf).unwrap(); + assert_eq!(size, 3); + assert_eq!( + buf, + [ + 0b1000_0000 | 0b0001_1111, // rq=1, d=0, instance id=0x1F + MctpControlCommandCode::GetEndpointId as u8, + u8::from(MctpCompletionCode::Success), + ] + ); + + let (parsed, rest) = MctpControlHeader::deserialize::(&buf).unwrap(); + assert_eq!(parsed, header); + assert_eq!(rest.len(), 0); + } + + #[test] + fn header_serialize_error_on_completion_code_in_request() { + let header = MctpControlHeader { + request_bit: true, + datagram_bit: false, + instance_id: 0, + command_code: MctpControlCommandCode::SetEndpointId, + completion_code: MctpCompletionCode::Error, + }; + + let mut buf = [0u8; 3]; + let err = header.serialize::(&mut buf).unwrap_err(); + match err { + MctpPacketError::ProtocolError(ProtocolError::CompletionCodeOnRequestMessage(code)) => { + assert_eq!(code, MctpCompletionCode::Error) + } + other => panic!("unexpected error: {:?}", other), + } + } + + #[rstest::rstest] + #[case(MctpControlCommandCode::SetEndpointId, false, MctpControl::SetEndpointIdResponse([0xAA, 0xBB, 0xCC]), &[0xAA, 0xBB, 0xCC])] + #[case(MctpControlCommandCode::SetEndpointId, true, MctpControl::SetEndpointIdRequest([0xAA, 0xBB]), &[0xAA, 0xBB])] + #[case(MctpControlCommandCode::GetEndpointId, false, MctpControl::GetEndpointIdResponse([0xAA, 0xBB, 0xCC]), &[0xAA, 0xBB, 0xCC])] + #[case(MctpControlCommandCode::GetEndpointId, true, MctpControl::GetEndpointIdRequest, &[])] + fn message_serialize_deserialize_happy_path( + #[case] command_code: MctpControlCommandCode, + #[case] request_bit: bool, + #[case] message: MctpControl, + #[case] expected: &[u8], + ) { + let mut buf = [0u8; 1024]; + let size = message.clone().serialize::(&mut buf).unwrap(); + assert_eq!(size, expected.len()); + assert_eq!(&buf[..size], expected); + + let header = MctpControlHeader { + request_bit, + datagram_bit: false, + instance_id: 0, + command_code, + completion_code: MctpCompletionCode::Success, + }; + + let parsed = MctpControl::deserialize::(&header, &buf).unwrap(); + assert_eq!(parsed, message); + } + + #[test] + fn message_deserialize_error_on_invalid_command_for_header() { + // request message with unsupported command code should error + let header = MctpControlHeader { + request_bit: true, + datagram_bit: false, + instance_id: 0, + command_code: MctpControlCommandCode::Reserved, + completion_code: MctpCompletionCode::Success, + }; + + let err = MctpControl::deserialize::(&header, &[]).unwrap_err(); + match err { + MctpPacketError::HeaderParseError(msg) => { + assert_eq!(msg, "invalid mctp control command code") + } + other => panic!("unexpected error: {:?}", other), + } + } +} diff --git a/mctp-rs/src/message_type/mod.rs b/mctp-rs/src/message_type/mod.rs new file mode 100644 index 000000000..bb356dc14 --- /dev/null +++ b/mctp-rs/src/message_type/mod.rs @@ -0,0 +1,22 @@ +mod mctp_control; +mod vendor_defined_pci; + +pub use mctp_control::*; +pub use vendor_defined_pci::*; + +use crate::{MctpMedium, error::MctpPacketResult}; + +pub trait MctpMessageHeaderTrait: Sized { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult; + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M>; +} + +pub trait MctpMessageTrait<'buf>: Sized { + const MESSAGE_TYPE: u8; + type Header: MctpMessageHeaderTrait; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult; + + fn deserialize(header: &Self::Header, buffer: &'buf [u8]) -> MctpPacketResult; +} diff --git a/mctp-rs/src/message_type/vendor_defined_pci.rs b/mctp-rs/src/message_type/vendor_defined_pci.rs new file mode 100644 index 000000000..fc591088c --- /dev/null +++ b/mctp-rs/src/message_type/vendor_defined_pci.rs @@ -0,0 +1,48 @@ +use super::*; + +pub struct VendorDefinedPci<'buf>(pub &'buf [u8]); +pub struct VendorDefinedPciHeader(pub u16); +const HEADER_LEN: usize = size_of::(); + +impl MctpMessageHeaderTrait for VendorDefinedPciHeader { + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + if buffer.len() < HEADER_LEN { + return Err(crate::MctpPacketError::SerializeError( + "buffer too small for vendor defined pci header", + )); + } + buffer[..HEADER_LEN].copy_from_slice(&self.0.to_be_bytes()); + Ok(HEADER_LEN) + } + + fn deserialize(buffer: &[u8]) -> MctpPacketResult<(Self, &[u8]), M> { + if buffer.len() < HEADER_LEN { + return Err(crate::MctpPacketError::HeaderParseError( + "buffer too small for vendor defined pci header", + )); + } + let header = VendorDefinedPciHeader(u16::from_be_bytes(buffer[..HEADER_LEN].try_into().unwrap())); + Ok((header, &buffer[HEADER_LEN..])) + } +} + +impl<'buf> MctpMessageTrait<'buf> for VendorDefinedPci<'buf> { + type Header = VendorDefinedPciHeader; + const MESSAGE_TYPE: u8 = 0x7E; + + fn serialize(self, buffer: &mut [u8]) -> MctpPacketResult { + let self_len = self.0.len(); + if buffer.len() < self_len { + return Err(crate::MctpPacketError::SerializeError( + "buffer too small for vendor defined pci message", + )); + } + buffer[..self_len].copy_from_slice(self.0); + Ok(self_len) + } + + fn deserialize(_: &Self::Header, buffer: &'buf [u8]) -> MctpPacketResult, M> { + let message = VendorDefinedPci(buffer); + Ok(message) + } +} diff --git a/mctp-rs/src/serialize.rs b/mctp-rs/src/serialize.rs new file mode 100644 index 000000000..0f2d3861e --- /dev/null +++ b/mctp-rs/src/serialize.rs @@ -0,0 +1,84 @@ +use crate::{ + MctpPacketError, error::MctpPacketResult, mctp_packet_context::MctpReplyContext, + mctp_transport_header::MctpTransportHeader, medium::MctpMedium, +}; + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SerializePacketState<'buf, M: MctpMedium> { + pub(crate) medium: &'buf M, + pub(crate) reply_context: MctpReplyContext, + pub(crate) current_packet_num: u8, + pub(crate) serialized_message_header: bool, + pub(crate) message_buffer: &'buf [u8], + pub(crate) assembly_buffer: &'buf mut [u8], +} + +pub const TRANSPORT_HEADER_SIZE: usize = 4; + +impl<'buf, M: MctpMedium> SerializePacketState<'buf, M> { + pub fn next(&mut self) -> Option> { + if self.message_buffer.is_empty() { + return None; + } + + let packet = self.medium.serialize( + self.reply_context.medium_context, + self.assembly_buffer, + |buffer: &mut [u8]| { + let max_packet_size = self.medium.max_message_body_size().min(buffer.len()); + if max_packet_size < TRANSPORT_HEADER_SIZE { + return Err(MctpPacketError::SerializeError( + "assembly buffer too small for mctp transport header", + )); + } + + let message_size = (max_packet_size - TRANSPORT_HEADER_SIZE).min(self.message_buffer.len()); + + // if there is no room for any of the body, and the body is not empty, + // then return an error, otherwise we infinate loop sending packets with headers and + // no body, making it impossible to ever assemble a message + if message_size == 0 && !self.message_buffer.is_empty() { + return Err(MctpPacketError::SerializeError( + "assembly buffer too small for non-empty message body", + )); + } + + let body = &self.message_buffer[..message_size]; + self.message_buffer = &self.message_buffer[message_size..]; + + let start_of_message = if self.current_packet_num == 0 { 1 } else { 0 }; + let end_of_message = if self.message_buffer.is_empty() { 1 } else { 0 }; + let packet_sequence_number = self.reply_context.packet_sequence_number.inc(); + let transport_header: u32 = MctpTransportHeader { + reserved: 0, + header_version: 1, + start_of_message, + end_of_message, + packet_sequence_number, + tag_owner: 0, + message_tag: self.reply_context.message_tag, + source_endpoint_id: self.reply_context.destination_endpoint_id, + destination_endpoint_id: self.reply_context.source_endpoint_id, + } + .try_into() + .map_err(MctpPacketError::SerializeError)?; + + // write the transport header and message body + let mut cursor = 0; + buffer[cursor..cursor + TRANSPORT_HEADER_SIZE].copy_from_slice(&transport_header.to_be_bytes()); + cursor += TRANSPORT_HEADER_SIZE; + // message body is the rest of the buffer, up to the packet size + buffer[cursor..cursor + body.len()].copy_from_slice(body); + Ok(cursor + body.len()) + }, + ); + + // Increment packet number for next call + if packet.is_ok() { + self.current_packet_num += 1; + } + + Some(packet) + } +} diff --git a/mctp-rs/src/test_util.rs b/mctp-rs/src/test_util.rs new file mode 100644 index 000000000..f367c670d --- /dev/null +++ b/mctp-rs/src/test_util.rs @@ -0,0 +1,97 @@ +use crate::{ + MctpPacketError, + error::MctpPacketResult, + medium::{MctpMedium, MctpMediumFrame}, +}; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct TestMedium { + header: &'static [u8], + trailer: &'static [u8], + mtu: usize, +} +impl TestMedium { + pub fn new() -> Self { + Self { + header: &[], + trailer: &[], + mtu: 32, + } + } + pub fn with_headers(mut self, header: &'static [u8], trailer: &'static [u8]) -> Self { + self.header = header; + self.trailer = trailer; + self + } + pub fn with_mtu(mut self, mtu: usize) -> Self { + self.mtu = mtu; + self + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct TestMediumFrame(usize); + +impl MctpMedium for TestMedium { + type Frame = TestMediumFrame; + type Error = &'static str; + type ReplyContext = (); + + fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { + let packet_len = packet.len(); + + // check that header / trailer is present and correct + if packet.len() < self.header.len() + self.trailer.len() { + return Err(MctpPacketError::MediumError("packet too short")); + } + if packet[0..self.header.len()] != *self.header { + return Err(MctpPacketError::MediumError("header mismatch")); + } + if packet[packet_len - self.trailer.len()..packet_len] != *self.trailer { + return Err(MctpPacketError::MediumError("trailer mismatch")); + } + + let packet = &packet[self.header.len()..packet_len - self.trailer.len()]; + Ok((TestMediumFrame(packet_len), packet)) + } + fn max_message_body_size(&self) -> usize { + self.mtu + } + fn serialize<'buf, F>( + &self, + _: Self::ReplyContext, + buffer: &'buf mut [u8], + message_writer: F, + ) -> MctpPacketResult<&'buf [u8], Self> + where + F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, + { + let header_len = self.header.len(); + let trailer_len = self.trailer.len(); + + // Ensure buffer can fit at least headers and trailers + if buffer.len() < header_len + trailer_len { + return Err(MctpPacketError::MediumError("Buffer too small for headers")); + } + + // Calculate available space for message (respecting MTU) + let max_packet_size = self.mtu.min(buffer.len()); + if max_packet_size < header_len + trailer_len { + return Err(MctpPacketError::MediumError("MTU too small for headers")); + } + let max_message_size = max_packet_size - header_len - trailer_len; + + buffer[0..header_len].copy_from_slice(self.header); + let size = message_writer(&mut buffer[header_len..header_len + max_message_size])?; + let len = header_len + size; + buffer[len..len + trailer_len].copy_from_slice(self.trailer); + Ok(&buffer[..len + trailer_len]) + } +} + +impl MctpMediumFrame for TestMediumFrame { + fn packet_size(&self) -> usize { + self.0 + } + fn reply_context(&self) -> ::ReplyContext {} +} diff --git a/supply-chain/config.toml b/supply-chain/config.toml index bbca560d0..7acb2d508 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -174,6 +174,10 @@ criteria = "safe-to-deploy" version = "1.0.7" criteria = "safe-to-deploy" +[[exemptions.diff]] +version = "0.1.13" +criteria = "safe-to-run" + [[exemptions.embassy-embedded-hal]] version = "0.5.0" criteria = "safe-to-deploy" @@ -258,6 +262,22 @@ criteria = "safe-to-deploy" version = "2.0.0" criteria = "safe-to-deploy" +[[exemptions.futures-macro]] +version = "0.3.31" +criteria = "safe-to-run" + +[[exemptions.futures-task]] +version = "0.3.32" +criteria = "safe-to-run" + +[[exemptions.futures-timer]] +version = "3.0.3" +criteria = "safe-to-run" + +[[exemptions.futures-util]] +version = "0.3.31" +criteria = "safe-to-run" + [[exemptions.generator]] version = "0.8.5" criteria = "safe-to-deploy" @@ -270,6 +290,10 @@ criteria = "safe-to-deploy" version = "0.14.5" criteria = "safe-to-deploy" +[[exemptions.hashbrown]] +version = "0.17.0" +criteria = "safe-to-deploy" + [[exemptions.hashlink]] version = "0.9.1" criteria = "safe-to-deploy" @@ -286,6 +310,10 @@ criteria = "safe-to-deploy" version = "2.11.0" criteria = "safe-to-deploy" +[[exemptions.indexmap]] +version = "2.14.0" +criteria = "safe-to-deploy" + [[exemptions.io-uring]] version = "0.7.10" criteria = "safe-to-run" @@ -350,6 +378,18 @@ criteria = "safe-to-deploy" version = "1.1.10" criteria = "safe-to-deploy" +[[exemptions.pin-utils]] +version = "0.1.0" +criteria = "safe-to-run" + +[[exemptions.pretty_assertions]] +version = "1.4.1" +criteria = "safe-to-run" + +[[exemptions.proc-macro-crate]] +version = "3.5.0" +criteria = "safe-to-run" + [[exemptions.proc-macro-error-attr2]] version = "2.0.0" criteria = "safe-to-deploy" @@ -374,6 +414,10 @@ criteria = "safe-to-deploy" version = "0.9.0" criteria = "safe-to-deploy" +[[exemptions.semver]] +version = "1.0.28" +criteria = "safe-to-run" + [[exemptions.semver-parser]] version = "0.7.0" criteria = "safe-to-deploy" @@ -398,10 +442,22 @@ criteria = "safe-to-deploy" version = "0.6.11" criteria = "safe-to-deploy" +[[exemptions.toml_datetime]] +version = "1.1.1+spec-1.1.0" +criteria = "safe-to-run" + [[exemptions.toml_edit]] version = "0.22.27" criteria = "safe-to-deploy" +[[exemptions.toml_edit]] +version = "0.25.11+spec-1.1.0" +criteria = "safe-to-run" + +[[exemptions.toml_parser]] +version = "1.1.2+spec-1.1.0" +criteria = "safe-to-run" + [[exemptions.typenum]] version = "1.18.0" criteria = "safe-to-deploy" @@ -438,6 +494,10 @@ criteria = "safe-to-deploy" version = "0.7.13" criteria = "safe-to-deploy" +[[exemptions.winnow]] +version = "1.0.2" +criteria = "safe-to-run" + [[exemptions.wyz]] version = "0.5.1" criteria = "safe-to-deploy" @@ -445,3 +505,7 @@ criteria = "safe-to-deploy" [[exemptions.yaml-rust2]] version = "0.9.0" criteria = "safe-to-deploy" + +[[exemptions.yansi]] +version = "1.0.1" +criteria = "safe-to-run" From 77905ee4ae172a2e29669d5ba10fddddce09fca7 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Wed, 6 May 2026 14:12:08 -0700 Subject: [PATCH 74/79] type-c-service/wrapper: Break-out existing wrapper code into per-port structs (#818) This PR moves the existing logic from the `wrapper` module, which manages multiple ports on a PD controller, into individual structs for each port. This is done in several steps: * Remaining power policy messaging is removed in favor of function calls * `EventReceiver` struct is broken up into per-port event receivers * Logic from `wrapper` is moved into the existing port proxy structs. * Module structure is simplified and port proxy structs renamed to just `Port`. --- Cargo.lock | 8 +- examples/rt685s-evk/Cargo.lock | 8 +- examples/rt685s-evk/src/bin/type_c.rs | 232 ++-- examples/rt685s-evk/src/bin/type_c_cfu.rs | 227 ++-- examples/std/src/bin/power_policy.rs | 12 +- examples/std/src/bin/type_c/service.rs | 189 ++- examples/std/src/bin/type_c/ucsi.rs | 265 ++--- examples/std/src/bin/type_c/unconstrained.rs | 324 +++--- .../std/src/lib/type_c/mock_controller.rs | 8 +- power-policy-interface/src/psu/event.rs | 25 - power-policy-interface/src/psu/mod.rs | 54 +- power-policy-service/src/service/consumer.rs | 7 - power-policy-service/src/service/mod.rs | 73 +- power-policy-service/src/service/provider.rs | 2 +- power-policy-service/tests/common/mock.rs | 17 +- supply-chain/audits.toml | 10 + supply-chain/config.toml | 428 ------- supply-chain/imports.lock | 1028 +++++++++-------- type-c-interface/src/service/event.rs | 18 +- .../src/{wrapper => controller}/config.rs | 0 type-c-service/src/controller/event.rs | 22 + .../src/controller/event_receiver.rs | 140 +++ type-c-service/src/controller/macros.rs | 170 +++ type-c-service/src/controller/mod.rs | 242 ++++ type-c-service/src/controller/pd.rs | 84 ++ type-c-service/src/controller/power.rs | 172 +++ type-c-service/src/controller/state.rs | 23 + type-c-service/src/driver/tps6699x.rs | 16 +- type-c-service/src/lib.rs | 2 +- type-c-service/src/service/mod.rs | 5 +- type-c-service/src/task.rs | 29 +- type-c-service/src/wrapper/backing.rs | 189 --- type-c-service/src/wrapper/dp.rs | 24 - type-c-service/src/wrapper/event_receiver.rs | 211 ---- type-c-service/src/wrapper/message.rs | 106 -- type-c-service/src/wrapper/mod.rs | 402 ------- type-c-service/src/wrapper/pd.rs | 57 - type-c-service/src/wrapper/power.rs | 126 -- type-c-service/src/wrapper/proxy.rs | 128 -- type-c-service/src/wrapper/vdm.rs | 47 - 40 files changed, 2055 insertions(+), 3075 deletions(-) rename type-c-service/src/{wrapper => controller}/config.rs (100%) create mode 100644 type-c-service/src/controller/event.rs create mode 100644 type-c-service/src/controller/event_receiver.rs create mode 100644 type-c-service/src/controller/macros.rs create mode 100644 type-c-service/src/controller/mod.rs create mode 100644 type-c-service/src/controller/pd.rs create mode 100644 type-c-service/src/controller/power.rs create mode 100644 type-c-service/src/controller/state.rs delete mode 100644 type-c-service/src/wrapper/backing.rs delete mode 100644 type-c-service/src/wrapper/dp.rs delete mode 100644 type-c-service/src/wrapper/event_receiver.rs delete mode 100644 type-c-service/src/wrapper/message.rs delete mode 100644 type-c-service/src/wrapper/mod.rs delete mode 100644 type-c-service/src/wrapper/pd.rs delete mode 100644 type-c-service/src/wrapper/power.rs delete mode 100644 type-c-service/src/wrapper/proxy.rs delete mode 100644 type-c-service/src/wrapper/vdm.rs diff --git a/Cargo.lock b/Cargo.lock index e9877c6e8..54eb00893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,9 +495,9 @@ dependencies = [ [[package]] name = "device-driver" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" +checksum = "c2e4547bd66511372d2a38ac3c1b2892c7ebf83cf0d5411c3406e496c85a1d96" dependencies = [ "defmt 0.3.100", "embedded-io 0.6.1", @@ -862,7 +862,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#21d0e228d21ddc6ccaeffc01d98ef9a5b87941ef" dependencies = [ "aquamarine", "bincode", @@ -2175,7 +2175,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.2", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 34391866e..24458c167 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -334,9 +334,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 = [ "defmt 0.3.100", "embedded-io 0.6.1", @@ -676,7 +676,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#21d0e228d21ddc6ccaeffc01d98ef9a5b87941ef" dependencies = [ "aquamarine", "bincode", @@ -1427,7 +1427,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", diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 4f6981990..cba3c6c60 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] - -use ::tps6699x::{ADDR1, TPS66994_NUM_PORTS}; +use ::tps6699x::ADDR1; +use ::tps6699x::asynchronous::embassy::interrupt::InterruptReceiver; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_executor::Spawner; use embassy_imxrt::gpio::{Input, Inverter, Pull}; @@ -15,58 +15,65 @@ use embassy_time::{self as _, Delay}; use embedded_services::GlobalRawMutex; use embedded_services::event::MapSender; use embedded_services::{error, info}; -use embedded_usb_pd::GlobalPortId; +use embedded_usb_pd::{GlobalPortId, 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::port::ControllerId; use type_c_interface::port::PortRegistration; +use type_c_interface::port::event::PortEventBitfield; +use type_c_interface::port::{ControllerId, Device}; 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::driver::tps6699x::{self as tps6699x_drv, InterruptReceiver}; -use type_c_service::service::{EventReceiver, Service}; -use type_c_service::wrapper::ControllerWrapper; -use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; -use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; -use type_c_service::wrapper::proxy::PowerProxyDevice; +use type_c_service::controller::Port; +use type_c_service::controller::event_receiver::{ + EventReceiver as PortEventReceiver, InterruptReceiver as _, 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::driver::tps6699x::{self as tps6699x_drv}; +use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; extern crate rt685s_evk_example; const CHANNEL_CAPACITY: usize = 4; -const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -type DeviceType = Mutex>; -type ChargerType = power_policy_interface::charger::mock::ChargerType; - bind_interrupts!(struct Irqs { FLEXCOMM2 => embassy_imxrt::i2c::InterruptHandler; }); +type SharedStateType = Mutex; +type PortType = Mutex< + GlobalRawMutex, + Port< + 'static, + Tps6699xMutex<'static>, + SharedStateType, + DynamicSender<'static, power_policy_interface::psu::event::EventData>, + DynamicSender<'static, type_c_service::controller::event::Loopback>, + >, +>; +type ChargerType = power_policy_interface::charger::mock::ChargerType; + type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper< - 'a, - GlobalRawMutex, - Tps6699xMutex<'a>, - DynamicSender<'a, power_policy_interface::psu::event::EventData>, ->; type Controller<'a> = tps6699x::controller::Controller>; type InterruptProcessor<'a> = tps6699x::interrupt::InterruptProcessor<'a, GlobalRawMutex, BusDevice<'a>>; type PowerPolicySenderType = MapSender< - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, power_policy_interface::service::event::EventData, DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, fn( - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, ) -> power_policy_interface::service::event::EventData, >; @@ -76,11 +83,17 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1, ChargerType, 0>, + ArrayRegistration<'static, PortType, 2, PowerPolicySenderType, 1, ChargerType, 0>, >, >; type ServiceType = Service<'static>; +type PortEventReceiverType = PortEventReceiver< + 'static, + SharedStateType, + DynamicReceiver<'static, PortEventBitfield>, + DynamicReceiver<'static, type_c_service::controller::event::Loopback>, +>; #[embassy_executor::task] async fn bridge_task( @@ -94,29 +107,15 @@ async fn bridge_task( } } -#[embassy_executor::task] -async fn pd_controller_task( - mut event_receiver: ArrayPortEventReceivers< - 'static, - 2, - InterruptReceiver<'static, GlobalRawMutex, BusDevice<'static>>, - >, - wrapper: &'static Wrapper<'static>, -) { +#[embassy_executor::task(pool_size = 2)] +async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { + port.lock().await.sync_state().await.unwrap(); + loop { let event = event_receiver.wait_event().await; - - let output = wrapper - .process_event(&mut event_receiver.sink_ready_timeout, event) - .await; + let output = port.lock().await.process_event(event).await; if let Err(e) = output { error!("Error processing event: {:?}", e); - continue; - } - - let output = output.unwrap(); - if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { - error!("Error finalizing output: {:?}", e); } } } @@ -126,9 +125,20 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: InterruptProc tps6699x::task::interrupt_task(&mut int_in, &mut [&mut interrupt]).await; } +#[embassy_executor::task] +async fn interrupt_splitter_task( + mut interrupt_receiver: InterruptReceiver<'static, GlobalRawMutex, BusDevice<'static>>, + mut interrupt_splitter: PortEventSplitter<2, DynamicSender<'static, PortEventBitfield>>, +) -> ! { + loop { + let interrupts = interrupt_receiver.wait_interrupt().await; + interrupt_splitter.process_interrupts(interrupts).await; + } +} + #[embassy_executor::task] async fn power_policy_task( - psu_events: PsuEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 2, PortType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::psu_task(psu_events, power_policy).await; @@ -137,10 +147,9 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( service: &'static Mutex, - event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, ) { - type_c_service::task::task(service, event_receiver, wrappers).await; + type_c_service::task::task(service, event_receiver).await; } #[embassy_executor::main] @@ -186,53 +195,6 @@ async fn main(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); - static PORT0_CHANNEL: Channel = Channel::new(); - static PORT1_CHANNEL: Channel = Channel::new(); - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - controller_context, - CONTROLLER0_ID, - [ - 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 POLICY_CHANNEL0: StaticCell> = StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - - let (intermediate, power_event_receivers) = storage - .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"); - static INTERMEDIATE: StaticCell< - IntermediateStorage>, - > = StaticCell::new(); - let intermediate = INTERMEDIATE.init(intermediate); - - static REFERENCED: StaticCell< - ReferencedStorage>, - > = StaticCell::new(); - let referenced = REFERENCED.init( - intermediate - .try_create_referenced() - .expect("Failed to create referenced storage"), - ); - info!("Spawining PD controller task"); static CONTROLLER_MUTEX: StaticCell> = StaticCell::new(); let controller_mutex = CONTROLLER_MUTEX.init(Mutex::new(tps6699x_drv::tps66994( @@ -242,14 +204,62 @@ async fn main(spawner: Spawner) { "tps6699x_0", ))); - static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = WRAPPER.init(ControllerWrapper::new( + 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); + + define_controller_port_static_cell_channel!(pub(self), port1, GlobalRawMutex, Tps6699xMutex<'static>); + let PortComponents { + port: port1, + power_policy_receiver: policy_receiver1, + event_receiver: event_receiver1, + interrupt_sender: port1_interrupt_sender, + } = port1::create( + "PD1", + LocalPortId(0), + PORT1_ID, Default::default(), - referenced, - )); - let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); - let bridge = Bridge::new(controller_mutex, &referenced.pd_controller); + controller_mutex, + controller_context, + ); + + let port_event_splitter = PortEventSplitter::new([port0_interrupt_sender, port1_interrupt_sender]); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< @@ -264,7 +274,7 @@ async fn main(spawner: Spawner) { // Create power policy service let power_policy_registration = ArrayRegistration { - psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + psus: [port0, port1], chargers: [], service_senders: [power_policy_sender], }; @@ -282,8 +292,7 @@ async fn main(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper], + ServiceEventReceiver::new(controller_context, power_policy_subscriber), ) .expect("Failed to create type-c service task"), ); @@ -291,24 +300,19 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.spawn( power_policy_task( - PsuEventReceivers::new( - [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], - [policy_receiver0, policy_receiver1], - ), + PsuEventReceivers::new([port0, port1], [policy_receiver0, policy_receiver1]), power_service, ) .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")); + spawner.spawn( - pd_controller_task( - ArrayPortEventReceivers::new( - InterruptReceiver::new(interrupt_receiver), - power_event_receivers, - ), - wrapper, - ) - .expect("Failed to create pd controller task"), + interrupt_splitter_task(interrupt_receiver, port_event_splitter) + .expect("Failed to spawn interrupt splitter task"), ); } diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index d308229df..1a9355bf4 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -1,7 +1,8 @@ #![no_std] #![no_main] -use ::tps6699x::{ADDR1, TPS66994_NUM_PORTS}; +use ::tps6699x::ADDR1; +use ::tps6699x::asynchronous::embassy::interrupt::InterruptReceiver; use cfu_service::CfuClient; use cfu_service::component::{CfuDevice, InternalResponseData, RequestData}; use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; @@ -21,22 +22,26 @@ use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferRe use embedded_services::GlobalRawMutex; use embedded_services::event::MapSender; use embedded_services::{error, info}; -use embedded_usb_pd::GlobalPortId; +use embedded_usb_pd::{GlobalPortId, 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::port::{ControllerId, PortRegistration}; +use type_c_interface::port::event::PortEventBitfield; +use type_c_interface::port::{ControllerId, 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::driver::tps6699x::{self as tps6699x_drv, InterruptReceiver}; -use type_c_service::service::{EventReceiver, Service}; -use type_c_service::wrapper::ControllerWrapper; -use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; -use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; -use type_c_service::wrapper::proxy::PowerProxyDevice; +use type_c_service::controller::Port; +use type_c_service::controller::event_receiver::{ + EventReceiver as PortEventReceiver, InterruptReceiver as _, PortEventSplitter, +}; +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}; extern crate rt685s_evk_example; @@ -55,27 +60,31 @@ impl cfu_service::customization::Customization for CfuCustomization { } } -type DeviceType = Mutex>; +type PortSharedStateType = Mutex; +type PortType = Mutex< + GlobalRawMutex, + Port< + 'static, + Tps6699xMutex<'static>, + PortSharedStateType, + DynamicSender<'static, power_policy_interface::psu::event::EventData>, + DynamicSender<'static, type_c_service::controller::event::Loopback>, + >, +>; type ChargerType = power_policy_interface::charger::mock::ChargerType; type BusMaster<'a> = I2cMaster<'a, Async>; type BusDevice<'a> = I2cDevice<'a, GlobalRawMutex, BusMaster<'a>>; type Tps6699xMutex<'a> = Mutex>>; -type Wrapper<'a> = ControllerWrapper< - 'a, - GlobalRawMutex, - Tps6699xMutex<'a>, - DynamicSender<'a, power_policy_interface::psu::event::EventData>, ->; type Controller<'a> = tps6699x::controller::Controller>; type InterruptProcessor<'a> = tps6699x::interrupt::InterruptProcessor<'a, GlobalRawMutex, BusDevice<'a>>; type PowerPolicySenderType = MapSender< - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, power_policy_interface::service::event::EventData, DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, fn( - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, ) -> power_policy_interface::service::event::EventData, >; @@ -85,16 +94,21 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1, ChargerType, 0>, + ArrayRegistration<'static, PortType, 2, PowerPolicySenderType, 1, ChargerType, 0>, >, >; type ServiceType = Service<'static>; +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 NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -112,28 +126,15 @@ async fn bridge_task( } } -#[embassy_executor::task] -async fn pd_controller_task( - mut event_receiver: ArrayPortEventReceivers< - 'static, - 2, - InterruptReceiver<'static, GlobalRawMutex, BusDevice<'static>>, - >, - wrapper: &'static Wrapper<'static>, -) { +#[embassy_executor::task(pool_size = 2)] +async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { + port.lock().await.sync_state().await.unwrap(); + loop { let event = event_receiver.wait_event().await; - - let output = wrapper - .process_event(&mut event_receiver.sink_ready_timeout, event) - .await; + let output = port.lock().await.process_event(event).await; if let Err(e) = output { error!("Error processing event: {:?}", e); - continue; - } - let output = output.unwrap(); - if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { - error!("Error finalizing output: {:?}", e); } } } @@ -155,6 +156,17 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: InterruptProc tps6699x::task::interrupt_task(&mut int_in, &mut [&mut interrupt]).await; } +#[embassy_executor::task] +async fn interrupt_splitter_task( + mut interrupt_receiver: InterruptReceiver<'static, GlobalRawMutex, BusDevice<'static>>, + mut interrupt_splitter: PortEventSplitter<2, DynamicSender<'static, PortEventBitfield>>, +) -> ! { + loop { + let interrupts = interrupt_receiver.wait_interrupt().await; + interrupt_splitter.process_interrupts(interrupts).await; + } +} + #[embassy_executor::task] async fn fw_update_task(cfu_client: &'static CfuClient) { Timer::after_millis(1000).await; @@ -238,7 +250,7 @@ async fn fw_update_task(cfu_client: &'static CfuClient) { #[embassy_executor::task] async fn power_policy_task( - psu_events: PsuEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 2, PortType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::psu_task(psu_events, power_policy).await; @@ -247,10 +259,9 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( service: &'static Mutex, - event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, ) { - type_c_service::task::task(service, event_receiver, wrappers).await; + type_c_service::task::task(service, event_receiver).await; } #[embassy_executor::main] @@ -296,57 +307,6 @@ async fn main(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); - static PORT0_CHANNEL: Channel = Channel::new(); - static PORT1_CHANNEL: Channel = Channel::new(); - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - controller_context, - CONTROLLER0_ID, - [ - 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 POLICY_CHANNEL0: StaticCell> = StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - - let (intermediate, power_event_receivers) = storage - .try_create_intermediate([("Pd0", policy_sender0), ("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"); - static INTERMEDIATE: StaticCell< - IntermediateStorage>, - > = StaticCell::new(); - let intermediate = INTERMEDIATE.init(intermediate); - - static REFERENCED: StaticCell< - ReferencedStorage< - TPS66994_NUM_PORTS, - GlobalRawMutex, - DynamicSender<'_, power_policy_interface::psu::event::EventData>, - >, - > = StaticCell::new(); - let referenced = REFERENCED.init( - intermediate - .try_create_referenced() - .expect("Failed to create referenced storage"), - ); - info!("Spawining PD controller task"); static CONTROLLER_MUTEX: StaticCell> = StaticCell::new(); let controller_mutex = CONTROLLER_MUTEX.init(Mutex::new(tps6699x_drv::tps66994( @@ -356,6 +316,27 @@ 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)); @@ -373,17 +354,45 @@ 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(); let cfu_client = CfuClient::new(&CFU_CLIENT).await; cfu_client.register_device(cfu_device).unwrap(); - let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); - let bridge = Bridge::new(controller_mutex, &referenced.pd_controller); + 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, + ); - static WRAPPER: StaticCell = StaticCell::new(); - let wrapper = WRAPPER.init(ControllerWrapper::new(controller_mutex, Default::default(), referenced)); + define_controller_port_static_cell_channel!(pub(self),port1, GlobalRawMutex, Tps6699xMutex<'static>); + let PortComponents { + port: port1, + 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, + ); + + let port_event_splitter = PortEventSplitter::new([port0_interrupt_sender, port1_interrupt_sender]); // Create power policy service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -398,7 +407,7 @@ async fn main(spawner: Spawner) { let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); let power_policy_registration = ArrayRegistration { - psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + psus: [port0, port1], chargers: [], service_senders: [power_policy_sender], }; @@ -416,8 +425,7 @@ async fn main(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper], + ServiceEventReceiver::new(controller_context, power_policy_subscriber), ) .expect("Failed to spawn type-c service task"), ); @@ -425,25 +433,20 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.spawn( power_policy_task( - PsuEventReceivers::new( - [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], - [policy_receiver0, policy_receiver1], - ), + PsuEventReceivers::new([port0, port1], [policy_receiver0, policy_receiver1]), power_service, ) .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")); + spawner.spawn( - pd_controller_task( - ArrayPortEventReceivers::new( - InterruptReceiver::new(interrupt_receiver), - power_event_receivers, - ), - wrapper, - ) - .expect("Failed to create PD controller task"), + interrupt_splitter_task(interrupt_receiver, port_event_splitter) + .expect("Failed to spawn interrupt splitter task"), ); spawner.spawn(cfu_updater_task(cfu_event_receiver, cfu_updater).expect("Failed to create CFU updater task")); diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 9df1dfbb5..763e106fd 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -60,18 +60,21 @@ impl<'a> ExampleDevice<'a> { } pub async fn simulate_attach(&mut self) { + self.state.attach().unwrap(); self.sender .send(power_policy_interface::psu::event::EventData::Attached) .await; } pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option) { + self.state.update_consumer_power_capability(capability).unwrap(); self.sender .send(power_policy_interface::psu::event::EventData::UpdatedConsumerCapability(capability)) .await; } pub async fn simulate_detach(&mut self) { + self.state.detach(); self.sender .send(power_policy_interface::psu::event::EventData::Detached) .await; @@ -81,6 +84,9 @@ impl<'a> ExampleDevice<'a> { &mut self, capability: Option, ) { + self.state + .update_requested_provider_power_capability(capability) + .unwrap(); self.sender .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) .await @@ -90,17 +96,17 @@ impl<'a> ExampleDevice<'a> { impl Psu for ExampleDevice<'_> { async fn disconnect(&mut self) -> Result<(), Error> { debug!("ExampleDevice disconnect"); - Ok(()) + self.state.disconnect(false) } async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { debug!("ExampleDevice connect_provider with {capability:?}"); - Ok(()) + self.state.connect_provider(capability) } async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { debug!("ExampleDevice connect_consumer with {capability:?}"); - Ok(()) + self.state.connect_consumer(capability) } fn state(&self) -> &psu::State { diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index 6d24fbe6b..e05b2f18e 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,43 +5,46 @@ 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; 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; use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; -use std_examples::type_c::mock_controller; -use std_examples::type_c::mock_controller::Wrapper; -use type_c_interface::port::{ControllerId, PortRegistration}; +use std_examples::type_c::mock_controller::Port; +use std_examples::type_c::mock_controller::{self, InterruptReceiver}; +use type_c_interface::port::event::PortEventBitfield; +use type_c_interface::port::{ControllerId, 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::config::Config; -use type_c_service::service::{EventReceiver, Service}; +use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; use type_c_service::util::power_capability_from_current; -use type_c_service::wrapper::backing::Storage; -use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; -use type_c_service::wrapper::message::*; -use type_c_service::wrapper::proxy::PowerProxyDevice; -const NUM_PD_CONTROLLERS: usize = 1; const CHANNEL_CAPACITY: usize = 4; const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); const DELAY_MS: u64 = 1000; -type DeviceType = Mutex>; +type ControllerType = Mutex>; +type PortType = Mutex>; type PowerPolicySenderType = MapSender< - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, power_policy_interface::service::event::EventData, DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, fn( - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, ) -> power_policy_interface::service::event::EventData, >; @@ -51,38 +54,43 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 1, PowerPolicySenderType, 1, ChargerType, 0>, + ArrayRegistration<'static, PortType, 1, PowerPolicySenderType, 1, ChargerType, 0>, >, >; type ServiceType = Service<'static>; +type SharedStateType = Mutex; +type PortEventReceiverType = PortEventReceiver< + 'static, + SharedStateType, + DynamicReceiver<'static, PortEventBitfield>, + DynamicReceiver<'static, type_c_service::controller::event::Loopback>, +>; #[embassy_executor::task] -async fn controller_task( - mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, - wrapper: &'static Wrapper<'static>, - controller: &'static Mutex>, -) { - controller.lock().await.custom_function(); - +async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { loop { let event = event_receiver.wait_event().await; - - let output = wrapper - .process_event(&mut event_receiver.sink_ready_timeout, event) - .await; + let output = port.lock().await.process_event(event).await; if let Err(e) = output { error!("Error processing event: {e:?}"); } let output = output.unwrap(); - if let Output::PdAlert(OutputPdAlert { port, ado }) = &output { - info!("Port{}: PD alert received: {:?}", port.0, ado); + if let Some(ServicePortEventData::Alert(ado)) = &output { + info!("PD alert received: {:?}", ado); } + } +} - if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { - error!("Error finalizing output: {e:?}"); - } +#[embassy_executor::task] +async fn interrupt_splitter_task( + mut interrupt_receiver: InterruptReceiver<'static>, + mut interrupt_splitter: PortEventSplitter<1, DynamicSender<'static, PortEventBitfield>>, +) -> ! { + loop { + let interrupts = interrupt_receiver.wait_interrupt().await; + interrupt_splitter.process_interrupts(interrupts).await; } } @@ -109,70 +117,40 @@ async fn task(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(mock_controller::ControllerState::new()); - static PORT0_CHANNEL: Channel = Channel::new(); - - static STORAGE: StaticCell> = StaticCell::new(); - let storage = STORAGE.init(Storage::new( - controller_context, - CONTROLLER0_ID, - [PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }], - )); - - static POLICY_CHANNEL: StaticCell> = - StaticCell::new(); - let policy_channel = POLICY_CHANNEL.init(Channel::new()); - - let policy_sender = policy_channel.dyn_sender(); - let policy_receiver = policy_channel.dyn_receiver(); - - let (intermediate, power_event_receivers) = storage - .try_create_intermediate([("Pd0", policy_sender)]) - .expect("Failed to create intermediate storage"); - - static INTERMEDIATE: StaticCell< - type_c_service::wrapper::backing::IntermediateStorage< - 1, - GlobalRawMutex, - DynamicSender<'static, psu::event::EventData>, - >, - > = StaticCell::new(); - let intermediate = INTERMEDIATE.init(intermediate); - - static REFERENCED: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, psu::event::EventData>, - >, - > = StaticCell::new(); - let referenced = REFERENCED.init( - intermediate - .try_create_referenced() - .expect("Failed to create referenced storage"), - ); - - let event_receiver = ArrayPortEventReceivers::new( - state.create_interrupt_receiver(), - power_event_receivers, - ); - - static CONTROLLER: StaticCell> = StaticCell::new(); + static CONTROLLER: StaticCell = StaticCell::new(); let controller = CONTROLLER.init(Mutex::new(mock_controller::Controller::new(state))); - static WRAPPER: StaticCell = StaticCell::new(); - - let wrapper = WRAPPER.init(mock_controller::Wrapper::new( - controller, + 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); + + define_controller_port_static_cell_channel!(pub(self), port, GlobalRawMutex, Mutex>); + let PortComponents { + port, + power_policy_receiver, + event_receiver, + interrupt_sender: port_interrupt_sender, + } = port::create( + "PD0", + LocalPortId(0), + PORT0_ID, Default::default(), - referenced, - )); - - let bridge_receiver = BridgeEventReceiver::new(&referenced.pd_controller); - let bridge = Bridge::new(controller, &referenced.pd_controller); + controller, + controller_context, + ); // Create type-c service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -187,7 +165,7 @@ async fn task(spawner: Spawner) { let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); let power_policy_registration = ArrayRegistration { - psus: [&wrapper.ports[0].proxy], + psus: [port], service_senders: [power_policy_sender], chargers: [], }; @@ -203,23 +181,27 @@ async fn task(spawner: Spawner) { // Spin up power policy service spawner.spawn( - power_policy_psu_task( - PsuEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), - power_service, - ) - .expect("Failed to create power policy task"), + power_policy_psu_task(PsuEventReceivers::new([port], [power_policy_receiver]), power_service) + .expect("Failed to create power policy task"), ); spawner.spawn( type_c_service_task( type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper], + ServiceEventReceiver::new(controller_context, 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(controller_task(event_receiver, wrapper, controller).expect("Failed to create controller task")); + spawner.spawn(port_task(event_receiver, port).expect("Failed to create controller task")); + + spawner.spawn( + interrupt_splitter_task( + state.create_interrupt_receiver(), + PortEventSplitter::new([port_interrupt_sender]), + ) + .expect("Failed to create interrupt splitter task"), + ); Timer::after_millis(1000).await; info!("Simulating connection"); @@ -247,7 +229,7 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_psu_task( - psu_events: PsuEventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 1, PortType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::psu_task(psu_events, power_policy).await; @@ -256,11 +238,10 @@ async fn power_policy_psu_task( #[embassy_executor::task] async fn type_c_service_task( service: &'static Mutex, - event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, ) { info!("Starting type-c task"); - type_c_service::task::task(service, event_receiver, wrappers).await; + type_c_service::task::task(service, event_receiver).await; } fn main() { diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 2e7ad82a1..04f126293 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -1,20 +1,19 @@ #![allow(unused_imports)] -use crate::mock_controller::Wrapper; use cfu_service::CfuClient; use embassy_executor::{Executor, Spawner}; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::{DynImmediatePublisher, DynSubscriber, PubSubChannel}; -use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; use embedded_services::event::MapSender; -use embedded_usb_pd::GlobalPortId; +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; use embedded_usb_pd::ucsi::ppm::get_capability::ResponseData as UcsiCapabilities; use embedded_usb_pd::ucsi::ppm::set_notification_enable::NotificationEnable; use embedded_usb_pd::ucsi::{Command, lpm, ppm}; +use embedded_usb_pd::{GlobalPortId, LocalPortId}; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::charger::mock::ChargerType; @@ -22,17 +21,21 @@ use power_policy_interface::psu; use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; -use std_examples::type_c::mock_controller; -use type_c_interface::port::{ControllerId, PortRegistration}; +use std_examples::type_c::mock_controller::{self, InterruptReceiver, Port}; +use type_c_interface::port::event::PortEventBitfield; +use type_c_interface::port::{ControllerId, Device, PortRegistration}; use type_c_interface::service::context::Context; -use type_c_interface::service::event::PortEvent as ServicePortEvent; +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::config::Config; -use type_c_service::service::{EventReceiver, Service}; -use type_c_service::wrapper::backing::Storage; -use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; -use type_c_service::wrapper::proxy::PowerProxyDevice; +use type_c_service::service::{EventReceiver as ServiceEventReceiver, Service}; const CHANNEL_CAPACITY: usize = 4; const NUM_PD_CONTROLLERS: usize = 2; @@ -41,14 +44,15 @@ const CONTROLLER1_ID: ControllerId = ControllerId(1); const PORT0_ID: GlobalPortId = GlobalPortId(0); const PORT1_ID: GlobalPortId = GlobalPortId(1); -type DeviceType = Mutex>; +type ControllerType = Mutex>; +type PortType = Mutex>; type PowerPolicySenderType = MapSender< - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, power_policy_interface::service::event::EventData, DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, fn( - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, ) -> power_policy_interface::service::event::EventData, >; @@ -58,11 +62,18 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 2, PowerPolicySenderType, 1, ChargerType, 0>, + ArrayRegistration<'static, PortType, 2, PowerPolicySenderType, 1, ChargerType, 0>, >, >; type ServiceType = Service<'static>; +type SharedStateType = Mutex; +type PortEventReceiverType = PortEventReceiver< + 'static, + SharedStateType, + DynamicReceiver<'static, PortEventBitfield>, + DynamicReceiver<'static, type_c_service::controller::event::Loopback>, +>; #[embassy_executor::task] async fn opm_task(_context: &'static Context, _state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { @@ -209,29 +220,30 @@ async fn bridge_task( } #[embassy_executor::task(pool_size = 2)] -async fn wrapper_task( - mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, - wrapper: &'static mock_controller::Wrapper<'static>, -) { +async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { loop { let event = event_receiver.wait_event().await; - - let output = wrapper - .process_event(&mut event_receiver.sink_ready_timeout, event) - .await; + let output = port.lock().await.process_event(event).await; if let Err(e) = output { error!("Error processing event: {e:?}"); } - let output = output.unwrap(); - if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { - error!("Error finalizing output: {e:#?}"); - } + } +} + +#[embassy_executor::task(pool_size = 2)] +async fn interrupt_splitter_task( + mut interrupt_receiver: InterruptReceiver<'static>, + mut interrupt_splitter: PortEventSplitter<1, DynamicSender<'static, PortEventBitfield>>, +) -> ! { + loop { + let interrupts = interrupt_receiver.wait_interrupt().await; + interrupt_splitter.process_interrupts(interrupts).await; } } #[embassy_executor::task] async fn power_policy_task( - psu_events: PsuEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 2, PortType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::psu_task(psu_events, power_policy).await; @@ -240,11 +252,9 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( service: &'static Mutex, - event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, ) { - info!("Starting type-c task"); - type_c_service::task::task(service, event_receiver, wrappers).await; + type_c_service::task::task(service, event_receiver).await; } #[embassy_executor::task] @@ -256,122 +266,77 @@ async fn task(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(Context::new()); - static PORT0_CHANNEL: Channel = Channel::new(); - static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new( - controller_context, - CONTROLLER0_ID, - [PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }], - )); - - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - - static INTERMEDIATE0: StaticCell< - type_c_service::wrapper::backing::IntermediateStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, psu::event::EventData>, - >, - > = StaticCell::new(); - let (intermediate0, power_event_receivers0) = storage0 - .try_create_intermediate([("Pd0", policy_sender0)]) - .expect("Failed to create intermediate storage"); - let intermediate0 = INTERMEDIATE0.init(intermediate0); - - static REFERENCED0: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, psu::event::EventData>, - >, - > = StaticCell::new(); - let referenced0 = REFERENCED0.init( - intermediate0 - .try_create_referenced() - .expect("Failed to create referenced storage"), - ); - static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); - let event_receiver0 = ArrayPortEventReceivers::new( - state0.create_interrupt_receiver(), - power_event_receivers0, - ); - static CONTROLLER0: StaticCell> = StaticCell::new(); + static CONTROLLER0: StaticCell = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); - static WRAPPER0: StaticCell = StaticCell::new(); - let wrapper0 = WRAPPER0.init(mock_controller::Wrapper::new( - controller0, - Default::default(), - referenced0, - )); - let bridge_receiver0 = BridgeEventReceiver::new(&referenced0.pd_controller); - let bridge0 = Bridge::new(controller0, &referenced0.pd_controller); - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - - static PORT1_CHANNEL: Channel = Channel::new(); - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new( + 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(); + + define_controller_port_static_cell_channel!(pub(self), port0, GlobalRawMutex, Mutex>); + 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(), + controller0, controller_context, - CONTROLLER1_ID, - [PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }], - )); - static INTERMEDIATE1: StaticCell< - type_c_service::wrapper::backing::IntermediateStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, psu::event::EventData>, - >, - > = StaticCell::new(); - let (intermediate1, power_event_receivers1) = storage1 - .try_create_intermediate([("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"); - let intermediate1 = INTERMEDIATE1.init(intermediate1); - - static REFERENCED1: StaticCell< - type_c_service::wrapper::backing::ReferencedStorage< - 1, - GlobalRawMutex, - DynamicSender<'_, psu::event::EventData>, - >, - > = StaticCell::new(); - let referenced1 = REFERENCED1.init( - intermediate1 - .try_create_referenced() - .expect("Failed to create referenced storage"), ); + let bridge_receiver0 = BridgeEventReceiver::new(pd_registration0); + let bridge0 = Bridge::new(controller0, pd_registration0); + static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); - let event_receiver1 = ArrayPortEventReceivers::new( - state1.create_interrupt_receiver(), - power_event_receivers1, - ); - static CONTROLLER1: StaticCell> = StaticCell::new(); + static CONTROLLER1: StaticCell = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); - static WRAPPER1: StaticCell = StaticCell::new(); - let wrapper1 = WRAPPER1.init(mock_controller::Wrapper::new( - controller1, + + 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(); + + define_controller_port_static_cell_channel!(pub(self), port1, GlobalRawMutex, Mutex>); + let PortComponents { + port: port1, + power_policy_receiver: policy_receiver1, + event_receiver: event_receiver1, + interrupt_sender: port1_interrupt_sender, + } = port1::create( + "PD1", + LocalPortId(0), + PORT1_ID, Default::default(), - referenced1, - )); - let bridge_receiver1 = BridgeEventReceiver::new(&referenced1.pd_controller); - let bridge1 = Bridge::new(controller1, &referenced1.pd_controller); + controller1, + controller_context, + ); + + let bridge_receiver1 = BridgeEventReceiver::new(pd_registration1); + let bridge1 = Bridge::new(controller1, pd_registration1); // Create power policy service // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot @@ -386,7 +351,7 @@ async fn task(spawner: Spawner) { let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); let power_policy_registration = ArrayRegistration { - psus: [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], + psus: [port0, port1], service_senders: [power_policy_sender], chargers: [], }; @@ -429,10 +394,7 @@ async fn task(spawner: Spawner) { spawner.spawn( power_policy_task( - PsuEventReceivers::new( - [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], - [policy_receiver0, policy_receiver1], - ), + PsuEventReceivers::new([port0, port1], [policy_receiver0, policy_receiver1]), power_service, ) .expect("Failed to create power policy task"), @@ -441,15 +403,28 @@ async fn task(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper0, wrapper1], + ServiceEventReceiver::new(controller_context, 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(wrapper_task(event_receiver0, wrapper0).expect("Failed to create wrapper0 task")); - spawner.spawn(wrapper_task(event_receiver1, wrapper1).expect("Failed to create wrapper1 task")); + spawner.spawn(port_task(event_receiver0, port0).expect("Failed to create wrapper0 task")); + spawner.spawn( + interrupt_splitter_task( + state0.create_interrupt_receiver(), + PortEventSplitter::new([port0_interrupt_sender]), + ) + .expect("Failed to create interrupt splitter 0 task"), + ); + spawner.spawn(port_task(event_receiver1, port1).expect("Failed to create wrapper1 task")); + spawner.spawn( + interrupt_splitter_task( + state1.create_interrupt_receiver(), + PortEventSplitter::new([port1_interrupt_sender]), + ) + .expect("Failed to create interrupt splitter 1 task"), + ); spawner.spawn(opm_task(controller_context, [state0, state1]).expect("Failed to create opm task")); } diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index c83d15bec..43b1b686c 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -1,4 +1,3 @@ -use crate::mock_controller::Wrapper; use embassy_executor::{Executor, Spawner}; use embassy_sync::channel::Channel; use embassy_sync::channel::DynamicReceiver; @@ -8,7 +7,7 @@ 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; +use embedded_usb_pd::{GlobalPortId, LocalPortId}; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::charger::mock::ChargerType; @@ -16,21 +15,24 @@ use power_policy_interface::psu; use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; -use std_examples::type_c::mock_controller; +use std_examples::type_c::mock_controller::Port; +use std_examples::type_c::mock_controller::{self, InterruptReceiver}; use type_c_interface::port::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::service::{EventReceiver, Service}; -use type_c_service::wrapper::backing::{IntermediateStorage, ReferencedStorage, Storage}; -use type_c_service::wrapper::event_receiver::ArrayPortEventReceivers; -use type_c_service::wrapper::proxy::PowerProxyDevice; +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 NUM_PD_CONTROLLERS: usize = 3; - const CONTROLLER0_ID: ControllerId = ControllerId(0); const PORT0_ID: GlobalPortId = GlobalPortId(0); @@ -42,14 +44,15 @@ const PORT2_ID: GlobalPortId = GlobalPortId(2); const DELAY_MS: u64 = 1000; -type DeviceType = Mutex>; +type ControllerType = Mutex>; +type PortType = Mutex>; type PowerPolicySenderType = MapSender< - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, power_policy_interface::service::event::EventData, DynImmediatePublisher<'static, power_policy_interface::service::event::EventData>, fn( - power_policy_interface::service::event::Event<'static, DeviceType>, + power_policy_interface::service::event::Event<'static, PortType>, ) -> power_policy_interface::service::event::EventData, >; @@ -59,11 +62,18 @@ type PowerPolicyServiceType = Mutex< GlobalRawMutex, power_policy_service::service::Service< 'static, - ArrayRegistration<'static, DeviceType, 3, PowerPolicySenderType, 1, ChargerType, 0>, + ArrayRegistration<'static, PortType, 3, PowerPolicySenderType, 1, ChargerType, 0>, >, >; type ServiceType = Service<'static>; +type SharedStateType = Mutex; +type PortEventReceiverType = PortEventReceiver< + 'static, + SharedStateType, + DynamicReceiver<'static, PortEventBitfield>, + DynamicReceiver<'static, type_c_service::controller::event::Loopback>, +>; #[embassy_executor::task(pool_size = 3)] async fn bridge_task( @@ -78,23 +88,24 @@ async fn bridge_task( } #[embassy_executor::task(pool_size = 3)] -async fn controller_task( - mut event_receiver: ArrayPortEventReceivers<'static, 1, mock_controller::InterruptReceiver<'static>>, - wrapper: &'static mock_controller::Wrapper<'static>, -) { +async fn port_task(mut event_receiver: PortEventReceiverType, port: &'static PortType) { loop { let event = event_receiver.wait_event().await; - - let output = wrapper - .process_event(&mut event_receiver.sink_ready_timeout, event) - .await; + let output = port.lock().await.process_event(event).await; if let Err(e) = output { error!("Error processing event: {e:?}"); } - let output = output.unwrap(); - if let Err(e) = wrapper.finalize(&mut event_receiver.power_proxies, output).await { - error!("Error finalizing output: {e:#?}"); - } + } +} + +#[embassy_executor::task(pool_size = 3)] +async fn interrupt_splitter_task( + mut interrupt_receiver: InterruptReceiver<'static>, + mut interrupt_splitter: PortEventSplitter<1, DynamicSender<'static, PortEventBitfield>>, +) -> ! { + loop { + let interrupts = interrupt_receiver.wait_interrupt().await; + interrupt_splitter.process_interrupts(interrupts).await; } } @@ -106,152 +117,110 @@ async fn task(spawner: Spawner) { static CONTROLLER_CONTEXT: StaticCell = StaticCell::new(); let controller_context = CONTROLLER_CONTEXT.init(type_c_interface::service::context::Context::new()); - static POLICY_CHANNEL0: StaticCell> = StaticCell::new(); - let policy_channel0 = POLICY_CHANNEL0.init(Channel::new()); - let policy_sender0 = policy_channel0.dyn_sender(); - let policy_receiver0 = policy_channel0.dyn_receiver(); - - static PORT0_CHANNEL: Channel = Channel::new(); - static STORAGE0: StaticCell> = StaticCell::new(); - let storage0 = STORAGE0.init(Storage::new( - controller_context, - CONTROLLER0_ID, - [PortRegistration { - id: PORT0_ID, - sender: PORT0_CHANNEL.dyn_sender(), - receiver: PORT0_CHANNEL.dyn_receiver(), - }], - )); - static INTERMEDIATE0: StaticCell< - IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, - > = StaticCell::new(); - let (intermediate0, power_event_receivers0) = storage0 - .try_create_intermediate([("Pd0", policy_sender0)]) - .expect("Failed to create intermediate storage"); - let intermediate0 = INTERMEDIATE0.init(intermediate0); - - static REFERENCED0: StaticCell>> = - StaticCell::new(); - let referenced0 = REFERENCED0.init( - intermediate0 - .try_create_referenced() - .expect("Failed to create referenced storage"), - ); - static STATE0: StaticCell = StaticCell::new(); let state0 = STATE0.init(mock_controller::ControllerState::new()); - let event_receiver0 = ArrayPortEventReceivers::new( - state0.create_interrupt_receiver(), - power_event_receivers0, - ); - static CONTROLLER0: StaticCell> = StaticCell::new(); + static CONTROLLER0: StaticCell = StaticCell::new(); let controller0 = CONTROLLER0.init(Mutex::new(mock_controller::Controller::new(state0))); - static WRAPPER0: StaticCell = StaticCell::new(); - let wrapper0 = WRAPPER0.init(mock_controller::Wrapper::new( - controller0, - Default::default(), - referenced0, - )); - let bridge_receiver0 = BridgeEventReceiver::new(&referenced0.pd_controller); - let bridge0 = Bridge::new(controller0, &referenced0.pd_controller); - static POLICY_CHANNEL1: StaticCell> = StaticCell::new(); - let policy_channel1 = POLICY_CHANNEL1.init(Channel::new()); - let policy_sender1 = policy_channel1.dyn_sender(); - let policy_receiver1 = policy_channel1.dyn_receiver(); - - static PORT1_CHANNEL: Channel = Channel::new(); - static STORAGE1: StaticCell> = StaticCell::new(); - let storage1 = STORAGE1.init(Storage::new( + 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(); + + define_controller_port_static_cell_channel!(pub(self), port0, GlobalRawMutex, Mutex>); + 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(), + controller0, controller_context, - CONTROLLER1_ID, - [PortRegistration { - id: PORT1_ID, - sender: PORT1_CHANNEL.dyn_sender(), - receiver: PORT1_CHANNEL.dyn_receiver(), - }], - )); - static INTERMEDIATE1: StaticCell< - IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, - > = StaticCell::new(); - let (intermediate1, power_event_receivers1) = storage1 - .try_create_intermediate([("Pd1", policy_sender1)]) - .expect("Failed to create intermediate storage"); - let intermediate1 = INTERMEDIATE1.init(intermediate1); - - static REFERENCED1: StaticCell>> = - StaticCell::new(); - let referenced1 = REFERENCED1.init( - intermediate1 - .try_create_referenced() - .expect("Failed to create referenced storage"), ); + let bridge_receiver0 = BridgeEventReceiver::new(pd_registration0); + let bridge0 = Bridge::new(controller0, pd_registration0); static STATE1: StaticCell = StaticCell::new(); let state1 = STATE1.init(mock_controller::ControllerState::new()); - let event_receiver1 = ArrayPortEventReceivers::new( - state1.create_interrupt_receiver(), - power_event_receivers1, - ); - static CONTROLLER1: StaticCell> = StaticCell::new(); + static CONTROLLER1: StaticCell = StaticCell::new(); let controller1 = CONTROLLER1.init(Mutex::new(mock_controller::Controller::new(state1))); - static WRAPPER1: StaticCell = StaticCell::new(); - let wrapper1 = WRAPPER1.init(mock_controller::Wrapper::new( - controller1, - Default::default(), - referenced1, - )); - let bridge_receiver1 = BridgeEventReceiver::new(&referenced1.pd_controller); - let bridge1 = Bridge::new(controller1, &referenced1.pd_controller); - - static POLICY_CHANNEL2: StaticCell> = StaticCell::new(); - let policy_channel2 = POLICY_CHANNEL2.init(Channel::new()); - let policy_sender2 = policy_channel2.dyn_sender(); - let policy_receiver2 = policy_channel2.dyn_receiver(); - static PORT2_CHANNEL: Channel = Channel::new(); - static STORAGE2: StaticCell> = StaticCell::new(); - let storage2 = STORAGE2.init(Storage::new( + 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(); + + define_controller_port_static_cell_channel!(pub(self), port1, GlobalRawMutex, Mutex>); + let PortComponents { + port: port1, + 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, - CONTROLLER2_ID, - [PortRegistration { - id: PORT2_ID, - sender: PORT2_CHANNEL.dyn_sender(), - receiver: PORT2_CHANNEL.dyn_receiver(), - }], - )); - static INTERMEDIATE2: StaticCell< - IntermediateStorage<1, GlobalRawMutex, DynamicSender<'static, psu::event::EventData>>, - > = StaticCell::new(); - let (intermediate2, power_event_receivers2) = storage2 - .try_create_intermediate([("Pd2", policy_sender2)]) - .expect("Failed to create intermediate storage"); - let intermediate2 = INTERMEDIATE2.init(intermediate2); - - static REFERENCED2: StaticCell>> = - StaticCell::new(); - let referenced2 = REFERENCED2.init( - intermediate2 - .try_create_referenced() - .expect("Failed to create referenced storage"), ); + let bridge_receiver1 = BridgeEventReceiver::new(pd_registration1); + let bridge1 = Bridge::new(controller1, pd_registration1); static STATE2: StaticCell = StaticCell::new(); let state2 = STATE2.init(mock_controller::ControllerState::new()); - let event_receiver2 = ArrayPortEventReceivers::new( - state2.create_interrupt_receiver(), - power_event_receivers2, - ); - static CONTROLLER2: StaticCell> = StaticCell::new(); + static CONTROLLER2: StaticCell = StaticCell::new(); let controller2 = CONTROLLER2.init(Mutex::new(mock_controller::Controller::new(state2))); - static WRAPPER2: StaticCell = StaticCell::new(); - let wrapper2 = WRAPPER2.init(mock_controller::Wrapper::new( - controller2, + + 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(); + + define_controller_port_static_cell_channel!(pub(self), port2, GlobalRawMutex, Mutex>); + let PortComponents { + port: port2, + power_policy_receiver: policy_receiver2, + event_receiver: event_receiver2, + interrupt_sender: port2_interrupt_sender, + } = port2::create( + "PD2", + LocalPortId(0), + PORT2_ID, Default::default(), - referenced2, - )); - let bridge_receiver2 = BridgeEventReceiver::new(&referenced2.pd_controller); - let bridge2 = Bridge::new(controller2, &referenced2.pd_controller); + controller2, + controller_context, + ); + let bridge_receiver2 = BridgeEventReceiver::new(pd_registration2); + let bridge2 = Bridge::new(controller2, pd_registration2); // The service is the only receiver and we only use a DynImmediatePublisher, which doesn't take a publisher slot static POWER_POLICY_CHANNEL: StaticCell< @@ -265,11 +234,7 @@ async fn task(spawner: Spawner) { let power_policy_subscriber = power_policy_channel.dyn_subscriber().unwrap(); let power_policy_registration = ArrayRegistration { - psus: [ - &wrapper0.ports[0].proxy, - &wrapper1.ports[0].proxy, - &wrapper2.ports[0].proxy, - ], + psus: [port0, port1, port2], service_senders: [power_policy_sender], chargers: [], }; @@ -287,11 +252,7 @@ async fn task(spawner: Spawner) { spawner.spawn( power_policy_task( PsuEventReceivers::new( - [ - &wrapper0.ports[0].proxy, - &wrapper1.ports[0].proxy, - &wrapper2.ports[0].proxy, - ], + [port0, port1, port2], [policy_receiver0, policy_receiver1, policy_receiver2], ), power_service, @@ -301,8 +262,7 @@ async fn task(spawner: Spawner) { spawner.spawn( type_c_service_task( type_c_service, - EventReceiver::new(controller_context, power_policy_subscriber), - [wrapper0, wrapper1, wrapper2], + ServiceEventReceiver::new(controller_context, power_policy_subscriber), ) .expect("Failed to create type-c service task"), ); @@ -310,9 +270,30 @@ async fn task(spawner: Spawner) { 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(controller_task(event_receiver0, wrapper0).expect("Failed to create controller0 task")); - spawner.spawn(controller_task(event_receiver1, wrapper1).expect("Failed to create controller1 task")); - spawner.spawn(controller_task(event_receiver2, wrapper2).expect("Failed to create controller2 task")); + spawner.spawn(port_task(event_receiver0, port0).expect("Failed to create controller0 task")); + spawner.spawn( + interrupt_splitter_task( + state0.create_interrupt_receiver(), + PortEventSplitter::new([port0_interrupt_sender]), + ) + .expect("Failed to create interrupt splitter 0 task"), + ); + spawner.spawn(port_task(event_receiver1, port1).expect("Failed to create controller1 task")); + spawner.spawn( + interrupt_splitter_task( + state1.create_interrupt_receiver(), + PortEventSplitter::new([port1_interrupt_sender]), + ) + .expect("Failed to create interrupt splitter 1 task"), + ); + spawner.spawn(port_task(event_receiver2, port2).expect("Failed to create controller2 task")); + spawner.spawn( + interrupt_splitter_task( + state2.create_interrupt_receiver(), + PortEventSplitter::new([port2_interrupt_sender]), + ) + .expect("Failed to create interrupt splitter 2 task"), + ); const CAPABILITY: PowerCapability = PowerCapability { voltage_mv: 20000, @@ -365,7 +346,7 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: PsuEventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + psu_events: PsuEventReceivers<'static, 3, PortType, DynamicReceiver<'static, psu::event::EventData>>, power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::psu_task(psu_events, power_policy).await; @@ -374,11 +355,10 @@ async fn power_policy_task( #[embassy_executor::task] async fn type_c_service_task( service: &'static Mutex, - event_receiver: EventReceiver<'static, PowerPolicyReceiverType>, - wrappers: [&'static Wrapper<'static>; NUM_PD_CONTROLLERS], + event_receiver: ServiceEventReceiver<'static, PowerPolicyReceiverType>, ) { info!("Starting type-c task"); - type_c_service::task::task(service, event_receiver, wrappers).await; + type_c_service::task::task(service, event_receiver).await; } fn main() { diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 28c6a6556..9ba50dad1 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -14,6 +14,7 @@ use type_c_interface::port::{ AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, event::PortEventBitfield, }; +use type_c_service::controller::state::SharedState; use type_c_service::util::power_capability_from_current; pub struct ControllerState { @@ -119,7 +120,7 @@ pub struct InterruptReceiver<'a> { events: &'a Signal, } -impl type_c_service::wrapper::event_receiver::InterruptReceiver for InterruptReceiver<'_> { +impl type_c_service::controller::event_receiver::InterruptReceiver for InterruptReceiver<'_> { async fn wait_interrupt(&mut self) -> [PortEventBitfield; N] { let events = self.events.wait().await; let mut result = [PortEventBitfield::none(); N]; @@ -321,9 +322,10 @@ impl type_c_interface::port::Controller for Controller<'_> { } } -pub type Wrapper<'a> = type_c_service::wrapper::ControllerWrapper< +pub type Port<'a> = type_c_service::controller::Port< 'a, - GlobalRawMutex, Mutex>, + Mutex, channel::DynamicSender<'a, power_policy_interface::psu::event::EventData>, + channel::DynamicSender<'a, type_c_service::controller::event::Loopback>, >; diff --git a/power-policy-interface/src/psu/event.rs b/power-policy-interface/src/psu/event.rs index 10b9fbdfe..aff48ef3d 100644 --- a/power-policy-interface/src/psu/event.rs +++ b/power-policy-interface/src/psu/event.rs @@ -34,28 +34,3 @@ where /// Event data pub event: EventData, } - -/// Data for a power policy response -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// The request was completed successfully - Complete, -} - -impl ResponseData { - /// Returns an InvalidResponse error if the response is not complete - pub fn complete_or_err(self) -> Result<(), super::Error> { - match self { - ResponseData::Complete => Ok(()), - } - } -} - -/// Response from the power policy service -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Response { - /// Response data - pub data: ResponseData, -} diff --git a/power-policy-interface/src/psu/mod.rs b/power-policy-interface/src/psu/mod.rs index bae5ced52..987f70174 100644 --- a/power-policy-interface/src/psu/mod.rs +++ b/power-policy-interface/src/psu/mod.rs @@ -125,12 +125,16 @@ impl State { pub fn disconnect(&mut self, clear_caps: bool) -> Result<(), Error> { let result = if matches!( self.psu_state, - PsuState::ConnectedConsumer(_) | PsuState::ConnectedProvider(_) + PsuState::ConnectedConsumer(_) | PsuState::ConnectedProvider(_) | PsuState::Idle ) { Ok(()) } else { Err(Error::InvalidState( - &[StateKind::ConnectedConsumer, StateKind::ConnectedProvider], + &[ + StateKind::ConnectedConsumer, + StateKind::ConnectedProvider, + StateKind::Idle, + ], self.psu_state.kind(), )) }; @@ -235,52 +239,6 @@ impl State { } } -/// Data for a device request -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CommandData { - /// Start consuming on this device - ConnectAsConsumer(ConsumerPowerCapability), - /// Start providing power to port partner on this device - ConnectAsProvider(ProviderPowerCapability), - /// Stop providing or consuming on this device - Disconnect, -} - -/// Request from power policy service to a device -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Command { - /// Request data - pub data: CommandData, -} - -/// Data for a device response -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ResponseData { - /// The request was successful - Complete, -} - -impl ResponseData { - /// Returns an InvalidResponse error if the response is not complete - pub fn complete_or_err(self) -> Result<(), Error> { - match self { - ResponseData::Complete => Ok(()), - } - } -} - -/// Wrapper type to make code cleaner -pub type InternalResponseData = Result; - -/// Response from a device to the power policy service -pub struct Response { - /// Response data - pub data: ResponseData, -} - /// Trait for PSU devices pub trait Psu: Named { /// Disconnect power from this device diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index fa84aaa33..77fffd30f 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -196,12 +196,7 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { if matches!(current_psu.state().psu_state, PsuState::ConnectedConsumer(_)) { // Disconnect the current consumer if needed info!("({}): Disconnecting current consumer", current_psu.name()); - // disconnect current consumer and set idle current_psu.disconnect().await?; - if let Err(e) = current_psu.state_mut().disconnect(false) { - // This should never happen because we check the state above, log an error instead of a panic - error!("({}): Disconnect transition failed: {:#?}", current_psu.name(), e); - } } // If no chargers are registered, they won't receive the new power capability. @@ -228,8 +223,6 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { e } else { psu.connect_consumer(new_consumer.consumer_power_capability).await?; - psu.state_mut() - .connect_consumer(new_consumer.consumer_power_capability)?; self.post_consumer_connected(new_consumer).await } } diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 822ccfc8d..701808347 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -8,8 +8,7 @@ pub mod registration; pub mod task; use embedded_services::named::Named; -use embedded_services::trace; -use embedded_services::{error, event::Sender, info, sync::Lockable}; +use embedded_services::{event::Sender, info, sync::Lockable, trace}; use power_policy_interface::charger::{Charger, PsuState}; use power_policy_interface::{ @@ -92,20 +91,11 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { } async fn process_notify_attach(&self, device: &'device Reg::Psu) { - let mut device = device.lock().await; - info!("({}): Received notify attached", device.name()); - if let Err(e) = device.state_mut().attach() { - error!("({}): Invalid state for attach: {:#?}", device.name(), e); - } + info!("({}): Received notify attached", device.lock().await.name()); } async fn process_notify_detach(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { - { - let mut device = device.lock().await; - info!("({}): Received notify detached", device.name()); - device.state_mut().detach(); - } - + info!("({}): Received notify detached", device.lock().await.name()); self.post_provider_removed(device).await; self.update_current_consumer().await?; Ok(()) @@ -116,21 +106,11 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { device: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { - { - let mut device = device.lock().await; - info!( - "({}): Received notify consumer capability: {:#?}", - device.name(), - capability, - ); - if let Err(e) = device.state_mut().update_consumer_power_capability(capability) { - error!( - "({}): Invalid state for notify consumer capability, catching up: {:#?}", - device.name(), - e, - ); - } - } + info!( + "({}): Received notify consumer capability: {:#?}", + device.lock().await.name(), + capability, + ); self.update_current_consumer().await } @@ -140,42 +120,17 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { requester: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { - { - let mut requester = requester.lock().await; - info!( - "({}): Received request provider capability: {:#?}", - requester.name(), - capability, - ); - if let Err(e) = requester - .state_mut() - .update_requested_provider_power_capability(capability) - { - error!( - "({}): Invalid state for notify provider capability, catching up: {:#?}", - requester.name(), - e, - ); - } - } + info!( + "({}): Received request provider capability: {:#?}", + requester.lock().await.name(), + capability, + ); self.connect_provider(requester).await } async fn process_notify_disconnect(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { - { - let mut locked_device = device.lock().await; - info!("({}): Received notify disconnect", locked_device.name()); - - if let Err(e) = locked_device.state_mut().disconnect(true) { - error!( - "({}): Invalid state for notify disconnect, catching up: {:#?}", - locked_device.name(), - e, - ); - } - } - + info!("({}): Received notify disconnect", device.lock().await.name()); self.post_provider_removed(device).await; self.update_current_consumer().await?; Ok(()) diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index 02029e61e..e41c15d6a 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -6,6 +6,7 @@ use core::ptr; use embedded_services::debug; +use embedded_services::error; use embedded_services::named::Named; use super::*; @@ -95,7 +96,6 @@ impl<'device, Reg: Registration<'device>> Service<'device, Reg> { e } else { locked_requester.connect_provider(target_power).await?; - locked_requester.state_mut().connect_provider(target_power)?; self.post_provider_connected(requester, target_power).await; Ok(()) } diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index 77b126e87..0b97d0f0c 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -46,29 +46,37 @@ impl<'a, S: Sender> Mock<'a, S> { } pub async fn simulate_consumer_connection(&mut self, capability: ConsumerPowerCapability) { + self.state.attach().unwrap(); self.sender.send(EventData::Attached).await; + self.state.update_consumer_power_capability(Some(capability)).unwrap(); self.sender .send(EventData::UpdatedConsumerCapability(Some(capability))) .await; } pub async fn simulate_detach(&mut self) { + self.state.detach(); self.sender.send(EventData::Detached).await; } pub async fn simulate_provider_connection(&mut self, capability: PowerCapability) { + self.state.attach().unwrap(); self.sender.send(EventData::Attached).await; let capability = Some(ProviderPowerCapability { capability, flags: ProviderFlags::none(), }); + self.state + .update_requested_provider_power_capability(capability) + .unwrap(); self.sender .send(EventData::RequestedProviderCapability(capability)) .await; } pub async fn simulate_disconnect(&mut self) { + self.state.disconnect(true).unwrap(); self.sender.send(EventData::Disconnected).await; } @@ -76,6 +84,9 @@ impl<'a, S: Sender> Mock<'a, S> { &mut self, capability: Option, ) { + self.state + .update_requested_provider_power_capability(capability) + .unwrap(); self.sender .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) .await @@ -86,19 +97,19 @@ impl<'a, S: Sender> Psu for Mock<'a, S> { async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> { info!("Connect consumer {:#?}", capability); self.record_fn_call(FnCall::ConnectConsumer(capability)); - Ok(()) + self.state.connect_consumer(capability) } async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> { info!("Connect provider: {:#?}", capability); self.record_fn_call(FnCall::ConnectProvider(capability)); - Ok(()) + self.state.connect_provider(capability) } async fn disconnect(&mut self) -> Result<(), Error> { info!("Disconnect"); self.record_fn_call(FnCall::Disconnect); - Ok(()) + self.state.disconnect(false) } fn state(&self) -> &State { diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index d03ff8e7e..0c001d01d 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -92,6 +92,16 @@ who = "Jerry Xie " criteria = "safe-to-deploy" version = "1.0.0" +[[audits.device-driver]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.9" + +[[audits.device-driver]] +who = "Felipe Balbi " +criteria = "safe-to-run" +version = "1.0.9" + [[audits.embassy-embedded-hal]] who = "Billy Price " criteria = "safe-to-deploy" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 7acb2d508..52d64d200 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -22,250 +22,10 @@ audit-as-crates-io = false [policy.keyberon] audit-as-crates-io = false -[[exemptions.ahash]] -version = "0.8.12" -criteria = "safe-to-deploy" - -[[exemptions.askama]] -version = "0.14.0" -criteria = "safe-to-deploy" - -[[exemptions.askama_derive]] -version = "0.14.0" -criteria = "safe-to-deploy" - -[[exemptions.askama_parser]] -version = "0.14.0" -criteria = "safe-to-deploy" - -[[exemptions.az]] -version = "1.2.1" -criteria = "safe-to-deploy" - -[[exemptions.bare-metal]] -version = "0.2.5" -criteria = "safe-to-deploy" - -[[exemptions.bbq2]] -version = "0.4.2" -criteria = "safe-to-deploy" - -[[exemptions.bincode]] -version = "2.0.1" -criteria = "safe-to-deploy" - -[[exemptions.bincode_derive]] -version = "2.0.1" -criteria = "safe-to-deploy" - -[[exemptions.bitfield]] -version = "0.13.2" -criteria = "safe-to-deploy" - -[[exemptions.bitfield]] -version = "0.15.0" -criteria = "safe-to-deploy" - -[[exemptions.bitfield]] -version = "0.17.0" -criteria = "safe-to-deploy" - -[[exemptions.bitfield]] -version = "0.19.2" -criteria = "safe-to-deploy" - -[[exemptions.bitfield-macros]] -version = "0.19.2" -criteria = "safe-to-deploy" - -[[exemptions.bitfield-struct]] -version = "0.10.1" -criteria = "safe-to-deploy" - -[[exemptions.bitvec]] -version = "1.0.1" -criteria = "safe-to-deploy" - -[[exemptions.bytemuck]] -version = "1.23.2" -criteria = "safe-to-deploy" - -[[exemptions.cfg-if]] -version = "1.0.3" -criteria = "safe-to-deploy" - -[[exemptions.chrono]] -version = "0.4.40" -criteria = "safe-to-deploy" - -[[exemptions.convert_case]] -version = "0.6.0" -criteria = "safe-to-deploy" - -[[exemptions.cordyceps]] -version = "0.3.4" -criteria = "safe-to-deploy" - -[[exemptions.cortex-m]] -version = "0.7.7" -criteria = "safe-to-deploy" - -[[exemptions.cortex-m-rt]] -version = "0.7.5" -criteria = "safe-to-deploy" - -[[exemptions.cortex-m-rt-macros]] -version = "0.7.5" -criteria = "safe-to-deploy" - -[[exemptions.crc]] -version = "3.3.0" -criteria = "safe-to-deploy" - -[[exemptions.crc-catalog]] -version = "2.4.0" -criteria = "safe-to-deploy" - -[[exemptions.critical-section]] -version = "1.2.0" -criteria = "safe-to-deploy" - -[[exemptions.darling]] -version = "0.20.11" -criteria = "safe-to-deploy" - -[[exemptions.darling_core]] -version = "0.20.11" -criteria = "safe-to-deploy" - -[[exemptions.darling_macro]] -version = "0.20.11" -criteria = "safe-to-deploy" - -[[exemptions.dd-manifest-tree]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.defmt]] -version = "0.3.100" -criteria = "safe-to-deploy" - -[[exemptions.defmt]] -version = "1.0.1" -criteria = "safe-to-deploy" - -[[exemptions.defmt-macros]] -version = "1.0.1" -criteria = "safe-to-deploy" - -[[exemptions.defmt-parser]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.device-driver]] -version = "1.0.7" -criteria = "safe-to-deploy" - -[[exemptions.device-driver-generation]] -version = "1.0.7" -criteria = "safe-to-deploy" - -[[exemptions.device-driver-macros]] -version = "1.0.7" -criteria = "safe-to-deploy" - [[exemptions.diff]] version = "0.1.13" criteria = "safe-to-run" -[[exemptions.embassy-embedded-hal]] -version = "0.5.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-executor]] -version = "0.9.1" -criteria = "safe-to-deploy" - -[[exemptions.embassy-executor-macros]] -version = "0.7.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-executor-timer-queue]] -version = "0.1.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-futures]] -version = "0.1.2" -criteria = "safe-to-deploy" - -[[exemptions.embassy-hal-internal]] -version = "0.3.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-sync]] -version = "0.7.2" -criteria = "safe-to-deploy" - -[[exemptions.embassy-sync]] -version = "0.8.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-time-driver]] -version = "0.2.1" -criteria = "safe-to-deploy" - -[[exemptions.embassy-time-queue-utils]] -version = "0.3.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal]] -version = "0.2.7" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal-async]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal-nb]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-io]] -version = "0.7.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-io-async]] -version = "0.6.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-io-async]] -version = "0.7.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-storage]] -version = "0.3.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-storage-async]] -version = "0.4.1" -criteria = "safe-to-deploy" - -[[exemptions.fixed]] -version = "1.29.0" -criteria = "safe-to-deploy" - -[[exemptions.funty]] -version = "2.0.0" -criteria = "safe-to-deploy" - -[[exemptions.futures-macro]] -version = "0.3.31" -criteria = "safe-to-run" - [[exemptions.futures-task]] version = "0.3.32" criteria = "safe-to-run" @@ -274,114 +34,18 @@ criteria = "safe-to-run" version = "3.0.3" criteria = "safe-to-run" -[[exemptions.futures-util]] -version = "0.3.31" -criteria = "safe-to-run" - [[exemptions.generator]] version = "0.8.5" criteria = "safe-to-deploy" -[[exemptions.hash32]] -version = "0.3.1" -criteria = "safe-to-deploy" - -[[exemptions.hashbrown]] -version = "0.14.5" -criteria = "safe-to-deploy" - [[exemptions.hashbrown]] version = "0.17.0" criteria = "safe-to-deploy" -[[exemptions.hashlink]] -version = "0.9.1" -criteria = "safe-to-deploy" - -[[exemptions.heapless]] -version = "0.8.0" -criteria = "safe-to-deploy" - -[[exemptions.heapless]] -version = "0.9.2" -criteria = "safe-to-deploy" - -[[exemptions.indexmap]] -version = "2.11.0" -criteria = "safe-to-deploy" - [[exemptions.indexmap]] version = "2.14.0" criteria = "safe-to-deploy" -[[exemptions.io-uring]] -version = "0.7.10" -criteria = "safe-to-run" - -[[exemptions.kdl]] -version = "6.3.4" -criteria = "safe-to-deploy" - -[[exemptions.litrs]] -version = "0.4.2" -criteria = "safe-to-deploy" - -[[exemptions.maitake-sync]] -version = "0.2.2" -criteria = "safe-to-deploy" - -[[exemptions.miette]] -version = "7.6.0" -criteria = "safe-to-deploy" - -[[exemptions.miette-derive]] -version = "7.6.0" -criteria = "safe-to-deploy" - -[[exemptions.mimxrt600-fcb]] -version = "0.2.2" -criteria = "safe-to-deploy" - -[[exemptions.mio]] -version = "1.0.4" -criteria = "safe-to-run" - -[[exemptions.mycelium-bitfield]] -version = "0.1.5" -criteria = "safe-to-deploy" - -[[exemptions.num]] -version = "0.4.3" -criteria = "safe-to-deploy" - -[[exemptions.num-bigint]] -version = "0.4.6" -criteria = "safe-to-deploy" - -[[exemptions.num-complex]] -version = "0.4.6" -criteria = "safe-to-deploy" - -[[exemptions.num-iter]] -version = "0.1.45" -criteria = "safe-to-deploy" - -[[exemptions.once_cell]] -version = "1.21.3" -criteria = "safe-to-deploy" - -[[exemptions.pin-project]] -version = "1.1.10" -criteria = "safe-to-deploy" - -[[exemptions.pin-project-internal]] -version = "1.1.10" -criteria = "safe-to-deploy" - -[[exemptions.pin-utils]] -version = "0.1.0" -criteria = "safe-to-run" - [[exemptions.pretty_assertions]] version = "1.4.1" criteria = "safe-to-run" @@ -390,66 +54,18 @@ criteria = "safe-to-run" version = "3.5.0" criteria = "safe-to-run" -[[exemptions.proc-macro-error-attr2]] -version = "2.0.0" -criteria = "safe-to-deploy" - -[[exemptions.proc-macro-error2]] -version = "2.0.1" -criteria = "safe-to-deploy" - -[[exemptions.radium]] -version = "0.7.0" -criteria = "safe-to-deploy" - [[exemptions.rustc-demangle]] version = "0.1.26" criteria = "safe-to-run" -[[exemptions.rustc_version]] -version = "0.2.3" -criteria = "safe-to-deploy" - -[[exemptions.semver]] -version = "0.9.0" -criteria = "safe-to-deploy" - [[exemptions.semver]] version = "1.0.28" criteria = "safe-to-run" -[[exemptions.semver-parser]] -version = "0.7.0" -criteria = "safe-to-deploy" - -[[exemptions.serde_spanned]] -version = "0.6.9" -criteria = "safe-to-deploy" - -[[exemptions.slab]] -version = "0.4.11" -criteria = "safe-to-run" - -[[exemptions.tokio]] -version = "1.47.1" -criteria = "safe-to-run" - -[[exemptions.toml]] -version = "0.8.23" -criteria = "safe-to-deploy" - -[[exemptions.toml_datetime]] -version = "0.6.11" -criteria = "safe-to-deploy" - [[exemptions.toml_datetime]] version = "1.1.1+spec-1.1.0" criteria = "safe-to-run" -[[exemptions.toml_edit]] -version = "0.22.27" -criteria = "safe-to-deploy" - [[exemptions.toml_edit]] version = "0.25.11+spec-1.1.0" criteria = "safe-to-run" @@ -458,54 +74,10 @@ criteria = "safe-to-run" version = "1.1.2+spec-1.1.0" criteria = "safe-to-run" -[[exemptions.typenum]] -version = "1.18.0" -criteria = "safe-to-deploy" - -[[exemptions.unty]] -version = "0.0.4" -criteria = "safe-to-deploy" - -[[exemptions.vcell]] -version = "0.1.3" -criteria = "safe-to-deploy" - -[[exemptions.version_check]] -version = "0.9.5" -criteria = "safe-to-deploy" - -[[exemptions.virtue]] -version = "0.0.18" -criteria = "safe-to-deploy" - -[[exemptions.volatile-register]] -version = "0.2.2" -criteria = "safe-to-deploy" - -[[exemptions.wasi]] -version = "0.11.1+wasi-snapshot-preview1" -criteria = "safe-to-run" - -[[exemptions.winnow]] -version = "0.6.24" -criteria = "safe-to-deploy" - -[[exemptions.winnow]] -version = "0.7.13" -criteria = "safe-to-deploy" - [[exemptions.winnow]] version = "1.0.2" criteria = "safe-to-run" -[[exemptions.wyz]] -version = "0.5.1" -criteria = "safe-to-deploy" - -[[exemptions.yaml-rust2]] -version = "0.9.0" -criteria = "safe-to-deploy" - [[exemptions.yansi]] version = "1.0.1" criteria = "safe-to-run" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index d185c6ac5..322196ff7 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -21,13 +21,6 @@ when = "2025-08-16" user-id = 55123 user-login = "rust-lang-owner" -[[publisher.encoding_rs]] -version = "0.8.35" -when = "2024-10-24" -user-id = 4484 -user-login = "hsivonen" -user-name = "Henri Sivonen" - [[publisher.libc]] version = "0.2.175" when = "2025-08-11" @@ -76,13 +69,6 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" -[[publisher.ryu]] -version = "1.0.20" -when = "2025-03-04" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - [[publisher.scoped-tls]] version = "1.0.1" when = "2022-10-31" @@ -90,13 +76,6 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" -[[publisher.serde_json]] -version = "1.0.143" -when = "2025-08-19" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - [[publisher.smallvec]] version = "1.15.1" when = "2025-06-06" @@ -118,13 +97,6 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" -[[publisher.thiserror]] -version = "1.0.69" -when = "2024-11-10" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - [[publisher.thiserror]] version = "2.0.16" when = "2025-08-20" @@ -132,13 +104,6 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" -[[publisher.thiserror-impl]] -version = "1.0.69" -when = "2024-11-10" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - [[publisher.thiserror-impl]] version = "2.0.16" when = "2025-08-20" @@ -146,20 +111,6 @@ user-id = 3618 user-login = "dtolnay" user-name = "David Tolnay" -[[publisher.unicode-segmentation]] -version = "1.12.0" -when = "2024-09-13" -user-id = 1139 -user-login = "Manishearth" -user-name = "Manish Goregaokar" - -[[publisher.unicode-width]] -version = "0.1.14" -when = "2024-09-19" -user-id = 1139 -user-login = "Manishearth" -user-name = "Manish Goregaokar" - [[publisher.windows]] version = "0.61.3" when = "2025-06-12" @@ -328,18 +279,102 @@ delta = "1.4.0 -> 1.5.0" notes = "No unsafe, no build.rs, no network access; delta adds edition-aware rustc probing and best-effort probe-file cleanup only. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.az]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.2.1" +notes = "No unsafe code. no_std library with only safe numeric cast traits. Build script probes for track_caller via rustc in OUT_DIR only. No network, no ambient capabilities. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.bare-metal]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.2.5" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.bbq2]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.4.2" +notes = "no_std SPSC bip-buffer queue. Non-trivial unsafe for lock-free coordination and pointer arithmetic, all reviewed and sound. No build script, no proc macros, no I/O. Has Miri CI. Assisted-by: copilot-chat:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bincode]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "2.0.1" +notes = "no_std binary serialization library. ~15 unsafe blocks for u8 type-specialization guarded by unty::type_equal and MaybeUninit patterns. No build script, no proc macros. std imports only for Encode/Decode trait impls. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bincode_derive]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "2.0.1" +notes = "Proc-macro derive for bincode Encode/Decode. No unsafe, no build script, no I/O. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.bitfield]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.13.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.bitfield]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.13.2 -> 0.15.0" +notes = "Delta audit: BitRange/Bit traits split into read-only and mutable variants (BitRangeMut/BitMut); added mask constant generation; clippy fixes; MSRV bump. No unsafe, no build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bitfield]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.15.0 -> 0.17.0" +notes = "Delta: adds bitwise op derives, constructor derives, arbitrary visibility. Pure declarative macros. No unsafe, no build script. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bitfield]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.15.0 -> 0.19.2" +notes = "Delta: refactored to proc macros in bitfield-macros, added BitAnd/BitOr/BitXor, signed types, bool arrays. No unsafe. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bitfield-macros]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.19.2" +notes = "Proc-macro generating bitfield getters/setters/masks. No unsafe, no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bitfield-struct]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.10.1" +notes = "Proc-macro crate generating safe bitfield structs. No unsafe, no build script. Standard proc-macro deps only. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bytemuck]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.22.0 -> 1.23.2" +notes = "Delta 1.22.0->1.23.2: new ZeroableInOption impls for function pointer types (sound, uses guaranteed niche optimization), core::error::Error impls behind feature flag, safe derive helper module. No new unsafe blocks, no build script, no I/O. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cfg-if]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 1.0.3" +notes = "Delta 1.0.0->1.0.3: formatting/readability refactor of macro identifiers, removed compiler_builtins dep, updated CI. No unsafe, no build script, no imports. Pure macro_rules crate. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cordyceps]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.4" +notes = "Intrusive data structures crate (no_std). ~115 unsafe blocks, all necessary for intrusive linked list/queue/stack ops. Correct patterns: addr_of_mut, proper atomic orderings, Vyukov MPSC algorithm. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.cortex-m]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -358,6 +393,20 @@ criteria = "safe-to-deploy" version = "0.7.5" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.crc]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "3.3.0" +notes = "No unsafe (forbid(unsafe_code)), no build script, no I/O, no_std pure CRC computation. Assisted-by: copilot-chat:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.crc-catalog]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "2.4.0" +notes = "Pure no_std data-only crate. No unsafe, no build script, no dependencies, no I/O. Contains only const CRC algorithm parameter structs. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.critical-section]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -371,6 +420,13 @@ delta = "0.2.3 -> 0.2.4" notes = "Tiny diff to use newer core/std features via build.rs env var for path separator; no safety impact. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.defmt]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.100" +notes = "Compatibility shim: no_std crate that re-exports defmt 1.x items for 0.3 API compatibility. No unsafe code, no build script, no powerful imports, no logic - pure pub-use re-exports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.defmt]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -389,18 +445,53 @@ criteria = "safe-to-deploy" version = "1.0.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embassy-embedded-hal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.5.0" +notes = "No unsafe, no build script, no proc macros. no_std shared bus/flash partition utilities for embedded-hal traits. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.embassy-executor-timer-queue]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.1.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-imxrt/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embassy-futures]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.2" +notes = "no_std future combinators. All unsafe is pin-projection and no-op RawWaker - reviewed and sound. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embassy-hal-internal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.0" +notes = "no_std HAL internals. Unsafe in atomic ring buffer (sound SPSC), peripheral singletons, cortex-m interrupt priority. Build script emits cfg flags only. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embassy-sync]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.8.0" +notes = "no_std async sync primitives. Substantial unsafe for UnsafeCell-based interiors and Send/Sync impls -- all reviewed and sound, guarded by RawMutex/critical_section. Build script only reads TARGET env var. No proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.embassy-time]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.5.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/tps6699x/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embassy-time-driver]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "no_std driver trait for embassy-time. Minimal unsafe for extern Rust FFI calls (sound via links key). Empty build.rs. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.embassy-time-queue-utils]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -413,6 +504,159 @@ criteria = "safe-to-deploy" version = "0.2.7" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embedded-hal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.7 -> 1.0.0" +notes = "Pure no_std trait crate. Complete API redesign for 1.0: removed nb-based traits, CAN module, all unsafe code. Only defines traits/enums/types for digital, I2C, SPI, PWM, delay. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-hal-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "no_std async HAL trait definitions. No unsafe in library. Build script only runs rustc --version. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-hal-nb]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "no_std trait-only crate. No unsafe, no build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-io]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.6.1 -> 0.7.1" +notes = "Add core::error::Error trait bound (MSRV 1.81). defmt 0.3->1.0. Implement ReadReady/WriteReady for slices and VecDeque. Add seek_relative(). Fix method forwardings. Trusted publisher (Dirbaio from Embedded WG)." +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-imxrt/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-io-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.6.1" +notes = "No unsafe. Build script only detects nightly via rustc --version. Pure async trait definitions for embedded I/O. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-io-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.6.1 -> 0.7.0" +notes = "Delta 0.6.1->0.7.0: No unsafe. Build script removed (AFIT now stable). flush() made required, BufRead requires Read, new VecDeque impls. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-storage]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "Pure no_std storage abstraction traits. deny(unsafe_code), no build script, no dependencies, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-storage-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.4.1" +notes = "Pure no_std async trait definitions for NOR flash storage. No unsafe code, no build script, no powerful imports. Only dependency is embedded-storage. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.fixed]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.29.0" +notes = "no_std fixed-point number library. Unsafe limited to: bytemuck Pod/Zeroable impls on repr(transparent) types, NonZero::new_unchecked after proven-nonzero guards, unreachable_unchecked in exhaustive remainder logic. Build script probes compiler features in OUT_DIR. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.hash32]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "no_std 32-bit hashing (FNV, MurmurHash3). ~10 unsafe blocks in murmur3.rs for MaybeUninit buffer handling - all sound. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heapless]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.8.0" +notes = "no_std fixed-capacity data structures. Extensive unsafe for MaybeUninit buffer management, lock-free queues (Vyukov MPMC, SPSC), and Treiber stack memory pools with ABA prevention. Patterns mirror std or published algorithms. Build script probes for atomic/LLSC support. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heapless]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.9.2" +notes = "no_std fixed-capacity data structures. Extensive unsafe for MaybeUninit buffers, lock-free queues (Vyukov MPMC, SPSC), Treiber stack pools with ABA prevention (CAS tagged pointers + ARM LLSC). All Send/Sync bounds verified correct. Build script probes for ARM LLSC. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.io-uring]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.5.13 -> 0.7.10" +notes = "Delta audit. Linux io_uring bindings. +15 hand-written unsafe (new from_fd, buffer/file registration APIs). SeqCst fence fix improves atomics correctness. Probe refactored to stack allocation. Build script adds cfg checks only. All unsafe for expected syscall/mmap/fd operations. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.litrs]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.4.1 -> 0.4.2" +notes = "Delta 0.4.1->0.4.2: Bug fixes for non-ASCII byte string escapes, removes CR LF normalization to align with spec, fixes error span for out-of-range Unicode escapes. No unsafe code, no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.maitake-sync]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.2.2" +notes = "No-std async sync primitives. Extensive unsafe for Send/Sync impls, UnsafeCell access under locks/atomics, intrusive linked list nodes, spinlocks -- all follow standard patterns. Uses unreachable_unchecked! macro (panics in debug). No build script, no proc macros. Loom-tested. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.mimxrt600-fcb]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "Pure no_std data-definition crate for MIMXRT600 flash config blocks. No unsafe, no build script. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.mimxrt600-fcb]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.1 -> 0.2.2" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embassy-imxrt/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.mio]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.0.1 -> 1.0.4" +notes = "Delta 1.0.1->1.0.4: I/O safety trait impls, AIX poll(2) support, windows-sys 0.59 pointer fixes. Unsafe Send/Sync for CompletionPort/Inner sound. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.mycelium-bitfield]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.5" +notes = "Pure safe no_std bitfield macro crate. No unsafe code, no build script, no proc macros, no dependencies, no powerful imports. Only core:: types used. Assisted-by: copilot-chat:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.once_cell]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.20.1" +notes = "Single-assignment cells and lazy values. All unsafe reviewed: UnsafeCell access, Send/Sync impls, atomic waiter queue, strict provenance polyfill - all sound with correct bounds. No build script, no proc macros, no powerful imports beyond std::thread/atomic. Assisted-by: copilot-chat:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.pin-project]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.1.10" +notes = "no_std pin-projection helper. Re-exports proc macros from pin-project-internal. Minimal unsafe in __private module (drop guards, UnsafeUnpin forwarding) -- all sound with SAFETY comments. No build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.pin-project-internal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.1.10" +notes = "Proc-macro for pin projection. forbid(unsafe_code) in macro itself. Generated unsafe is sound pin projection (Pin::new_unchecked, get_unchecked_mut) with compile-time safety enforced via trait tricks. No build script, no I/O. Deps: proc-macro2, quote, syn only. Assisted-by: copilot-chat:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.proc-macro-error-attr2]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -443,11 +687,18 @@ criteria = "safe-to-deploy" version = "0.7.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" -[[audits.OpenDevicePartnership.audits.tap]] +[[audits.OpenDevicePartnership.audits.serde_spanned]] who = "Jerry Xie " criteria = "safe-to-deploy" -version = "1.0.1" -notes = "No unsafe, no build.rs, no ambient I/O/process/network capabilities; behavior matches no_std tap/pipe/conv utility traits. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" +delta = "0.6.8 -> 0.6.9" +notes = "Trivial delta: metadata, lint config, and doc formatting only. No functional code changes, no unsafe, no build script, no I/O. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.slab]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "0.4.8 -> 0.4.11" +notes = "Delta 0.4.8->0.4.11: new get_disjoint_mut uses unsafe (MaybeUninit + raw ptrs) with sound bounds/overlap checks. build.rs removed. No powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" [[audits.OpenDevicePartnership.audits.thread_local]] @@ -457,6 +708,48 @@ delta = "1.1.4 -> 1.1.9" notes = "No build script, no FS/net/process capability expansion; unsafe refactor to lock-free insertion and nightly TLS path appears sound on review. Assisted-by: copilot-cli:GPT-5.3-Codex cargo-vet" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.tokio]] +who = "Jerry Xie " +criteria = "safe-to-run" +delta = "1.45.0 -> 1.47.1" +notes = "Delta audit. New SetOnce sync primitive, OwnedNotified, spawn location tracking (tokio_unstable), experimental io_uring behind cfg gate, block_in_place hardening. All new unsafe follows existing patterns with safety comments. No build script, no proc macros. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.toml]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.8.22 -> 0.8.23" +notes = "Delta: adds TupleVariant/StructVariant serialization support. All new code is thin wrappers delegating to toml_edit. No unsafe (forbid(unsafe_code)), no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.toml_datetime]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.6.9 -> 0.6.11" +notes = "Delta 0.6.9->0.6.11: parser refactored from char-by-char to lexer-based tokenizer with improved error messages; no unsafe (forbid(unsafe_code)), no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.toml_edit]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.22.26 -> 0.22.27" +notes = "Delta: no changes to unsafe code (all pre-existing from_utf8_unchecked on ASCII-validated buffers). Visibility reductions on parser internals, serializer refactoring, new consuming accessors. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.typenum]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.18.0" +notes = "Pure no_std type-level numbers crate. forbid(unsafe_code) -- zero unsafe anywhere. Build script only writes generated test code to OUT_DIR. No proc macros, no FFI, no network/filesystem/process access in library. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.unty]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.0.4" +notes = "Tiny no_std crate (1 file, ~120 LOC, zero deps). Two unsafe blocks: transmute_copy guarded by TypeId check in unty(), and a dtolnay-pattern transmute in non_static_type_id(). Both documented; no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.valuable]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -470,18 +763,46 @@ criteria = "safe-to-deploy" version = "0.1.3" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.version_check]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.9.4 -> 0.9.5" +notes = "Delta 0.9.4->0.9.5: documentation-only changes (added feature detection guidance, doc cross-references) and Cargo.toml normalization. No code changes, no unsafe, no new imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.virtue]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.0.18" +notes = "Proc-macro derive helper library. No unsafe code, no build script. Uses std::fs/std::env only in opt-in export_to_file() debug helper scoped to target/ dir. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.volatile-register]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.2.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.wasi]] +who = "Jerry Xie " +criteria = "safe-to-run" +version = "0.11.1+wasi-snapshot-preview1" +notes = "Auto-generated WASI snapshot-preview1 bindings from Bytecode Alliance. no_std, no build script, zero runtime deps. Unsafe limited to FFI wrappers for WASI host calls and unreachable_unchecked in exhaustive enum match arms. Assisted-by: copilot-chat:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.windows-sys]] who = "Felipe Balbi " criteria = "safe-to-run" version = "0.61.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.winnow]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.10 -> 0.7.13" +notes = "Delta adds Accumulate impls (Cow str, String, VecDeque), fixes macro PartialEq/PartialOrd, optimizes str::next_token, adds tests, improves docs. No unsafe changes, no build script, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.6 cargo-vet" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.bytecode-alliance.audits.adler2]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -522,6 +843,12 @@ criteria = "safe-to-deploy" delta = "2.7.0 -> 2.9.4" notes = "Tweaks to the macro, nothing out of order." +[[audits.bytecode-alliance.audits.cfg-if]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "I am the author of this crate." + [[audits.bytecode-alliance.audits.embedded-io]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -545,6 +872,11 @@ who = "Pat Hickey " criteria = "safe-to-deploy" delta = "0.3.28 -> 0.3.31" +[[audits.bytecode-alliance.audits.futures-macro]] +who = "Joel Dice " +criteria = "safe-to-deploy" +version = "0.3.31" + [[audits.bytecode-alliance.audits.futures-sink]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -555,11 +887,6 @@ who = "Pat Hickey " criteria = "safe-to-deploy" delta = "0.3.28 -> 0.3.31" -[[audits.bytecode-alliance.audits.hashbrown]] -who = "Chris Fallin " -criteria = "safe-to-deploy" -delta = "0.14.5 -> 0.15.2" - [[audits.bytecode-alliance.audits.itertools]] who = "Nick Fitzgerald " criteria = "safe-to-deploy" @@ -578,11 +905,6 @@ Lots of new iterators and shuffling some things around. Some new unsafe code but it's well-documented and well-tested. Nothing suspicious. """ -[[audits.bytecode-alliance.audits.itoa]] -who = "Dan Gohman " -criteria = "safe-to-deploy" -delta = "1.0.11 -> 1.0.14" - [[audits.bytecode-alliance.audits.log]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -636,21 +958,10 @@ criteria = "safe-to-deploy" version = "0.46.0" notes = "one use of unsafe to call windows specific api to get console handle." -[[audits.bytecode-alliance.audits.percent-encoding]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "2.2.0" -notes = """ -This crate is a single-file crate that does what it says on the tin. There are -a few `unsafe` blocks related to utf-8 validation which are locally verifiable -as correct and otherwise this crate is good to go. -""" - -[[audits.bytecode-alliance.audits.semver]] +[[audits.bytecode-alliance.audits.pin-utils]] who = "Pat Hickey " criteria = "safe-to-deploy" -version = "1.0.17" -notes = "plenty of unsafe pointer and vec tricks, but in well-structured and commented code that appears to be correct" +version = "0.1.0" [[audits.bytecode-alliance.audits.sharded-slab]] who = "Pat Hickey " @@ -752,6 +1063,67 @@ Additional review comments can be found at https://crrev.com/c/4723145/31 """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.bytemuck]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.16.3" +notes = """ +Review notes from the original audit (of 1.14.3) may be found in +https://crrev.com/c/5362675. Note that this audit has initially missed UB risk +that was fixed in 1.16.2 - see https://github.com/Lokathor/bytemuck/pull/258. +Because of this, the original audit has been edited to certify version `1.16.3` +instead (see also https://crrev.com/c/5771867). +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bytemuck]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.16.3 -> 1.17.1" +notes = "Unsafe review comments can be found in https://crrev.com/c/5813463" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bytemuck]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.17.1 -> 1.18.0" +notes = "No code changes - just altering feature flag arrangements" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bytemuck]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.18.0 -> 1.19.0" +notes = "No code changes - just comment changes and adding the track_caller attribute." +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bytemuck]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.19.0 -> 1.20.0" +notes = "`unsafe` review can be found at https://crrev.com/c/6096767" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bytemuck]] +who = "Adrian Taylor " +criteria = "safe-to-deploy" +delta = "1.20.0 -> 1.21.0" +notes = "Unsafe review at https://chromium-review.googlesource.com/c/chromium/src/+/6111154/" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.bytemuck]] +who = "Daniel Cheng " +criteria = "safe-to-deploy" +delta = "1.21.0 -> 1.22.0" +notes = """ +This adds new instances of unsafe, but the uses are justified: +- BoxBytes is essentially a Box<[u8], which is Send + Sync, so also marking BoxBytes as Send + Sync is justified. +- core::num::Saturating meets the criteria for Zeroable + Pod, so marking it as such is justified. + +See https://crrev.com/c/6321863 for more audit notes. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.byteorder]] who = "danakj " criteria = "safe-to-deploy" @@ -800,55 +1172,44 @@ delta = "1.0.1 -> 1.0.2" notes = "No changes to any .rs files or Rust code." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.glob]] +[[audits.google.audits.futures-util]] who = "George Burgess IV " -criteria = "safe-to-deploy" -version = "0.3.1" +criteria = "safe-to-run" +version = "0.3.28" +notes = """ +There's a custom xorshift-based `random::shuffle` implementation in +src/async_await/random.rs. This is `doc(hidden)` and seems to exist just so +that `futures-macro::select` can be unbiased. Sicne xorshift is explicitly not +intended to be a cryptographically secure algorithm, it is not considered +crypto. +""" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.glob]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "0.3.1 -> 0.3.2" -notes = "Still no unsafe" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.itoa]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.0.10" -notes = ''' -I grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits. - -There are a few places where `unsafe` is used. Unsafe review notes can be found -in https://crrev.com/c/5350697. - -Version 1.0.1 of this crate has been added to Chromium in -https://crrev.com/c/3321896. -''' -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.futures-util]] +who = "George Burgess IV " +criteria = "safe-to-run" +delta = "0.3.28 -> 0.3.31" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.itoa]] -who = "Lukasz Anforowicz " +[[audits.google.audits.glob]] +who = "George Burgess IV " criteria = "safe-to-deploy" -delta = "1.0.10 -> 1.0.11" -notes = """ -Straightforward diff between 1.0.10 and 1.0.11 - only 3 commits: - -* Bumping up the version -* A touch up of comments -* And my own PR to make `unsafe` blocks more granular: - https://github.com/dtolnay/itoa/pull/42 -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +version = "0.3.1" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.itoa]] -who = "Daniel Cheng " +[[audits.google.audits.glob]] +who = "Dustin J. Mitchell " criteria = "safe-to-deploy" -delta = "1.0.14 -> 1.0.15" -notes = "Only minor rustdoc changes." +delta = "0.3.1 -> 0.3.2" +notes = "Still no unsafe" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.io-uring]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "0.5.13" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.lazy_static]] who = "Lukasz Anforowicz " criteria = "safe-to-deploy" @@ -882,6 +1243,12 @@ describe in the review doc. """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.mio]] +who = "Vovo Yang " +criteria = "safe-to-run" +version = "0.8.8" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.nb]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -900,20 +1267,6 @@ criteria = "safe-to-deploy" delta = "1.0.0 -> 1.1.0" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.num-integer]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "0.1.46" -notes = "Contains no unsafe" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.num-rational]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "0.4.2" -notes = "Contains no unsafe" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.num-traits]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -1074,247 +1427,11 @@ delta = "0.4.0 -> 0.4.1" notes = "No unsafe, net or fs." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.semver]] -who = "Daniel Cheng " +[[audits.google.audits.slab]] +who = "Android Legacy" criteria = "safe-to-run" -delta = "1.0.25 -> 1.0.26" -notes = "Only minor documentation updates." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.0.197" -notes = """ -Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'`, `'\bnet\b'`, `'\bunsafe\b'`. - -There were some hits for `net`, but they were related to serialization and -not actually opening any connections or anything like that. - -There were 2 hits of `unsafe` when grepping: -* In `fn as_str` in `impl Buf` -* In `fn serialize` in `impl Serialize for net::Ipv4Addr` - -Unsafe review comments can be found in https://crrev.com/c/5350573/2 (this -review also covered `serde_json_lenient`). - -Version 1.0.130 of the crate has been added to Chromium in -https://crrev.com/c/3265545. The CL description contains a link to a -(Google-internal, sorry) document with a mini security review. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.197 -> 1.0.198" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "danakj " -criteria = "safe-to-deploy" -delta = "1.0.198 -> 1.0.201" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.201 -> 1.0.202" -notes = "Trivial changes" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.202 -> 1.0.203" -notes = "s/doc_cfg/docsrs/ + tuple_impls/tuple_impl_body-related changes" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.203 -> 1.0.204" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.204 -> 1.0.207" -notes = "The small change in `src/private/ser.rs` should have no impact on `ub-risk-2`." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.207 -> 1.0.209" -notes = """ -The delta carries fairly small changes in `src/private/de.rs` and -`src/private/ser.rs` (see https://crrev.com/c/5812194/2..5). AFAICT the -delta has no impact on the `unsafe`, `from_utf8_unchecked`-related parts -of the crate (in `src/de/format.rs` and `src/ser/impls.rs`). -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.209 -> 1.0.210" -notes = "Almost no new code - just feature rearrangement" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Liza Burakova " -criteria = "safe-to-deploy" -delta = "1.0.210 -> 1.0.213" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.213 -> 1.0.214" -notes = "No unsafe, no crypto" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.214 -> 1.0.215" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.215 -> 1.0.216" -notes = "The delta makes minor changes in `build.rs` - switching to the `?` syntax sugar." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.216 -> 1.0.217" -notes = "Minimal changes, nothing unsafe" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Daniel Cheng " -criteria = "safe-to-deploy" -delta = "1.0.217 -> 1.0.218" -notes = "No changes outside comments and documentation." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.218 -> 1.0.219" -notes = "Just allowing `clippy::elidable_lifetime_names`." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.0.197" -notes = 'Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits' -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "danakj " -criteria = "safe-to-deploy" -delta = "1.0.197 -> 1.0.201" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.201 -> 1.0.202" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.202 -> 1.0.203" -notes = 'Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits' -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.203 -> 1.0.204" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.204 -> 1.0.207" -notes = 'Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits' -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.207 -> 1.0.209" -notes = ''' -There are no code changes in this delta - see https://crrev.com/c/5812194/2..5 - -I've neverthless also grepped for `-i cipher`, `-i crypto`, `\bfs\b`, -`\bnet\b`, and `\bunsafe\b`. There were no hits. -''' -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.209 -> 1.0.210" -notes = "Almost no new code - just feature rearrangement" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Liza Burakova " -criteria = "safe-to-deploy" -delta = "1.0.210 -> 1.0.213" -notes = "Grepped for 'unsafe', 'crypt', 'cipher', 'fs', 'net' - there were no hits" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.213 -> 1.0.214" -notes = "No changes to unsafe, no crypto" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.214 -> 1.0.215" -notes = "Minor changes should not impact UB risk" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.215 -> 1.0.216" -notes = "The delta adds `#[automatically_derived]` in a few places. Still no `unsafe`." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.216 -> 1.0.217" -notes = "No changes" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Daniel Cheng " -criteria = "safe-to-deploy" -delta = "1.0.217 -> 1.0.218" -notes = "No changes outside comments and documentation." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.serde_derive]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.218 -> 1.0.219" -notes = "Minor changes (clippy tweaks, using `mem::take` instead of `mem::replace`)." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +version = "0.4.7" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" [[audits.google.audits.stable_deref_trait]] who = "Manish Goregaokar " @@ -1323,17 +1440,6 @@ version = "1.2.0" notes = "Purely a trait, crates using this should be carefully vetted since self-referential stuff can be super tricky around various unsafe rust edges." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.strsim]] -who = "danakj@chromium.org" -criteria = "safe-to-deploy" -version = "0.10.0" -notes = """ -Reviewed in https://crrev.com/c/5171063 - -Previously reviewed during security review and the audit is grandparented in. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.unicode-ident]] who = "Lukasz Anforowicz " criteria = "safe-to-deploy" @@ -1390,38 +1496,17 @@ criteria = "safe-to-run" version = "0.2.1" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.void]] +[[audits.google.audits.version_check]] who = "George Burgess IV " criteria = "safe-to-deploy" -version = "1.0.2" +version = "0.9.4" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.mozilla.wildcard-audits.encoding_rs]] -who = "Henri Sivonen " -criteria = "safe-to-deploy" -user-id = 4484 # Henri Sivonen (hsivonen) -start = "2019-02-26" -end = "2025-10-23" -notes = "I, Henri Sivonen, wrote encoding_rs for Gecko and have reviewed contributions by others. There are two caveats to the certification: 1) The crate does things that are documented to be UB but that do not appear to actually be UB due to integer types differing from the general rule; https://github.com/hsivonen/encoding_rs/issues/79 . 2) It would be prudent to re-review the code that reinterprets buffers of integers as SIMD vectors; see https://github.com/hsivonen/encoding_rs/issues/87 ." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.wildcard-audits.unicode-segmentation]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -user-id = 1139 # Manish Goregaokar (Manishearth) -start = "2019-05-15" -end = "2026-02-01" -notes = "All code written or reviewed by Manish" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.wildcard-audits.unicode-width]] -who = "Manish Goregaokar " +[[audits.google.audits.void]] +who = "George Burgess IV " criteria = "safe-to-deploy" -user-id = 1139 # Manish Goregaokar (Manishearth) -start = "2019-12-05" -end = "2026-02-01" -notes = "All code written or reviewed by Manish" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +version = "1.0.2" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" [[audits.mozilla.audits.adler2]] who = "Erich Gubler " @@ -1435,67 +1520,6 @@ criteria = "safe-to-deploy" version = "0.5.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.askama]] -who = "Ben Dean-Kawamura " -criteria = "safe-to-deploy" -version = "0.13.1" -notes = """ -Template crate. This is only used to generate the Rust/JS code for UniFFI. - -We used to use askama, then we switched to rinja which was a fork. Now rinja and -askama have merged again. - -The differences from askama 0.12, are pretty straightforward and don't seem risky to me. There's -some unsafe code and macros, but nothing that complicated. -""" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.askama]] -who = "Jan-Erik Rediger " -criteria = "safe-to-deploy" -delta = "0.13.1 -> 0.14.0" -aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" - -[[audits.mozilla.audits.askama_derive]] -who = "Ben Dean-Kawamura " -criteria = "safe-to-deploy" -version = "0.13.1" -notes = """ -Template crate. This is only used to generate the Rust/JS code for UniFFI. - -We used to use askama, then we switched to rinja which was a fork. Now rinja and -askama have merged again. - -I did a quick scan of the current code and couldn't find any issues. -""" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.askama_derive]] -who = "Jan-Erik Rediger " -criteria = "safe-to-deploy" -delta = "0.13.1 -> 0.14.0" -aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" - -[[audits.mozilla.audits.askama_parser]] -who = "Ben Dean-Kawamura " -criteria = "safe-to-deploy" -version = "0.13.0" -notes = """ -Template crate. This is only used to generate the Rust/JS code for UniFFI. - -We used to use askama, then we switched to rinja which was a fork. Now rinja and -askama have merged again. - -I did a quick scan of the current code and couldn't find any issues. -""" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.askama_parser]] -who = "Jan-Erik Rediger " -criteria = "safe-to-deploy" -delta = "0.13.0 -> 0.14.0" -aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" - [[audits.mozilla.audits.bitflags]] who = "Alex Franchuk " criteria = "safe-to-deploy" @@ -1567,13 +1591,6 @@ criteria = "safe-to-deploy" delta = "0.2.10 -> 0.2.11" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.fnv]] -who = "Bobby Holley " -criteria = "safe-to-deploy" -version = "1.0.7" -notes = "Simple hasher implementation with no unsafe code." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.audits.futures-core]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -1609,28 +1626,41 @@ criteria = "safe-to-deploy" delta = "1.8.3 -> 2.5.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.hashbrown]] +[[audits.mozilla.audits.litrs]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +version = "0.4.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.mio]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.8.8 -> 1.0.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.once_cell]] who = "Erich Gubler " criteria = "safe-to-deploy" -delta = "0.15.2 -> 0.15.5" +delta = "1.20.1 -> 1.20.2" +notes = "This update works around a Cargo bug that forces the addition of `portable-atomic` into a lockfile, which we have never needed to use." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.percent-encoding]] -who = "Valentin Gosu " +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " criteria = "safe-to-deploy" -delta = "2.2.0 -> 2.3.0" +delta = "1.20.2 -> 1.20.3" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.percent-encoding]] -who = "Valentin Gosu " +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " criteria = "safe-to-deploy" -delta = "2.3.0 -> 2.3.1" +delta = "1.20.3 -> 1.21.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.percent-encoding]] -who = "edgul " +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " criteria = "safe-to-deploy" -delta = "2.3.1 -> 2.3.2" +delta = "1.21.1 -> 1.21.3" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" [[audits.mozilla.audits.pin-project-lite]] @@ -1649,26 +1679,6 @@ Only functional change is to work around a bug in the negative_impls feature """ aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" -[[audits.mozilla.audits.rustc-hash]] -who = "Bobby Holley " -criteria = "safe-to-deploy" -version = "1.1.0" -notes = "Straightforward crate with no unsafe code, does what it says on the tin." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.rustc-hash]] -who = "Ben Dean-Kawamura " -criteria = "safe-to-deploy" -delta = "1.1.0 -> 2.1.1" -notes = "Simple hashing crate, no unsafe code." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.semver]] -who = "Jan-Erik Rediger " -criteria = "safe-to-deploy" -delta = "1.0.17 -> 1.0.25" -aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" - [[audits.mozilla.audits.serde_core]] who = "Erich Gubler " criteria = "safe-to-deploy" @@ -1693,10 +1703,10 @@ criteria = "safe-to-deploy" delta = "1.1.0 -> 1.3.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.strsim]] -who = "Ben Dean-Kawamura " +[[audits.mozilla.audits.slab]] +who = "Mike Hommey " criteria = "safe-to-deploy" -delta = "0.10.0 -> 0.11.1" +delta = "0.4.7 -> 0.4.8" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" [[audits.mozilla.audits.tracing]] diff --git a/type-c-interface/src/service/event.rs b/type-c-interface/src/service/event.rs index 08ac63cae..88b4e7ea6 100644 --- a/type-c-interface/src/service/event.rs +++ b/type-c-interface/src/service/event.rs @@ -3,16 +3,28 @@ use embedded_usb_pd::{GlobalPortId, ado::Ado}; use crate::port::{ - PortStatus, + DpStatus, PortStatus, event::{PortStatusEventBitfield, VdmData}, }; +/// Struct containing data for a [`PortEventData::StatusChanged`] event +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StatusChangedData { + /// Status changed event + pub status_event: PortStatusEventBitfield, + /// Previous port status + pub previous_status: PortStatus, + /// Current port status + pub current_status: PortStatus, +} + /// Enum to contain all port event variants #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PortEventData { /// Port status change events - StatusChanged(PortStatusEventBitfield, PortStatus), + StatusChanged(StatusChangedData), /// PD alert Alert(Ado), /// VDM @@ -22,7 +34,7 @@ pub enum PortEventData { /// USB mux error recovery UsbMuxErrorRecovery, /// DP status update - DpStatusUpdate, + DpStatusUpdate(DpStatus), } /// Struct containing a complete port event diff --git a/type-c-service/src/wrapper/config.rs b/type-c-service/src/controller/config.rs similarity index 100% rename from type-c-service/src/wrapper/config.rs rename to type-c-service/src/controller/config.rs diff --git a/type-c-service/src/controller/event.rs b/type-c-service/src/controller/event.rs new file mode 100644 index 000000000..40c91bc38 --- /dev/null +++ b/type-c-service/src/controller/event.rs @@ -0,0 +1,22 @@ +//! Port event types + +use type_c_interface::port::event::PortEventBitfield; + +/// Top-level port event type +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Event { + /// Port event + PortEvent(type_c_interface::port::event::PortEvent), +} + +/// Loopback event to allow `sync_state` and similar functions +/// to generate events that can be processed by the same code as real events. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Loopback { + /// Port event + PortEvent(PortEventBitfield), +} diff --git a/type-c-service/src/controller/event_receiver.rs b/type-c-service/src/controller/event_receiver.rs new file mode 100644 index 000000000..8a17f93a0 --- /dev/null +++ b/type-c-service/src/controller/event_receiver.rs @@ -0,0 +1,140 @@ +//! This module contains event receiver types for the controller wrapper. +use core::array; +use core::future::pending; +use embassy_futures::select::{Either, select}; +use embassy_time::Timer; +use embedded_services::event::{Receiver, Sender}; +use embedded_services::sync::Lockable; + +use crate::PortEventStreamer; +use crate::controller::event::{Event, Loopback}; +use crate::controller::state::SharedState; +use type_c_interface::port::event::{PortEvent, PortEventBitfield, PortStatusEventBitfield}; + +/// Trait used for receiving interrupt from the controller. +pub trait InterruptReceiver { + /// Wait for the next interrupt event. + fn wait_interrupt(&mut self) -> impl Future; +} + +/// Struct to send received interrupts to their corresponding port receivers +pub struct PortEventSplitter> { + /// Senders to forward port events to their corresponding port receivers + sender: [S; N], +} + +impl> PortEventSplitter { + /// Create a new instance + pub fn new(sender: [S; N]) -> Self { + Self { sender } + } + + /// Wait for the next interrupt event and forward it to the corresponding port receiver. + pub async fn process_interrupts(&mut self, interrupts: [PortEventBitfield; N]) { + for (interrupt, sender) in interrupts.into_iter().zip(self.sender.iter_mut()) { + if interrupt != PortEventBitfield::none() { + sender.send(interrupt).await; + } + } + } +} + +/// Struct to receive and stream port events from the controller. +pub struct PortEventReceiver, LoopbackReceiver: Receiver> { + /// Receiver for the controller's interrupt events + receiver: R, + /// Port event streaming state + streaming_state: Option>>, + /// Loopback receiver for software-generated events + loopback_receiver: LoopbackReceiver, +} + +impl, LoopbackReceiver: Receiver> PortEventReceiver { + /// Create a new instance + pub fn new(receiver: R, loopback_receiver: LoopbackReceiver) -> Self { + Self { + receiver, + streaming_state: None, + loopback_receiver, + } + } + + /// Wait for the next port event + pub async fn wait_next(&mut self) -> type_c_interface::port::event::PortEvent { + loop { + let streaming_state = if let Some(streaming_state) = &mut self.streaming_state { + // Yield to ensure we don't monopolize the executor + embassy_futures::yield_now().await; + streaming_state + } else { + let (Either::First(Loopback::PortEvent(events)) | Either::Second(events)) = + select(self.loopback_receiver.wait_next(), self.receiver.wait_next()).await; + self.streaming_state + .insert(PortEventStreamer::new([events].into_iter())) + }; + + if let Some((_, event)) = streaming_state.next() { + return event; + } else { + self.streaming_state = None; + } + } + } +} + +/// Struct used for containing controller event receivers. +pub struct EventReceiver< + 'a, + State: Lockable, + InterruptReceiver: Receiver, + LoopbackReceiver: Receiver, +> { + /// Port event receiver + port_event_receiver: PortEventReceiver, + /// Shared state + shared_state: &'a State, +} + +impl< + 'a, + State: Lockable, + InterruptReceiver: Receiver, + LoopbackReceiver: Receiver, +> EventReceiver<'a, State, InterruptReceiver, LoopbackReceiver> +{ + /// Create a new instance + pub fn new( + shared_state: &'a State, + port_event_receiver: InterruptReceiver, + loopback_receiver: LoopbackReceiver, + ) -> Self { + Self { + shared_state, + port_event_receiver: PortEventReceiver::new(port_event_receiver, loopback_receiver), + } + } + + /// Wait for the next port event from any port. + /// + /// Returns the local port ID and the event bitfield. + pub async fn wait_event(&mut self) -> Event { + let timeout = self.shared_state.lock().await.sink_ready_timeout; + match select(self.port_event_receiver.wait_next(), async move { + if let Some(timeout) = timeout { + Timer::at(timeout).await; + } else { + pending::<()>().await; + } + }) + .await + { + Either::First(event) => Event::PortEvent(event), + Either::Second(_) => { + let mut status_event = PortStatusEventBitfield::none(); + status_event.set_sink_ready(true); + self.shared_state.lock().await.sink_ready_timeout = None; + Event::PortEvent(PortEvent::StatusChanged(status_event)) + } + } + } +} diff --git a/type-c-service/src/controller/macros.rs b/type-c-service/src/controller/macros.rs new file mode 100644 index 000000000..8d27f94e4 --- /dev/null +++ b/type-c-service/src/controller/macros.rs @@ -0,0 +1,170 @@ +use embedded_services::{ + event::{Receiver, Sender}, + sync::Lockable, +}; +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_LOOPBACK_CHANNEL_SIZE: usize = 1; +pub const DEFAULT_INTERRUPT_CHANNEL_SIZE: usize = 4; + +/// Components returned from port creation +pub struct PortComponents< + 'a, + Port, + SharedState: Lockable, + PowerPolicyReceveiver: Receiver, + LoopbackReceiver: Receiver, + InterruptReceiver: Receiver, + InterruptSender: Sender, +> { + /// Port instance + pub port: &'a Port, + /// Power policy event receiver + pub power_policy_receiver: PowerPolicyReceveiver, + /// Port event receiver + pub event_receiver: EventReceiver<'a, SharedState, InterruptReceiver, LoopbackReceiver>, + /// Interrupt sender + pub interrupt_sender: InterruptSender, +} + +/// Creates a module containing all state for a controller port, based on static cells and channels. +#[macro_export] +macro_rules! define_controller_port_static_cell_channel { + ($vis:vis, $name:ident, $mutex:ty, $controller:ty) => { + $vis mod $name { + use super::*; + + // We prefix all aliases with 'Inner' to avoid potential name conflicts with user code when this macro is invoked + // Unfortunately, super::$ty is not valid syntax in a macro, so we have to pull in everything with super::*. + /// Type alias for the power policy sender + pub type InnerPowerPolicySenderType = + ::embassy_sync::channel::DynamicSender<'static, ::power_policy_interface::psu::event::EventData>; + /// Type alias for the power policy receiver + pub type InnerPowerPolicyReceiverType = + ::embassy_sync::channel::DynamicReceiver<'static, ::power_policy_interface::psu::event::EventData>; + + /// Type alias for the loopback sender + pub type InnerLoopbackSenderType = + ::embassy_sync::channel::DynamicSender<'static, $crate::controller::event::Loopback>; + /// Type alias for the loopback receiver + pub type InnerLoopbackReceiverType = + ::embassy_sync::channel::DynamicReceiver<'static, $crate::controller::event::Loopback>; + + /// Type alias for the interrupt sender + pub type InnerInterruptReceiverType = + ::embassy_sync::channel::DynamicReceiver<'static, ::type_c_interface::port::event::PortEventBitfield>; + /// Type alias for the interrupt receiver + pub type InnerInterruptSenderType = + ::embassy_sync::channel::DynamicSender<'static, ::type_c_interface::port::event::PortEventBitfield>; + + /// Type alias for the shared state mutex + pub type InnerSharedStateType = + ::embassy_sync::mutex::Mutex<$mutex, $crate::controller::state::SharedState>; + /// Type alias for the port + pub type InnerPortType = ::embassy_sync::mutex::Mutex< + $mutex, + $crate::controller::Port< + 'static, + // Controller type + $controller, + // Shared state type + InnerSharedStateType, + // Power policy event sender type + InnerPowerPolicySenderType, + // Loopback event sender type + InnerLoopbackSenderType, + >, + >; + + /// Channel to send events to the power policy service + static POWER_POLICY_CHANNEL: ::static_cell::StaticCell< + ::embassy_sync::channel::Channel< + $mutex, + ::power_policy_interface::psu::event::EventData, + { $crate::controller::macros::DEFAULT_POWER_POLICY_CHANNEL_SIZE }, + >, + > = ::static_cell::StaticCell::new(); + /// Loopback channel + static LOOPBACK_CHANNEL: ::static_cell::StaticCell< + ::embassy_sync::channel::Channel< + $mutex, + $crate::controller::event::Loopback, + { $crate::controller::macros::DEFAULT_LOOPBACK_CHANNEL_SIZE }, + >, + > = ::static_cell::StaticCell::new(); + /// Interrupt channel + static INTERRUPT_CHANNEL: ::static_cell::StaticCell< + ::embassy_sync::channel::Channel< + $mutex, + ::type_c_interface::port::event::PortEventBitfield, + { $crate::controller::macros::DEFAULT_INTERRUPT_CHANNEL_SIZE }, + >, + > = ::static_cell::StaticCell::new(); + /// State shared between the port and event receiver + static SHARED_STATE: ::static_cell::StaticCell< + ::embassy_sync::mutex::Mutex<$mutex, $crate::controller::state::SharedState>, + > = ::static_cell::StaticCell::new(); + /// Port instance + static PORT: ::static_cell::StaticCell = ::static_cell::StaticCell::new(); + + 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, + InnerPowerPolicyReceiverType, + InnerLoopbackReceiverType, + InnerInterruptReceiverType, + InnerInterruptSenderType, + > { + let shared_state = SHARED_STATE.init(::embassy_sync::mutex::Mutex::new( + $crate::controller::state::SharedState::new(), + )); + + let power_policy_channel = POWER_POLICY_CHANNEL.init(::embassy_sync::channel::Channel::new()); + let power_policy_sender = power_policy_channel.dyn_sender(); + let power_policy_receiver = power_policy_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(); + + let interrupt_channel = INTERRUPT_CHANNEL.init(::embassy_sync::channel::Channel::new()); + let interrupt_sender = interrupt_channel.dyn_sender(); + let interrupt_receiver = interrupt_channel.dyn_receiver(); + + let port = PORT.init(::embassy_sync::mutex::Mutex::new($crate::controller::Port::new( + name, + config, + port, + global_port, + controller, + shared_state, + power_policy_sender, + loopback_sender, + context, + ))); + let event_receiver = $crate::controller::event_receiver::EventReceiver::new( + shared_state, + interrupt_receiver, + loopback_receiver, + ); + $crate::controller::macros::PortComponents { + port, + power_policy_receiver, + event_receiver, + interrupt_sender, + } + } + } + }; +} diff --git a/type-c-service/src/controller/mod.rs b/type-c-service/src/controller/mod.rs new file mode 100644 index 000000000..dc6bf0d84 --- /dev/null +++ b/type-c-service/src/controller/mod.rs @@ -0,0 +1,242 @@ +//! 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::{Error, GlobalPortId, LocalPortId, PdError}; +use power_policy_interface::psu::PsuState; +use type_c_interface::port::event::PortEventBitfield; +use type_c_interface::port::{ + Controller, PortStatus, event::PortEvent as InterfacePortEvent, event::PortStatusEventBitfield, +}; +use type_c_interface::service::event::{ + PortEvent as ServicePortEvent, PortEventData as ServicePortEventData, StatusChangedData, +}; + +use crate::controller::event::{Event, Loopback}; +use crate::controller::state::SharedState; + +pub mod config; +pub mod event; +pub mod event_receiver; +pub mod macros; +mod pd; +mod power; +pub mod state; + +pub struct Port< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> { + /// Local port + port: LocalPortId, + /// Global port + global_port: GlobalPortId, + /// Controller + controller: &'device C, + /// Per-port PSU state + psu_state: power_policy_interface::psu::State, + /// Name for this port + name: &'static str, + /// Cached port status + status: PortStatus, + /// 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, +} + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + /// Create new Port instance + // Argument count will be reduced as the last bit of refactoring is done + #[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, + 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, + } + } + + /// Top-level processing function + pub async fn process_event( + &mut self, + event: Event, + ) -> Result, Error<::BusError>> { + match event { + Event::PortEvent(port_event) => self.process_port_event(port_event).await, + } + } + + /// Process a port notification + async fn process_port_event( + &mut self, + event: InterfacePortEvent, + ) -> Result, Error<::BusError>> { + match event { + InterfacePortEvent::StatusChanged(status_event) => { + self.process_port_status_changed(status_event).await.map(Some) + } + InterfacePortEvent::Alert => self.process_pd_alert().await, + InterfacePortEvent::Vdm(vdm_event) => self.process_vdm_event(vdm_event).await.map(Some), + InterfacePortEvent::DpStatusUpdate => self.process_dp_status_update().await.map(Some), + rest => { + // Nothing currently implemented for these + debug!("({}): Notification: {:#?}", self.name, rest); + Ok(None) + } + } + } + + /// Process port status changed events + async fn process_port_status_changed( + &mut self, + status_event: PortStatusEventBitfield, + ) -> Result::BusError>> { + let new_status = self.controller.lock().await.get_port_status(self.port).await?; + debug!("({}) status: {:#?}", self.name, new_status); + debug!("({}) status events: {:#?}", self.name, status_event); + + if status_event.plug_inserted_or_removed() { + self.process_plug_event(&new_status).await?; + } + + // Only notify power policy of a contract after Sink Ready event (always after explicit or implicit contract) + if status_event.sink_ready() { + self.process_new_consumer_contract(&new_status).await?; + } + + if status_event.new_power_contract_as_provider() { + self.process_new_provider_contract(&new_status).await?; + } + + self.check_sink_ready_timeout( + &new_status, + status_event.new_power_contract_as_consumer(), + status_event.sink_ready(), + ) + .await?; + + let event = ServicePortEventData::StatusChanged(StatusChangedData { + status_event, + previous_status: self.status, + current_status: new_status, + }); + self.status = new_status; + self.context + .send_port_event(ServicePortEvent { + port: self.global_port, + event, + }) + .await + .map_err(Error::Pd)?; + Ok(event) + } + + /// Handle a plug event + async fn process_plug_event( + &mut self, + new_status: &PortStatus, + ) -> Result<(), Error<::BusError>> { + info!("Plug event"); + if new_status.is_connected() { + info!("Plug inserted"); + if self.psu_state.psu_state != PsuState::Detached { + info!("Device not in detached state, recovering"); + self.psu_state.detach(); + } + + if let Err(e) = self.psu_state.attach() { + // This should never happen because we should have detached above + error!("Failed to attach PSU: {:?}", e); + return Err(Error::Pd(PdError::Failed)); + } + + self.power_policy_sender + .send(power_policy_interface::psu::event::EventData::Attached) + .await; + } else { + info!("Plug removed"); + self.psu_state.detach(); + self.power_policy_sender + .send(power_policy_interface::psu::event::EventData::Detached) + .await; + } + + Ok(()) + } + + /// Get the cached port status, returns None if the port is invalid + pub fn get_cached_port_status(&self) -> PortStatus { + self.status + } + + /// Synchronize the state between the controller and the internal state + pub async fn sync_state(&mut self) -> Result<(), Error<::BusError>> { + let status = self.controller.lock().await.get_port_status(self.port).await?; + + let mut event = PortEventBitfield::none(); + let previous_status = self.status; + + if previous_status.is_connected() != status.is_connected() { + event.status.set_plug_inserted_or_removed(true); + } + + if previous_status.available_sink_contract != status.available_sink_contract { + event.status.set_new_power_contract_as_consumer(true); + } + + if previous_status.available_source_contract != status.available_source_contract { + event.status.set_new_power_contract_as_provider(true); + } + + if event != PortEventBitfield::none() { + self.loopback_sender.send(Loopback::PortEvent(event)).await; + } + Ok(()) + } +} + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> Named for Port<'device, C, Shared, 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 new file mode 100644 index 000000000..07d71c9f4 --- /dev/null +++ b/type-c-service/src/controller/pd.rs @@ -0,0 +1,84 @@ +//! PD functionality unrelated to power contracts and general port status +use embedded_services::{event::Sender, sync::Lockable}; +use type_c_interface::port::{ + Controller, + event::{VdmData, VdmNotification}, +}; +use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventData as ServicePortEventData}; + +use super::*; +use crate::controller::state::SharedState; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> Port<'device, C, Shared, 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::BusError>> { + debug!("({}): Processing VDM event: {:?}", self.name, event); + let vdm_data = { + let mut controller = self.controller.lock().await; + match event { + VdmNotification::Entered => VdmData::Entered(controller.get_other_vdm(self.port).await?), + VdmNotification::Exited => VdmData::Exited(controller.get_other_vdm(self.port).await?), + VdmNotification::OtherReceived => VdmData::ReceivedOther(controller.get_other_vdm(self.port).await?), + VdmNotification::AttentionReceived => VdmData::ReceivedAttn(controller.get_attn_vdm(self.port).await?), + } + }; + + let event = ServicePortEventData::Vdm(vdm_data); + let _ = self + .context + .send_port_event(ServicePortEvent { + port: self.global_port, + event: ServicePortEventData::Vdm(vdm_data), + }) + .await; + Ok(event) + } + + /// Process a DisplayPort status update by retrieving the current DP status from the `controller` for the appropriate `port`. + pub(super) async fn process_dp_status_update( + &mut self, + ) -> Result::BusError>> { + 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; + Ok(event) + } + + pub(super) async fn process_pd_alert( + &mut self, + ) -> Result, Error<::BusError>> { + let ado = self.controller.lock().await.get_pd_alert(self.port).await?; + 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; + Ok(Some(event)) + } else { + // For some reason we didn't read an alert, nothing to do + Ok(None) + } + } +} diff --git a/type-c-service/src/controller/power.rs b/type-c-service/src/controller/power.rs new file mode 100644 index 000000000..8fd0bd6e0 --- /dev/null +++ b/type-c-service/src/controller/power.rs @@ -0,0 +1,172 @@ +//! Module for power policy related functionality +use embassy_time::{Duration, Instant}; +use embedded_services::{debug, error, event::Sender, info, sync::Lockable}; +use embedded_usb_pd::{ + PdError, + constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}, +}; +use power_policy_interface::{ + capability::{ConsumerPowerCapability, ProviderPowerCapability, PsuType}, + psu::{Error as PsuError, Psu, State}, +}; +use type_c_interface::port::Controller; + +use crate::{controller::config::UnconstrainedSink, util::power_policy_error_from_pd_bus_error}; + +use super::*; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + /// Handle a new contract as consumer + pub(super) async fn process_new_consumer_contract( + &mut self, + new_status: &PortStatus, + ) -> Result<(), Error<::BusError>> { + info!("Process new consumer contract"); + let available_sink_contract = new_status.available_sink_contract.map(|c| { + let mut c: ConsumerPowerCapability = c.into(); + let unconstrained = match self.config.unconstrained_sink { + UnconstrainedSink::Auto => new_status.unconstrained_power, + UnconstrainedSink::PowerThresholdMilliwatts(threshold) => c.capability.max_power_mw() >= threshold, + UnconstrainedSink::Never => false, + }; + c.flags.set_unconstrained_power(unconstrained); + c.flags.set_psu_type(PsuType::TypeC); + c + }); + + if let Err(e) = self.psu_state.update_consumer_power_capability(available_sink_contract) { + error!("Failed to update consumer power capability: {:?}", e); + return Err(Error::Pd(PdError::Failed)); + } + self.power_policy_sender + .send(power_policy_interface::psu::event::EventData::UpdatedConsumerCapability(available_sink_contract)) + .await; + Ok(()) + } + + /// Handle a new contract as provider + pub(super) async fn process_new_provider_contract( + &mut self, + new_status: &PortStatus, + ) -> Result<(), Error<::BusError>> { + info!("Process New provider contract"); + let capability = new_status.available_source_contract.map(|caps| { + let mut caps = ProviderPowerCapability::from(caps); + caps.flags.set_psu_type(PsuType::TypeC); + caps + }); + if let Err(e) = self.psu_state.update_requested_provider_power_capability(capability) { + error!("Failed to update requested provider power capability: {:?}", e); + return Err(Error::Pd(PdError::Failed)); + } + self.power_policy_sender + .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) + .await; + Ok(()) + } + + /// Check the sink ready timeout + /// + /// After accepting a sink contract (new contract as consumer), the PD spec guarantees that the + /// source will be available to provide power after `tPSTransition`. This allows us to handle transitions + /// even for controllers that might not always broadcast sink ready events. + pub(super) async fn check_sink_ready_timeout( + &mut self, + new_status: &PortStatus, + new_contract: bool, + sink_ready: bool, + ) -> Result<(), PdError> { + let contract_changed = self.status.available_sink_contract != new_status.available_sink_contract; + let mut shared_state = self.shared_state.lock().await; + let timeout = &mut shared_state.sink_ready_timeout; + + // Don't start the timeout if the sink has signaled it's ready or if the contract didn't change. + // The latter ensures that soft resets won't continually reset the ready timeout + debug!( + "({}): Check sink ready: new_contract={:?}, sink_ready={:?}, contract_changed={:?}, deadline={:?}", + self.name, new_contract, sink_ready, contract_changed, timeout, + ); + if new_contract && !sink_ready && contract_changed { + // Start the timeout + // Double the spec maximum transition time to provide a safety margin for hardware/controller delays or out-of-spec controllers. + let timeout_ms = if new_status.epr { + T_PS_TRANSITION_EPR_MS + } else { + T_PS_TRANSITION_SPR_MS + } + .maximum + .0 * 2; + + debug!("({}): Sink ready timeout started for {}ms", self.name, timeout_ms); + *timeout = Some(Instant::now() + Duration::from_millis(timeout_ms as u64)); + } else if timeout.is_some() + && (!new_status.is_connected() || new_status.available_sink_contract.is_none() || sink_ready) + { + debug!("({}): Sink ready timeout cleared", self.name); + *timeout = None; + } + Ok(()) + } +} + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> Psu for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn disconnect(&mut self) -> Result<(), PsuError> { + self.controller + .lock() + .await + .enable_sink_path(self.port, false) + .await + .map_err(|e| { + error!("({}): Error disabling sink path", self.name); + power_policy_error_from_pd_bus_error(e) + })?; + self.psu_state.disconnect(false) + } + + async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), PsuError> { + info!("({}): Connect as provider: {:#?}", self.name, capability); + // TODO: Implement controller over provider enablement + self.psu_state.connect_provider(capability).inspect_err(|e| { + error!("({}): Failed to transition to provider state: {:#?}", self.name, e); + }) + } + + async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), PsuError> { + info!( + "({}): Connect as consumer: {:?}, enable input switch", + self.name, capability + ); + self.controller + .lock() + .await + .enable_sink_path(self.port, true) + .await + .map_err(|e| { + error!("({}): Error enabling sink path", self.name); + power_policy_error_from_pd_bus_error(e) + })?; + self.psu_state.connect_consumer(capability) + } + + fn state(&self) -> &State { + &self.psu_state + } + + fn state_mut(&mut self) -> &mut State { + &mut self.psu_state + } +} diff --git a/type-c-service/src/controller/state.rs b/type-c-service/src/controller/state.rs new file mode 100644 index 000000000..c19fd3a6f --- /dev/null +++ b/type-c-service/src/controller/state.rs @@ -0,0 +1,23 @@ +use embassy_time::Instant; + +/// State shared between the port and event receiver +#[derive(Copy, Clone)] +pub struct SharedState { + /// Sink ready timeout + pub(crate) sink_ready_timeout: Option, +} + +impl SharedState { + /// Create a new instance with default values + pub fn new() -> Self { + Self { + sink_ready_timeout: None, + } + } +} + +impl Default for SharedState { + fn default() -> Self { + Self::new() + } +} diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index 6a17a43df..906a298ea 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -871,21 +871,11 @@ bitfield! { pub u32, ti_fw_version, set_ti_fw_version: 63, 32; } -pub struct InterruptReceiver<'a, M: RawMutex, BUS: I2c> { - interrupt_receiver: interrupt::InterruptReceiver<'a, M, BUS>, -} - -impl<'a, M: RawMutex, BUS: I2c> InterruptReceiver<'a, M, BUS> { - pub fn new(interrupt_receiver: interrupt::InterruptReceiver<'a, M, BUS>) -> Self { - Self { interrupt_receiver } - } -} - -impl<'a, M: RawMutex, BUS: I2c> crate::wrapper::event_receiver::InterruptReceiver - for InterruptReceiver<'a, M, BUS> +impl<'a, M: RawMutex, BUS: I2c> crate::controller::event_receiver::InterruptReceiver + for interrupt::InterruptReceiver<'a, M, BUS> { async fn wait_interrupt(&mut self) -> [PortEventBitfield; MAX_SUPPORTED_PORTS] { - let interrupts = self.interrupt_receiver.wait_any(false).await; + let interrupts = self.wait_any(false).await; let mut port_events = [PortEventBitfield::none(); MAX_SUPPORTED_PORTS]; for (interrupt, event) in zip(interrupts.iter(), port_events.iter_mut()) { if *interrupt == IntEventBus1::new_zero() { diff --git a/type-c-service/src/lib.rs b/type-c-service/src/lib.rs index 3ccaa27d6..9e42b2000 100644 --- a/type-c-service/src/lib.rs +++ b/type-c-service/src/lib.rs @@ -1,10 +1,10 @@ #![no_std] pub mod bridge; +pub mod controller; pub mod driver; pub mod service; pub mod task; pub mod util; -pub mod wrapper; use core::iter::Enumerate; diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 1e1db4b18..70e7f11e6 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -133,8 +133,9 @@ impl<'a> Service<'a> { async fn process_port_event(&mut self, event: &PortEvent) -> Result<(), Error> { match &event.event { - PortEventData::StatusChanged(status_event, status) => { - self.process_port_status_event(event.port, *status_event, *status).await + PortEventData::StatusChanged(status_event) => { + self.process_port_status_event(event.port, status_event.status_event, status_event.current_status) + .await } unhandled => { // Currently just log notifications, but may want to do more in the future diff --git a/type-c-service/src/task.rs b/type-c-service/src/task.rs index cd1ed7dd9..98dbe4f18 100644 --- a/type-c-service/src/task.rs +++ b/type-c-service/src/task.rs @@ -1,36 +1,15 @@ -use embedded_services::{ - error, - event::{self, Receiver}, - info, - sync::Lockable, -}; +use embedded_services::{error, event::Receiver, info, sync::Lockable}; use power_policy_interface::service::event::EventData as PowerPolicyEventData; -use crate::{ - service::{EventReceiver, Service}, - wrapper::ControllerWrapper, -}; +use crate::service::{EventReceiver, Service}; /// Task to run the Type-C service, running the default event loop -pub async fn task, const N: usize>( +pub async fn task>( service: &'static impl Lockable>, mut event_receiver: EventReceiver<'static, PowerReceiver>, - wrappers: [&'static ControllerWrapper<'static, M, D, S>; N], -) where - M: embassy_sync::blocking_mutex::raw::RawMutex, - D: embedded_services::sync::Lockable, - S: event::Sender, - ::Inner: type_c_interface::port::Controller, -{ +) { info!("Starting type-c task"); - for controller_wrapper in wrappers { - if controller_wrapper.register().is_err() { - error!("Failed to register a controller"); - return; - } - } - loop { let event = event_receiver.wait_next().await; if let Err(e) = service.lock().await.process_event(event).await { diff --git a/type-c-service/src/wrapper/backing.rs b/type-c-service/src/wrapper/backing.rs deleted file mode 100644 index 4d287ebf6..000000000 --- a/type-c-service/src/wrapper/backing.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Various types of state and objects required for [`crate::wrapper::ControllerWrapper`]. -//! -//! TODO: update this documentation when the type-C service is refactored -//! -use core::array::from_fn; - -use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; - -use embedded_services::event; - -use type_c_interface::port::{ControllerId, PortRegistration, PortStatus, event::PortStatusEventBitfield}; - -use crate::wrapper::proxy::{PowerProxyChannel, PowerProxyDevice, PowerProxyReceiver}; - -/// Service registration objects -pub struct Registration<'a, M: RawMutex> { - pub context: &'a type_c_interface::service::context::Context, - pub pd_controller: &'a type_c_interface::port::Device<'a>, - pub power_devices: &'a [&'a Mutex>], -} - -impl<'a, M: RawMutex> Registration<'a, M> { - pub fn num_ports(&self) -> usize { - self.power_devices.len() - } -} - -/// Base storage -pub struct Storage<'a, const N: usize, M: RawMutex> { - // Registration-related - context: &'a type_c_interface::service::context::Context, - controller_id: ControllerId, - pd_ports: [PortRegistration; N], - power_proxy_channels: [PowerProxyChannel; N], -} - -impl<'a, const N: usize, M: RawMutex> Storage<'a, N, M> { - pub fn new( - context: &'a type_c_interface::service::context::Context, - controller_id: ControllerId, - pd_ports: [PortRegistration; N], - ) -> Self { - Self { - context, - controller_id, - pd_ports, - power_proxy_channels: from_fn(|_| PowerProxyChannel::new()), - } - } - - /// Create intermediate storage from this storage - pub fn try_create_intermediate>( - &self, - power_policy_init: [(&'static str, S); N], - ) -> Option<(IntermediateStorage<'_, N, M, S>, [PowerProxyReceiver<'_>; N])> { - IntermediateStorage::try_from_storage(self, power_policy_init) - } -} - -pub struct Port<'a, M: RawMutex, S: event::Sender> { - pub proxy: Mutex>, - pub state: Mutex>, -} - -pub struct PortState> { - /// Cached port status - pub(crate) status: PortStatus, - /// Software status event - pub(crate) sw_status_event: PortStatusEventBitfield, - /// Sender to send events to the power policy service - pub(crate) power_policy_sender: S, -} - -impl> PortState { - pub fn new(power_policy_sender: S) -> Self { - Self { - status: PortStatus::default(), - sw_status_event: PortStatusEventBitfield::default(), - power_policy_sender, - } - } -} - -/// Intermediate storage that holds power proxy devices -pub struct IntermediateStorage< - 'a, - const N: usize, - M: RawMutex, - S: event::Sender, -> { - storage: &'a Storage<'a, N, M>, - ports: [Port<'a, M, S>; N], -} - -impl<'a, const N: usize, M: RawMutex, S: event::Sender> - IntermediateStorage<'a, N, M, S> -{ - fn try_from_storage( - storage: &'a Storage<'a, N, M>, - power_policy_init: [(&'static str, S); N], - ) -> Option<(Self, [PowerProxyReceiver<'a>; N])> { - let mut ports = heapless::Vec::<_, N>::new(); - let mut power_proxy_receivers = heapless::Vec::<_, N>::new(); - - for (power_proxy_channel, (name, policy_sender)) in - storage.power_proxy_channels.iter().zip(power_policy_init.into_iter()) - { - let (device_sender, device_receiver) = power_proxy_channel.get_device_components(); - - ports - .push(Port { - proxy: Mutex::new(PowerProxyDevice::new(name, device_sender, device_receiver)), - state: Mutex::new(PortState::new(policy_sender)), - }) - .ok()?; - power_proxy_receivers.push(power_proxy_channel.get_receiver()).ok()?; - } - - Some(( - Self { - storage, - ports: ports.into_array().ok()?, - }, - power_proxy_receivers.into_array().ok()?, - )) - } - - /// Create referenced storage from this intermediate storage - pub fn try_create_referenced<'b>(&'b self) -> Option> - where - 'b: 'a, - { - ReferencedStorage::try_from_intermediate(self) - } -} - -/// Contains any values that need to reference [`Storage`] -/// -/// To simplify usage, we use interior mutability through a ref cell to avoid having to declare the state -/// completely separately. -pub struct ReferencedStorage< - 'a, - const N: usize, - M: RawMutex, - S: event::Sender, -> { - intermediate: &'a IntermediateStorage<'a, N, M, S>, - pub pd_controller: type_c_interface::port::Device<'a>, - power_devices: [&'a Mutex>; N], -} - -impl<'a, const N: usize, M: RawMutex, S: event::Sender> - ReferencedStorage<'a, N, M, S> -{ - /// Create a new referenced storage from the given intermediate storage - fn try_from_intermediate(intermediate: &'a IntermediateStorage<'a, N, M, S>) -> Option { - Some(Self { - intermediate, - pd_controller: type_c_interface::port::Device::new( - intermediate.storage.controller_id, - intermediate.storage.pd_ports.as_slice(), - ), - // Panic safety: will not panic because array length is fixed by generic argument - #[allow(clippy::indexing_slicing)] - power_devices: from_fn(|i| &intermediate.ports[i].proxy), - }) - } - - /// Creates the backing, returns `None` if a backing has already been created - pub fn create_backing<'b>(&'b self) -> Backing<'b, M, S> - where - 'b: 'a, - { - Backing { - registration: Registration { - context: self.intermediate.storage.context, - pd_controller: &self.pd_controller, - power_devices: &self.power_devices, - }, - ports: &self.intermediate.ports, - } - } -} - -/// Wrapper around registration and type-erased state -pub struct Backing<'a, M: RawMutex, S: event::Sender> { - pub(crate) registration: Registration<'a, M>, - pub(crate) ports: &'a [Port<'a, M, S>], -} diff --git a/type-c-service/src/wrapper/dp.rs b/type-c-service/src/wrapper/dp.rs deleted file mode 100644 index a5c6740f7..000000000 --- a/type-c-service/src/wrapper/dp.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::ControllerWrapper; -use crate::wrapper::message::OutputDpStatusChanged; -use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_services::{event, sync::Lockable, trace}; -use embedded_usb_pd::{Error, LocalPortId}; -use type_c_interface::port::Controller; - -impl<'device, M: RawMutex, D: Lockable, S: event::Sender> - ControllerWrapper<'device, M, D, S> -where - D::Inner: Controller, -{ - /// Process a DisplayPort status update by retrieving the current DP status from the `controller` for the appropriate `port`. - pub(super) async fn process_dp_status_update( - &self, - controller: &mut D::Inner, - port: LocalPortId, - ) -> Result::BusError>> { - trace!("Processing DP status update event on port {}", port.0); - - let status = controller.get_dp_status(port).await?; - Ok(OutputDpStatusChanged { port, status }) - } -} diff --git a/type-c-service/src/wrapper/event_receiver.rs b/type-c-service/src/wrapper/event_receiver.rs deleted file mode 100644 index dff283dea..000000000 --- a/type-c-service/src/wrapper/event_receiver.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! This module contains event receiver types for the controller wrapper. -use core::array; -use core::future::pending; -use core::pin::pin; -use embassy_futures::select::{Either3, select_slice, select3}; -use embassy_time::{Instant, Timer}; -use embedded_services::{debug, trace}; -use embedded_usb_pd::LocalPortId; - -use crate::PortEventStreamer; -use crate::wrapper::message::{Event, LocalPortEvent, PowerPolicyCommand}; -use crate::wrapper::proxy::PowerProxyReceiver; -use type_c_interface::port::event::{PortEvent, PortEventBitfield, PortStatusEventBitfield}; - -/// Trait used for receiving interrupt from the controller. -pub trait InterruptReceiver { - /// Wait for the next interrupt event. - fn wait_interrupt(&mut self) -> impl Future; -} - -/// Struct to receive and stream port events from the controller. -pub struct PortEventReceiver> { - /// Receiver for the controller's interrupt events - receiver: Receiver, - /// Port event streaming state - streaming_state: Option>>, -} - -impl> PortEventReceiver { - /// Create a new instance - pub fn new(receiver: Receiver) -> Self { - Self { - receiver, - streaming_state: None, - } - } - - /// Wait for the next port event - pub async fn wait_next(&mut self) -> LocalPortEvent { - loop { - let streaming_state = if let Some(streaming_state) = &mut self.streaming_state { - // Yield to ensure we don't monopolize the executor - embassy_futures::yield_now().await; - streaming_state - } else { - let events = self.receiver.wait_interrupt().await; - self.streaming_state.insert(PortEventStreamer::new(events.into_iter())) - }; - - if let Some((port_index, event)) = streaming_state.next() { - return LocalPortEvent { - port: LocalPortId(port_index as u8), - event, - }; - } else { - self.streaming_state = None; - } - } - } -} - -/// Struct to receive power policy messages. -pub struct ArrayPowerProxyEventReceiver<'device, const N: usize> { - receivers: [PowerProxyReceiver<'device>; N], -} - -impl<'device, const N: usize> ArrayPowerProxyEventReceiver<'device, N> { - /// Create a new array power proxy event receiver - pub fn new(receivers: [PowerProxyReceiver<'device>; N]) -> Self { - Self { receivers } - } - - /// Wait for the next power policy command - pub async fn wait_next(&mut self) -> PowerPolicyCommand { - let mut futures = heapless::Vec::<_, N>::new(); - for receiver in self.receivers.iter_mut() { - // Size is fixed at compile time, so no chance of overflow - let _ = futures.push(async { receiver.receive().await }); - } - - // DROP SAFETY: Select over drop safe futures - let (request, local_id) = select_slice(pin!(futures.as_mut_slice())).await; - trace!("Power command: device{} {:#?}", local_id, request); - PowerPolicyCommand { - port: LocalPortId(local_id as u8), - request, - } - } - - /// Temporary function until the conversion to direct function calls is complete - pub async fn send_response( - &mut self, - port: LocalPortId, - response: power_policy_interface::psu::InternalResponseData, - ) -> Result<(), ()> { - self.receivers.get_mut(port.0 as usize).ok_or(())?.send(response).await; - Ok(()) - } -} - -/// Struct to receive sink ready timeout events. -pub struct SinkReadyTimeoutEvent { - timeouts: [Option; N], -} - -impl SinkReadyTimeoutEvent { - /// Create a new instance - pub fn new() -> Self { - Self { timeouts: [None; N] } - } - - /// Set a timeout for a specific port - pub fn set_timeout(&mut self, port: LocalPortId, new_timeout: Instant) { - let index = port.0 as usize; - if let Some(timeout) = self.timeouts.get_mut(index) { - *timeout = Some(new_timeout); - } - } - - /// Clear the timeout for a specific port - pub fn clear_timeout(&mut self, port: LocalPortId) { - let index = port.0 as usize; - if let Some(timeout) = self.timeouts.get_mut(index) { - *timeout = None; - } - } - - pub fn get_timeout(&self, port: LocalPortId) -> Option { - let index = port.0 as usize; - self.timeouts.get(index).copied().flatten() - } - - /// Wait for a sink ready timeout and return the port that has timed out. - /// - /// DROP SAFETY: No state to restore - pub async fn wait_next(&mut self) -> LocalPortId { - let mut futures = heapless::Vec::<_, N>::new(); - for (i, timeout) in self.timeouts.iter().enumerate() { - let timeout = *timeout; - // Size is fixed at compile time, so no chance of overflow - let _ = futures.push(async move { - if let Some(timeout) = timeout { - Timer::at(timeout).await; - debug!("Port{}: Sink ready timeout reached", i); - } else { - pending::<()>().await; - } - }); - } - - // DROP SAFETY: Select over drop safe futures - let (_, port_index) = select_slice(pin!(futures.as_mut_slice())).await; - if let Some(timeout) = self.timeouts.get_mut(port_index) { - *timeout = None; - } - LocalPortId(port_index as u8) - } -} - -impl Default for SinkReadyTimeoutEvent { - fn default() -> Self { - Self::new() - } -} - -/// Struct used for containing controller event receivers. -pub struct ArrayPortEventReceivers<'device, const N: usize, PortInterrupts: InterruptReceiver> { - /// Port event receiver - pub port_events: PortEventReceiver, - /// Power proxy event receiver - pub power_proxies: ArrayPowerProxyEventReceiver<'device, N>, - /// Sink ready timeout event receiver - pub sink_ready_timeout: SinkReadyTimeoutEvent, -} - -impl<'device, const N: usize, PortInterrupts: InterruptReceiver> - ArrayPortEventReceivers<'device, N, PortInterrupts> -{ - /// Create a new instance - pub fn new(port_interrupts: PortInterrupts, power_proxies: [PowerProxyReceiver<'device>; N]) -> Self { - Self { - port_events: PortEventReceiver::new(port_interrupts), - power_proxies: ArrayPowerProxyEventReceiver::new(power_proxies), - sink_ready_timeout: SinkReadyTimeoutEvent::new(), - } - } - - /// Wait for the next port event from any port. - /// - /// Returns the local port ID and the event bitfield. - pub async fn wait_event(&mut self) -> Event { - match select3( - self.port_events.wait_next(), - self.power_proxies.wait_next(), - self.sink_ready_timeout.wait_next(), - ) - .await - { - Either3::First(event) => Event::PortEvent(event), - Either3::Second(command) => Event::PowerPolicyCommand(command), - Either3::Third(port) => { - let mut status_event = PortStatusEventBitfield::none(); - status_event.set_sink_ready(true); - Event::PortEvent(LocalPortEvent { - port, - event: PortEvent::StatusChanged(status_event), - }) - } - } - } -} diff --git a/type-c-service/src/wrapper/message.rs b/type-c-service/src/wrapper/message.rs deleted file mode 100644 index f0c8152b7..000000000 --- a/type-c-service/src/wrapper/message.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! [`crate::wrapper::ControllerWrapper`] message types -use embedded_usb_pd::{LocalPortId, ado::Ado}; - -use type_c_interface::{ - port::event::PortStatusEventBitfield, - port::{DpStatus, PortStatus}, -}; - -/// Port event -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct LocalPortEvent { - /// Port ID - pub port: LocalPortId, - /// Port event - pub event: type_c_interface::port::event::PortEvent, -} - -/// Power policy command event data -pub struct PowerPolicyCommand { - /// Port ID - pub port: LocalPortId, - /// Power policy request - pub request: power_policy_interface::psu::CommandData, -} - -/// Wrapper events -pub enum Event { - /// Port status changed - PortEvent(LocalPortEvent), - /// Power policy command received - PowerPolicyCommand(PowerPolicyCommand), -} - -/// Port status changed output data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OutputPortStatusChanged { - /// Port ID - pub port: LocalPortId, - /// Status changed event - pub status_event: PortStatusEventBitfield, - /// Port status - pub status: PortStatus, -} - -/// PD alert output data -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OutputPdAlert { - /// Port ID - pub port: LocalPortId, - /// ADO data - pub ado: Ado, -} - -/// Power policy command output data -pub struct OutputPowerPolicyCommand { - /// Port ID - pub port: LocalPortId, - /// Response - pub response: power_policy_interface::psu::InternalResponseData, -} - -pub mod vdm { - //! Events and output for vendor-defined messaging. - use type_c_interface::port::event::VdmData; - - use super::LocalPortId; - - /// Output from processing a vendor-defined message. - #[derive(Copy, Clone, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub struct Output { - /// The port that the VDM message is associated with. - pub port: LocalPortId, - /// VDM data - pub vdm_data: VdmData, - } -} - -/// DP status changed output data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OutputDpStatusChanged { - /// Port ID - pub port: LocalPortId, - /// Port status - pub status: DpStatus, -} - -/// [`crate::wrapper::ControllerWrapper`] output -pub enum Output { - /// No-op when nothing specific is needed - Nop, - /// Port status changed - PortStatusChanged(OutputPortStatusChanged), - /// PD alert - PdAlert(OutputPdAlert), - /// Vendor-defined messaging. - Vdm(vdm::Output), - /// Power policy command received - PowerPolicyCommand(OutputPowerPolicyCommand), - /// Dp status update - DpStatusUpdate(OutputDpStatusChanged), -} diff --git a/type-c-service/src/wrapper/mod.rs b/type-c-service/src/wrapper/mod.rs deleted file mode 100644 index 9a757059d..000000000 --- a/type-c-service/src/wrapper/mod.rs +++ /dev/null @@ -1,402 +0,0 @@ -//! This module contains the [`ControllerWrapper`] struct. This struct serves as a bridge between various service messages -//! and the actual controller functions provided by [`type_c_interface::port::Controller`]. -//! # Supported service messaging -//! This struct currently supports messages from the following services: -//! * Type-C: [`type_c_interface::port::Command`] -//! # Event loop -//! This struct follows a standard process/finalize event loop. -//! -//! [`ControllerWrapper::process_event`] reads any additional data relevant to the event and returns [`message::Output`]. -//! e.g. port status for a port status changed event, VDM data for a VDM event -//! -//! [`ControllerWrapper::finalize`] consumes [`message::Output`] and responds to any deferred requests, performs -//! any caching/buffering of data, and notifies the type-C service implementation of the event if needed. -use core::ops::DerefMut; - -use crate::wrapper::backing::PortState; -use crate::wrapper::event_receiver::{ArrayPowerProxyEventReceiver, SinkReadyTimeoutEvent}; -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::signal::Signal; -use embassy_time::Instant; -use embedded_services::event; -use embedded_services::sync::Lockable; -use embedded_services::{error, info, trace}; -use embedded_usb_pd::ado::Ado; -use embedded_usb_pd::{Error, LocalPortId, PdError}; -use type_c_interface::port::event::PortEvent as InterfacePortEvent; -use type_c_interface::service::event::{PortEvent as ServicePortEvent, PortEventData as ServicePortEventData}; - -use crate::wrapper::message::*; - -pub mod backing; -pub mod config; -mod dp; -pub mod event_receiver; -pub mod message; -mod pd; -mod power; -pub mod proxy; -mod vdm; - -use type_c_interface::port::event::PortStatusEventBitfield; -use type_c_interface::port::{Controller, PortStatus}; - -/// Maximum number of supported ports -pub const MAX_SUPPORTED_PORTS: usize = 2; - -/// Common functionality implemented on top of [`type_c_interface::port::Controller`] -pub struct ControllerWrapper< - 'device, - M: RawMutex, - D: Lockable, - S: event::Sender, -> where - D::Inner: Controller, -{ - controller: &'device D, - /// Registration information for services - pub registration: backing::Registration<'device, M>, - /// SW port status event signal - sw_status_event: Signal, - /// General config - config: config::Config, - /// Port proxies - pub ports: &'device [backing::Port<'device, M, S>], -} - -impl<'device, M: RawMutex, D: Lockable, S: event::Sender> - ControllerWrapper<'device, M, D, S> -where - D::Inner: Controller, -{ - /// Create a new controller wrapper - pub fn new( - controller: &'device D, - config: config::Config, - storage: &'device backing::ReferencedStorage<'device, N, M, S>, - ) -> Self { - const { - assert!(N > 0 && N <= MAX_SUPPORTED_PORTS, "Invalid number of ports"); - }; - - let backing = storage.create_backing(); - Self { - controller, - config, - registration: backing.registration, - sw_status_event: Signal::new(), - ports: backing.ports, - } - } - - /// Get the cached port status, returns None if the port is invalid - pub async fn get_cached_port_status(&self, local_port: LocalPortId) -> Option { - let port = self.ports.get(local_port.0 as usize)?; - Some(port.state.lock().await.status) - } - - /// Synchronize the state between the controller and the internal state - pub async fn sync_state(&self) -> Result<(), Error<::BusError>> { - let mut controller = self.controller.lock().await; - self.sync_state_internal(&mut controller).await - } - - /// Synchronize the state between the controller and the internal state - async fn sync_state_internal( - &self, - controller: &mut D::Inner, - ) -> Result<(), Error<::BusError>> { - // Sync the controller state with the PD controller - for (i, port) in self.ports.iter().enumerate() { - let mut port_state = port.state.lock().await; - - let mut status_changed = port_state.sw_status_event; - let local_port = LocalPortId(i as u8); - let status = controller.get_port_status(local_port).await?; - trace!("Port{} status: {:#?}", i, status); - - let previous_status = port_state.status; - - if previous_status.is_connected() != status.is_connected() { - status_changed.set_plug_inserted_or_removed(true); - } - - if previous_status.available_sink_contract != status.available_sink_contract { - status_changed.set_new_power_contract_as_consumer(true); - } - - if previous_status.available_source_contract != status.available_source_contract { - status_changed.set_new_power_contract_as_provider(true); - } - - port_state.sw_status_event = status_changed; - if port_state.sw_status_event != PortStatusEventBitfield::none() { - // Have a status changed event, notify - trace!("Port{} status changed: {:#?}", i, status); - self.sw_status_event.signal(()); - } - } - Ok(()) - } - - /// Handle a plug event - async fn process_plug_event( - &self, - port_state: &mut PortState, - status: &PortStatus, - ) -> Result<(), Error<::BusError>> { - info!("Plug event"); - if status.is_connected() { - info!("Plug inserted"); - port_state - .power_policy_sender - .send(power_policy_interface::psu::event::EventData::Attached) - .await; - } else { - info!("Plug removed"); - port_state - .power_policy_sender - .send(power_policy_interface::psu::event::EventData::Detached) - .await; - } - - Ok(()) - } - - /// Process port status changed events - async fn process_port_status_changed( - &self, - sink_ready_timeout: &mut SinkReadyTimeoutEvent, - controller: &mut D::Inner, - local_port_id: LocalPortId, - status_event: PortStatusEventBitfield, - ) -> Result::BusError>> { - let global_port_id = self - .registration - .pd_controller - .lookup_global_port(local_port_id) - .map_err(Error::Pd)?; - - let mut port_state = self - .ports - .get(local_port_id.0 as usize) - .ok_or(Error::Pd(PdError::InvalidPort))? - .state - .lock() - .await; - - let status = controller.get_port_status(local_port_id).await?; - trace!("Port{} status: {:#?}", global_port_id.0, status); - trace!("Port{} status events: {:#?}", global_port_id.0, status_event); - - if status_event.plug_inserted_or_removed() { - self.process_plug_event(&mut port_state, &status).await?; - } - - // Only notify power policy of a contract after Sink Ready event (always after explicit or implicit contract) - if status_event.sink_ready() { - self.process_new_consumer_contract(&mut port_state, &status).await?; - } - - if status_event.new_power_contract_as_provider() { - self.process_new_provider_contract(&mut port_state, &status).await?; - } - - self.check_sink_ready_timeout( - sink_ready_timeout, - &port_state.status, - &status, - local_port_id, - status_event.new_power_contract_as_consumer(), - status_event.sink_ready(), - )?; - - Ok(Output::PortStatusChanged(OutputPortStatusChanged { - port: local_port_id, - status_event, - status, - })) - } - - /// Finalize a port status change output - async fn finalize_port_status_change( - &self, - local_port: LocalPortId, - status_event: PortStatusEventBitfield, - status: PortStatus, - ) -> Result<(), Error<::BusError>> { - let global_port_id = self - .registration - .pd_controller - .lookup_global_port(local_port) - .map_err(Error::Pd)?; - - self.ports - .get(local_port.0 as usize) - .ok_or(Error::Pd(PdError::InvalidPort))? - .state - .lock() - .await - .status = status; - - self.registration - .context - .send_port_event(ServicePortEvent { - port: global_port_id, - event: ServicePortEventData::StatusChanged(status_event, status), - }) - .await - .map_err(Error::Pd) - } - - /// Finalize a PD alert output - async fn finalize_pd_alert( - &self, - local_port: LocalPortId, - alert: Ado, - ) -> Result<(), Error<::BusError>> { - let global_port_id = self - .registration - .pd_controller - .lookup_global_port(local_port) - .map_err(Error::Pd)?; - - self.registration - .context - .send_port_event(ServicePortEvent { - port: global_port_id, - event: ServicePortEventData::Alert(alert), - }) - .await - .map_err(Error::Pd) - } - - /// Process a port notification - async fn process_port_event( - &self, - sink_ready_timeout: &mut SinkReadyTimeoutEvent, - controller: &mut D::Inner, - event: LocalPortEvent, - ) -> Result::BusError>> { - match event.event { - InterfacePortEvent::StatusChanged(status_event) => { - self.process_port_status_changed(sink_ready_timeout, controller, event.port, status_event) - .await - } - InterfacePortEvent::Alert => { - let ado = controller.get_pd_alert(event.port).await?; - trace!("Port{}: PD alert: {:#?}", event.port.0, ado); - if let Some(ado) = ado { - Ok(Output::PdAlert(OutputPdAlert { port: event.port, ado })) - } else { - // For some reason we didn't read an alert, nothing to do - Ok(Output::Nop) - } - } - InterfacePortEvent::Vdm(vdm_event) => self - .process_vdm_event(controller, event.port, vdm_event) - .await - .map(Output::Vdm), - InterfacePortEvent::DpStatusUpdate => self - .process_dp_status_update(controller, event.port) - .await - .map(Output::DpStatusUpdate), - rest => { - // Nothing currently implemented for these - trace!("Port{}: Notification: {:#?}", event.port.0, rest); - Ok(Output::Nop) - } - } - } - - /// Top-level processing function - /// Only call this fn from one place in a loop. Otherwise a deadlock could occur. - pub async fn process_event( - &self, - sink_ready_timeout: &mut SinkReadyTimeoutEvent, - event: Event, - ) -> Result::BusError>> { - let mut controller = self.controller.lock().await; - match event { - Event::PortEvent(port_event) => { - self.process_port_event(sink_ready_timeout, &mut controller, port_event) - .await - } - Event::PowerPolicyCommand(PowerPolicyCommand { port, request }) => { - let response = self.process_power_command(&mut controller, port, &request).await; - Ok(Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response })) - } - } - } - - /// Event loop finalize - pub async fn finalize( - &self, - event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, - output: Output, - ) -> Result<(), Error<::BusError>> { - match output { - Output::Nop => Ok(()), - Output::PortStatusChanged(OutputPortStatusChanged { - port, - status_event, - status, - }) => self.finalize_port_status_change(port, status_event, status).await, - Output::PdAlert(OutputPdAlert { port, ado }) => self.finalize_pd_alert(port, ado).await, - Output::Vdm(vdm) => self.finalize_vdm(vdm).await.map_err(Error::Pd), - Output::PowerPolicyCommand(OutputPowerPolicyCommand { port, response }) => { - event_receiver - .send_response(port, response) - .await - .map_err(|_| Error::Pd(PdError::Failed))?; - Ok(()) - } - Output::DpStatusUpdate(_) => { - // Nothing to do here - Ok(()) - } - } - } - - /// Combined processing and finialization function - pub async fn process_and_finalize_event( - &self, - sink_ready_timeout: &mut SinkReadyTimeoutEvent, - power_event_receiver: &mut ArrayPowerProxyEventReceiver<'device, N>, - event: Event, - ) -> Result<(), Error<::BusError>> { - let output = self.process_event(sink_ready_timeout, event).await?; - self.finalize(power_event_receiver, output).await - } - - /// Register all devices with their respective services - pub fn register(&'static self) -> Result<(), Error<::BusError>> { - self.registration - .context - .register_controller(self.registration.pd_controller) - .map_err(|_| { - error!( - "Controller{}: Failed to register PD controller", - self.registration.pd_controller.id().0 - ); - Error::Pd(PdError::Failed) - })?; - Ok(()) - } -} - -impl<'device, M: RawMutex, C: Lockable, S: event::Sender> Lockable - for ControllerWrapper<'device, M, C, S> -where - ::Inner: Controller, -{ - type Inner = C::Inner; - - fn try_lock(&self) -> Option> { - self.controller.try_lock() - } - - fn lock(&self) -> impl Future> { - self.controller.lock() - } -} diff --git a/type-c-service/src/wrapper/pd.rs b/type-c-service/src/wrapper/pd.rs deleted file mode 100644 index 641608cd2..000000000 --- a/type-c-service/src/wrapper/pd.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::wrapper::event_receiver::SinkReadyTimeoutEvent; -use embassy_time::Duration; -use embedded_services::debug; -use embedded_usb_pd::constants::{T_PS_TRANSITION_EPR_MS, T_PS_TRANSITION_SPR_MS}; - -use super::*; - -impl<'device, M: RawMutex, D: Lockable, S: event::Sender> - ControllerWrapper<'device, M, D, S> -where - D::Inner: Controller, -{ - /// Check the sink ready timeout - /// - /// After accepting a sink contract (new contract as consumer), the PD spec guarantees that the - /// source will be available to provide power after `tPSTransition`. This allows us to handle transitions - /// even for controllers that might not always broadcast sink ready events. - pub(super) fn check_sink_ready_timeout( - &self, - sink_ready_timeout: &mut SinkReadyTimeoutEvent, - previous_status: &PortStatus, - new_status: &PortStatus, - port: LocalPortId, - new_contract: bool, - sink_ready: bool, - ) -> Result<(), PdError> { - let contract_changed = previous_status.available_sink_contract != new_status.available_sink_contract; - let timeout = sink_ready_timeout.get_timeout(port); - - // Don't start the timeout if the sink has signaled it's ready or if the contract didn't change. - // The latter ensures that soft resets won't continually reset the ready timeout - debug!( - "Port{}: Check sink ready: new_contract={:?}, sink_ready={:?}, contract_changed={:?}, deadline={:?}", - port.0, new_contract, sink_ready, contract_changed, timeout, - ); - if new_contract && !sink_ready && contract_changed { - // Start the timeout - // Double the spec maximum transition time to provide a safety margin for hardware/controller delays or out-of-spec controllers. - let timeout_ms = if new_status.epr { - T_PS_TRANSITION_EPR_MS - } else { - T_PS_TRANSITION_SPR_MS - } - .maximum - .0 * 2; - - debug!("Port{}: Sink ready timeout started for {}ms", port.0, timeout_ms); - sink_ready_timeout.set_timeout(port, Instant::now() + Duration::from_millis(timeout_ms as u64)); - } else if timeout.is_some() - && (!new_status.is_connected() || new_status.available_sink_contract.is_none() || sink_ready) - { - debug!("Port{}: Sink ready timeout cleared", port.0); - sink_ready_timeout.clear_timeout(port); - } - Ok(()) - } -} diff --git a/type-c-service/src/wrapper/power.rs b/type-c-service/src/wrapper/power.rs deleted file mode 100644 index b52b7942c..000000000 --- a/type-c-service/src/wrapper/power.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! Module contain power-policy related message handling -use crate::util::power_policy_error_from_pd_bus_error; -use crate::wrapper::config::UnconstrainedSink; -use power_policy_interface::capability::{ConsumerPowerCapability, ProviderPowerCapability, PsuType}; -use power_policy_interface::psu::CommandData as PowerCommand; -use power_policy_interface::psu::{CommandData, InternalResponseData, ResponseData}; - -use super::*; - -impl<'device, M: RawMutex, D: Lockable, S: event::Sender> - ControllerWrapper<'device, M, D, S> -where - D::Inner: Controller, -{ - /// Handle a new contract as consumer - pub(super) async fn process_new_consumer_contract( - &self, - port_state: &mut PortState, - status: &PortStatus, - ) -> Result<(), Error<::BusError>> { - info!("Process new consumer contract"); - let available_sink_contract = status.available_sink_contract.map(|c| { - let mut c: ConsumerPowerCapability = c.into(); - let unconstrained = match self.config.unconstrained_sink { - UnconstrainedSink::Auto => status.unconstrained_power, - UnconstrainedSink::PowerThresholdMilliwatts(threshold) => c.capability.max_power_mw() >= threshold, - UnconstrainedSink::Never => false, - }; - c.flags.set_unconstrained_power(unconstrained); - c.flags.set_psu_type(PsuType::TypeC); - c - }); - - port_state - .power_policy_sender - .send(power_policy_interface::psu::event::EventData::UpdatedConsumerCapability(available_sink_contract)) - .await; - Ok(()) - } - - /// Handle a new contract as provider - pub(super) async fn process_new_provider_contract( - &self, - port_state: &mut PortState, - status: &PortStatus, - ) -> Result<(), Error<::BusError>> { - info!("Process New provider contract"); - port_state - .power_policy_sender - .send( - power_policy_interface::psu::event::EventData::RequestedProviderCapability( - status.available_source_contract.map(|caps| { - let mut caps = ProviderPowerCapability::from(caps); - caps.flags.set_psu_type(PsuType::TypeC); - caps - }), - ), - ) - .await; - Ok(()) - } - - /// Handle a disconnect command - async fn process_disconnect( - &self, - port: LocalPortId, - controller: &mut D::Inner, - ) -> Result<(), Error<::BusError>> { - if controller.enable_sink_path(port, false).await.is_err() { - error!("Error disabling sink path"); - return PdError::Failed.into(); - } - Ok(()) - } - - /// Handle a connect as provider command - fn process_connect_as_provider( - &self, - port: LocalPortId, - capability: ProviderPowerCapability, - _controller: &mut D::Inner, - ) -> Result<(), Error<::BusError>> { - info!("Port{}: Connect as provider: {:#?}", port.0, capability); - // TODO: double check explicit contract handling - Ok(()) - } - - /// Process a power command - /// Returns no error because this is a top-level function - pub(super) async fn process_power_command( - &self, - controller: &mut D::Inner, - port: LocalPortId, - command: &CommandData, - ) -> InternalResponseData { - trace!("Processing power command: device{} {:#?}", port.0, command); - - match command { - PowerCommand::ConnectAsConsumer(capability) => { - info!( - "Port{}: Connect as consumer: {:?}, enable input switch", - port.0, capability - ); - controller.enable_sink_path(port, true).await.map_err(|e| { - error!("Error enabling sink path"); - power_policy_error_from_pd_bus_error(e) - })?; - } - PowerCommand::ConnectAsProvider(capability) => { - self.process_connect_as_provider(port, *capability, controller) - .map_err(|e| { - error!("Error processing connect provider"); - power_policy_error_from_pd_bus_error(e) - })?; - } - PowerCommand::Disconnect => { - self.process_disconnect(port, controller).await.map_err(|e| { - error!("Error processing disconnect"); - power_policy_error_from_pd_bus_error(e) - })?; - } - } - - Ok(ResponseData::Complete) - } -} diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs deleted file mode 100644 index 170fd55d3..000000000 --- a/type-c-service/src/wrapper/proxy.rs +++ /dev/null @@ -1,128 +0,0 @@ -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; -use embedded_services::named::Named; -use power_policy_interface::psu::{CommandData as PolicyCommandData, InternalResponseData as PolicyResponseData, Psu}; - -pub struct PowerProxyChannel { - command_channel: Channel, - response_channel: Channel, -} - -impl PowerProxyChannel { - pub fn new() -> Self { - Self { - command_channel: Channel::new(), - response_channel: Channel::new(), - } - } - - pub fn get_device_components( - &self, - ) -> ( - DynamicSender<'_, PolicyCommandData>, - DynamicReceiver<'_, PolicyResponseData>, - ) { - (self.command_channel.dyn_sender(), self.response_channel.dyn_receiver()) - } - - pub fn get_receiver(&self) -> PowerProxyReceiver<'_> { - PowerProxyReceiver { - receiver: self.command_channel.dyn_receiver(), - sender: self.response_channel.dyn_sender(), - } - } -} - -pub struct PowerProxyReceiver<'a> { - sender: DynamicSender<'a, PolicyResponseData>, - receiver: DynamicReceiver<'a, PolicyCommandData>, -} - -impl<'a> PowerProxyReceiver<'a> { - pub fn new( - receiver: DynamicReceiver<'a, PolicyCommandData>, - sender: DynamicSender<'a, PolicyResponseData>, - ) -> Self { - Self { receiver, sender } - } - - pub async fn receive(&mut self) -> PolicyCommandData { - self.receiver.receive().await - } - - pub async fn send(&mut self, response: PolicyResponseData) { - self.sender.send(response).await; - } -} - -pub struct PowerProxyDevice<'a> { - sender: DynamicSender<'a, PolicyCommandData>, - receiver: DynamicReceiver<'a, PolicyResponseData>, - /// Per-port PSU state - pub(crate) psu_state: power_policy_interface::psu::State, - name: &'static str, -} - -impl<'a> PowerProxyDevice<'a> { - pub fn new( - name: &'static str, - sender: DynamicSender<'a, PolicyCommandData>, - receiver: DynamicReceiver<'a, PolicyResponseData>, - ) -> Self { - Self { - name, - sender, - receiver, - psu_state: power_policy_interface::psu::State::default(), - } - } - - async fn execute(&mut self, command: PolicyCommandData) -> PolicyResponseData { - self.sender.send(command).await; - self.receiver.receive().await - } -} - -impl<'a> Psu for PowerProxyDevice<'a> { - async fn disconnect(&mut self) -> Result<(), power_policy_interface::psu::Error> { - self.execute(PolicyCommandData::Disconnect).await?.complete_or_err() - } - - async fn connect_provider( - &mut self, - capability: power_policy_interface::capability::ProviderPowerCapability, - ) -> Result<(), power_policy_interface::psu::Error> { - self.execute(PolicyCommandData::ConnectAsProvider(capability)) - .await? - .complete_or_err() - } - - async fn connect_consumer( - &mut self, - capability: power_policy_interface::capability::ConsumerPowerCapability, - ) -> Result<(), power_policy_interface::psu::Error> { - self.execute(PolicyCommandData::ConnectAsConsumer(capability)) - .await? - .complete_or_err() - } - - fn state(&self) -> &power_policy_interface::psu::State { - &self.psu_state - } - - fn state_mut(&mut self) -> &mut power_policy_interface::psu::State { - &mut self.psu_state - } -} - -impl<'a> Named for PowerProxyDevice<'a> { - fn name(&self) -> &'static str { - self.name - } -} - -impl Default for PowerProxyChannel { - fn default() -> Self { - Self::new() - } -} diff --git a/type-c-service/src/wrapper/vdm.rs b/type-c-service/src/wrapper/vdm.rs deleted file mode 100644 index 2f6baebfa..000000000 --- a/type-c-service/src/wrapper/vdm.rs +++ /dev/null @@ -1,47 +0,0 @@ -use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_services::{event, sync::Lockable, trace}; -use embedded_usb_pd::{Error, LocalPortId, PdError}; - -use type_c_interface::port::event::VdmNotification; -use type_c_interface::port::{Controller, event::VdmData}; -use type_c_interface::service::event::{PortEvent, PortEventData}; - -use super::{ControllerWrapper, message::vdm::Output}; - -impl<'device, M: RawMutex, D: Lockable, S: event::Sender> - ControllerWrapper<'device, M, D, S> -where - D::Inner: Controller, -{ - /// Process a VDM event by retrieving the relevant VDM data from the `controller` for the appropriate `port`. - pub(super) async fn process_vdm_event( - &self, - controller: &mut D::Inner, - port: LocalPortId, - event: VdmNotification, - ) -> Result::BusError>> { - trace!("Processing VDM event: {:?} on port {}", event, port.0); - let kind = match event { - VdmNotification::Entered => VdmData::Entered(controller.get_other_vdm(port).await?), - VdmNotification::Exited => VdmData::Exited(controller.get_other_vdm(port).await?), - VdmNotification::OtherReceived => VdmData::ReceivedOther(controller.get_other_vdm(port).await?), - VdmNotification::AttentionReceived => VdmData::ReceivedAttn(controller.get_attn_vdm(port).await?), - }; - - Ok(Output { port, vdm_data: kind }) - } - - /// Finalize a VDM output by notifying the service. - pub(super) async fn finalize_vdm(&self, output: Output) -> Result<(), PdError> { - trace!("Finalizing VDM output: {:?}", output); - let Output { port, vdm_data } = output; - let global_port_id = self.registration.pd_controller.lookup_global_port(port)?; - self.registration - .context - .send_port_event(PortEvent { - port: global_port_id, - event: PortEventData::Vdm(vdm_data), - }) - .await - } -} From bd46037d63c0b39ccd73a23fffeb328dea8fea00 Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Tue, 12 May 2026 10:13:28 -0700 Subject: [PATCH 75/79] Update embedded-io-async to v0.7.0 (#842) Updates uart-service to use the latest `embedded-io-async` (v0.7). Originally used v0.6.1 to support the IMXRT HAL but now we are supporting more platforms which have all moved onto the v0.7 impl of this trait. Note: No other crate currently uses `embedded-io-async` from the workspace Cargo toml so this won't cause any breakage in other crates. Can still support the `dev-imxrt` platform by making a wrapper that impls v0.7, which is better than the reverse (wrapping all the v0.7 HALs in v0.6 impls). See: https://github.com/OpenDevicePartnership/odp-embedded-controller/issues/15 for some context Resolves #824 --------- Signed-off-by: Kurtis Dinelle --- Cargo.lock | 2 +- Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54eb00893..3c5e577dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2301,7 +2301,7 @@ version = "0.1.0" dependencies = [ "defmt 0.3.100", "embassy-sync", - "embedded-io-async 0.6.1", + "embedded-io-async 0.7.0", "embedded-services", "log", "mctp-rs", diff --git a/Cargo.toml b/Cargo.toml index 56845b038..7073ed90d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,8 +79,7 @@ debug-service-messages = { path = "./debug-service-messages" } embassy-executor = "0.10.0" cfu-service = { path = "./cfu-service" } embedded-hal-nb = "1.0" -embedded-io = "0.6.1" -embedded-io-async = "0.6.1" +embedded-io-async = "0.7.0" embedded-mcu-hal = "0.2.0" embassy-futures = "0.1.2" embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" } From a6db5b42e8452005c8d44fb5f1f8591900130d64 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Wed, 13 May 2026 10:31:35 -0700 Subject: [PATCH 76/79] Type-C: Trait refactor and clean-up (#821) Refactor traits and do some general clean-up. * Move `Controller` trait into its own module. * Remove bus error type from traits to make it easier to abstract over PD hardware. * Break-up `Controller` trait into multiple traits. * Introduce corresponding port-level traits. --- examples/rt685s-evk/src/bin/type_c.rs | 3 +- examples/rt685s-evk/src/bin/type_c_cfu.rs | 3 +- examples/std/src/bin/type_c/basic.rs | 19 +- examples/std/src/bin/type_c/service.rs | 3 +- examples/std/src/bin/type_c/ucsi.rs | 3 +- examples/std/src/bin/type_c/unconstrained.rs | 2 +- .../std/src/lib/type_c/mock_controller.rs | 174 +++---- type-c-interface/src/control/dp.rs | 32 ++ type-c-interface/src/control/mod.rs | 9 + type-c-interface/src/control/pd.rs | 84 ++++ type-c-interface/src/control/power.rs | 20 + type-c-interface/src/control/retimer.rs | 11 + type-c-interface/src/control/tbt.rs | 9 + type-c-interface/src/control/type_c.rs | 15 + type-c-interface/src/control/usb.rs | 23 + type-c-interface/src/control/vdm.rs | 93 ++++ .../src/controller/electrical_disconnect.rs | 17 + .../src/controller/max_sink_voltage.rs | 15 + type-c-interface/src/controller/mod.rs | 21 + type-c-interface/src/controller/pd.rs | 65 +++ type-c-interface/src/controller/power.rs | 14 + type-c-interface/src/controller/retimer.rs | 20 + type-c-interface/src/controller/type_c.rs | 13 + type-c-interface/src/lib.rs | 3 + .../src/port/electrical_disconnect.rs | 16 + type-c-interface/src/port/max_sink_voltage.rs | 11 + type-c-interface/src/port/mod.rs | 474 +----------------- type-c-interface/src/port/pd.rs | 56 +++ type-c-interface/src/port/power.rs | 13 + type-c-interface/src/port/retimer.rs | 17 + type-c-interface/src/port/type_c.rs | 12 + type-c-interface/src/service/context.rs | 39 +- type-c-interface/src/service/event.rs | 6 +- type-c-interface/src/ucsi.rs | 10 + type-c-service/src/bridge/event_receiver.rs | 4 +- type-c-service/src/bridge/mod.rs | 258 ++++------ .../src/controller/electrical_disconnect.rs | 27 + .../src/controller/max_sink_voltage.rs | 24 + type-c-service/src/controller/mod.rs | 43 +- type-c-service/src/controller/pd.rs | 110 +++- type-c-service/src/controller/power.rs | 46 +- type-c-service/src/controller/retimer.rs | 37 ++ type-c-service/src/controller/type_c.rs | 25 + type-c-service/src/controller/ucsi.rs | 23 + type-c-service/src/driver/tps6699x.rs | 437 +++++++++------- type-c-service/src/service/mod.rs | 3 +- type-c-service/src/service/vdm.rs | 2 +- 47 files changed, 1365 insertions(+), 999 deletions(-) create mode 100644 type-c-interface/src/control/dp.rs create mode 100644 type-c-interface/src/control/mod.rs create mode 100644 type-c-interface/src/control/pd.rs create mode 100644 type-c-interface/src/control/power.rs create mode 100644 type-c-interface/src/control/retimer.rs create mode 100644 type-c-interface/src/control/tbt.rs create mode 100644 type-c-interface/src/control/type_c.rs create mode 100644 type-c-interface/src/control/usb.rs create mode 100644 type-c-interface/src/control/vdm.rs create mode 100644 type-c-interface/src/controller/electrical_disconnect.rs create mode 100644 type-c-interface/src/controller/max_sink_voltage.rs create mode 100644 type-c-interface/src/controller/mod.rs create mode 100644 type-c-interface/src/controller/pd.rs create mode 100644 type-c-interface/src/controller/power.rs create mode 100644 type-c-interface/src/controller/retimer.rs create mode 100644 type-c-interface/src/controller/type_c.rs create mode 100644 type-c-interface/src/port/electrical_disconnect.rs create mode 100644 type-c-interface/src/port/max_sink_voltage.rs create mode 100644 type-c-interface/src/port/pd.rs create mode 100644 type-c-interface/src/port/power.rs create mode 100644 type-c-interface/src/port/retimer.rs create mode 100644 type-c-interface/src/port/type_c.rs create mode 100644 type-c-interface/src/ucsi.rs create mode 100644 type-c-service/src/controller/electrical_disconnect.rs create mode 100644 type-c-service/src/controller/max_sink_voltage.rs create mode 100644 type-c-service/src/controller/retimer.rs create mode 100644 type-c-service/src/controller/type_c.rs create mode 100644 type-c-service/src/controller/ucsi.rs diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index cba3c6c60..755124076 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -21,9 +21,10 @@ 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::port::{ControllerId, Device}; 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; diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index 1a9355bf4..622336de8 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -28,8 +28,9 @@ 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::{ControllerId, Device, PortRegistration}; +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; diff --git a/examples/std/src/bin/type_c/basic.rs b/examples/std/src/bin/type_c/basic.rs index 69f811316..516e56069 100644 --- a/examples/std/src/bin/type_c/basic.rs +++ b/examples/std/src/bin/type_c/basic.rs @@ -6,7 +6,8 @@ use embedded_services::GlobalRawMutex; use embedded_usb_pd::{GlobalPortId, PdError as Error}; use log::*; use static_cell::StaticCell; -use type_c_interface::port::{self, ControllerId, PortRegistration}; +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; @@ -16,7 +17,7 @@ const PORT1_ID: GlobalPortId = GlobalPortId(1); const CHANNEL_CAPACITY: usize = 4; mod test_controller { - use type_c_interface::port::{ControllerStatus, PortRegistration}; + use type_c_interface::port::PortRegistration; use super::*; @@ -40,21 +41,12 @@ mod test_controller { async fn process_controller_command( &self, command: port::InternalCommandData, - ) -> Result, Error> { + ) -> Result { match command { port::InternalCommandData::Reset => { info!("Reset controller"); Ok(port::InternalResponseData::Complete) } - port::InternalCommandData::Status => { - info!("Get controller status"); - Ok(port::InternalResponseData::Status(ControllerStatus { - mode: "Test", - valid_fw_bank: true, - fw_version0: 0xbadf00d, - fw_version1: 0xdeadbeef, - })) - } port::InternalCommandData::SyncState => { info!("Sync controller state"); Ok(port::InternalResponseData::Complete) @@ -122,9 +114,6 @@ async fn task(spawner: Spawner) { Timer::after_secs(1).await; controller_context.reset_controller(CONTROLLER0_ID).await.unwrap(); - - let status = controller_context.get_controller_status(CONTROLLER0_ID).await.unwrap(); - info!("Controller 0 status: {status:#?}"); } fn main() { diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index e05b2f18e..008c20e48 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -16,8 +16,9 @@ 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::{ControllerId, Device, PortRegistration}; +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; diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 04f126293..0262d5fd8 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -22,8 +22,9 @@ use power_policy_service::psu::PsuEventReceivers; use power_policy_service::service::registration::ArrayRegistration; 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::{ControllerId, Device, PortRegistration}; +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; diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 43b1b686c..8b1191463 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -17,7 +17,7 @@ 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::port::ControllerId; +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; diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 9ba50dad1..5acab89d4 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -2,18 +2,22 @@ use std::num::NonZeroU8; use embassy_sync::{channel, mutex::Mutex, signal::Signal}; use embedded_services::GlobalRawMutex; -use embedded_usb_pd::{Error, ado::Ado}; +use embedded_usb_pd::ado::Ado; use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; use log::{debug, info}; use power_policy_interface::capability::PowerCapability; -use type_c_interface::port::SystemPowerState; -use type_c_interface::port::{ - AttnVdm, ControllerStatus, DpConfig, DpPinConfig, DpStatus, OtherVdm, PdStateMachineConfig, PortStatus, - RetimerFwUpdateState, SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, event::PortEventBitfield, -}; +use type_c_interface::control::dp::{DpConfig, DpPinConfig, DpStatus}; +use type_c_interface::control::pd::{PdStateMachineConfig, PortStatus}; +use type_c_interface::control::power::SystemPowerState; +use type_c_interface::control::retimer::RetimerFwUpdateState; +use type_c_interface::control::tbt::TbtConfig; +use type_c_interface::control::type_c::TypeCStateMachineState; +use type_c_interface::control::usb::UsbControlConfig; +use type_c_interface::control::vdm::{AttnVdm, OtherVdm, SendVdm}; +use type_c_interface::port::event::PortEventBitfield; use type_c_service::controller::state::SharedState; use type_c_service::util::power_capability_from_current; @@ -129,58 +133,25 @@ impl type_c_service::controller::event_receiver::InterruptReceiv } } -impl type_c_interface::port::Controller for Controller<'_> { - type BusError = (); - - async fn get_port_status(&mut self, _port: LocalPortId) -> Result> { - debug!("Get port status: {:#?}", *self.state.status.lock().await); - Ok(*self.state.status.lock().await) - } - - async fn enable_sink_path(&mut self, _port: LocalPortId, enable: bool) -> Result<(), Error> { - debug!("Enable sink path: {enable}"); - Ok(()) - } - - async fn get_controller_status(&mut self) -> Result, Error> { - debug!("Get controller status"); - Ok(ControllerStatus { - mode: "Test", - valid_fw_bank: true, - fw_version0: 0xbadf00d, - fw_version1: 0xdeadbeef, - }) - } - - async fn reset_controller(&mut self) -> Result<(), Error> { +impl type_c_interface::controller::Controller for Controller<'_> { + async fn reset_controller(&mut self) -> Result<(), PdError> { debug!("Reset controller"); Ok(()) } +} - async fn get_rt_fw_update_status( - &mut self, - _port: LocalPortId, - ) -> Result> { - debug!("Get retimer fw update status"); - Ok(RetimerFwUpdateState::Inactive) - } - - async fn set_rt_fw_update_state(&mut self, _port: LocalPortId) -> Result<(), Error> { - debug!("Set retimer fw update state"); - Ok(()) - } - - async fn clear_rt_fw_update_state(&mut self, _port: LocalPortId) -> Result<(), Error> { - debug!("Clear retimer fw update state"); - Ok(()) +impl type_c_interface::controller::pd::Pd for Controller<'_> { + async fn get_port_status(&mut self, _port: LocalPortId) -> Result { + debug!("Get port status: {:#?}", *self.state.status.lock().await); + Ok(*self.state.status.lock().await) } - async fn set_rt_compliance(&mut self, _port: LocalPortId) -> Result<(), Error> { - debug!("Set retimer compliance"); + async fn enable_sink_path(&mut self, _port: LocalPortId, enable: bool) -> Result<(), PdError> { + debug!("Enable sink path: {enable}"); Ok(()) } - async fn get_pd_alert(&mut self, port: LocalPortId) -> Result, Error> { + async fn get_pd_alert(&mut self, port: LocalPortId) -> Result, PdError> { let pd_alert = self.state.pd_alert.lock().await; if let Some(ado) = *pd_alert { debug!("Port{}: Get PD alert: {ado:#?}", port.0); @@ -191,54 +162,32 @@ impl type_c_interface::port::Controller for Controller<'_> { } } - async fn set_unconstrained_power( - &mut self, - _port: LocalPortId, - unconstrained: bool, - ) -> Result<(), Error> { + async fn set_unconstrained_power(&mut self, _port: LocalPortId, unconstrained: bool) -> Result<(), PdError> { debug!("Set unconstrained power: {unconstrained}"); Ok(()) } - async fn set_max_sink_voltage( - &mut self, - port: LocalPortId, - voltage_mv: Option, - ) -> Result<(), Error> { - debug!("Set max sink voltage for port {}: {:?}", port.0, voltage_mv); - Ok(()) - } - - async fn reconfigure_retimer(&mut self, port: LocalPortId) -> Result<(), Error> { - debug!("reconfigure_retimer(port: {port:?})"); - Ok(()) - } - - async fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> Result<(), Error> { + async fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> Result<(), PdError> { debug!("clear_dead_battery_flag(port: {port:?})"); Ok(()) } - async fn get_other_vdm(&mut self, port: LocalPortId) -> Result> { + async fn get_other_vdm(&mut self, port: LocalPortId) -> Result { debug!("Get other VDM for port {port:?}"); Ok(OtherVdm::default()) } - async fn get_attn_vdm(&mut self, port: LocalPortId) -> Result> { + async fn get_attn_vdm(&mut self, port: LocalPortId) -> Result { debug!("Get attention VDM for port {port:?}"); Ok(AttnVdm::default()) } - async fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> Result<(), Error> { + async fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { debug!("Send VDM for port {port:?}: {tx_vdm:?}"); Ok(()) } - async fn set_usb_control( - &mut self, - port: LocalPortId, - config: UsbControlConfig, - ) -> Result<(), Error> { + async fn set_usb_control(&mut self, port: LocalPortId, config: UsbControlConfig) -> Result<(), PdError> { debug!( "set_usb_control(port: {port:?}, usb2: {}, usb3: {}, usb4: {})", config.usb2_enabled, config.usb3_enabled, config.usb4_enabled @@ -246,7 +195,7 @@ impl type_c_interface::port::Controller for Controller<'_> { Ok(()) } - async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { + async fn get_dp_status(&mut self, port: LocalPortId) -> Result { debug!("Get DisplayPort status for port {port:?}"); Ok(DpStatus { alt_mode_entered: false, @@ -254,7 +203,7 @@ impl type_c_interface::port::Controller for Controller<'_> { }) } - async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), Error> { + async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), PdError> { debug!( "Set DisplayPort config for port {port:?}: enable={}, pin_cfg={:?}", config.enable, config.dfp_d_pin_cfg @@ -262,62 +211,103 @@ impl type_c_interface::port::Controller for Controller<'_> { Ok(()) } - async fn execute_drst(&mut self, port: LocalPortId) -> Result<(), Error> { + async fn execute_drst(&mut self, port: LocalPortId) -> Result<(), PdError> { debug!("Execute PD Data Reset for port {port:?}"); Ok(()) } - async fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> Result<(), Error> { + async fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> Result<(), PdError> { debug!("Set Thunderbolt config for port {port:?}: {config:?}"); Ok(()) } +} + +impl type_c_interface::controller::max_sink_voltage::MaxSinkVoltage for Controller<'_> { + async fn set_max_sink_voltage(&mut self, port: LocalPortId, voltage_mv: Option) -> Result<(), PdError> { + debug!("Set max sink voltage for port {}: {:?}", port.0, voltage_mv); + Ok(()) + } +} +impl type_c_interface::controller::pd::StateMachine for Controller<'_> { async fn set_pd_state_machine_config( &mut self, port: LocalPortId, config: PdStateMachineConfig, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { debug!("Set PD State Machine config for port {port:?}: {config:?}"); Ok(()) } +} +impl type_c_interface::controller::type_c::StateMachine for Controller<'_> { async fn set_type_c_state_machine_config( &mut self, port: LocalPortId, state: TypeCStateMachineState, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { debug!("Set Type-C State Machine state for port {port:?}: {state:?}"); Ok(()) } +} - async fn execute_ucsi_command( - &mut self, - command: lpm::LocalCommand, - ) -> Result, Error> { +impl type_c_interface::ucsi::Lpm for Controller<'_> { + async fn execute_lpm_command(&mut self, command: lpm::LocalCommand) -> Result, PdError> { debug!("Execute UCSI command for port {:?}: {command:?}", command.port()); match command.operation() { lpm::CommandData::GetConnectorStatus => Ok(Some(lpm::ResponseData::GetConnectorStatus( lpm::get_connector_status::ResponseData::default(), ))), - _ => Err(PdError::UnrecognizedCommand.into()), + _ => Err(PdError::UnrecognizedCommand), } } +} +impl type_c_interface::controller::electrical_disconnect::ElectricalDisconnect for Controller<'_> { async fn execute_electrical_disconnect( &mut self, port: LocalPortId, reconnect_time_s: Option, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { debug!("Execute electrical disconnect for port {port:?} with reconnect time {reconnect_time_s:?}"); Ok(()) } +} - async fn set_power_state( +impl type_c_interface::controller::power::SystemPowerStateStatus for Controller<'_> { + async fn set_system_power_state_status( &mut self, port: LocalPortId, state: SystemPowerState, - ) -> Result<(), Error> { - debug!("Set power state for port {port:?}: {state:?}"); + ) -> Result<(), PdError> { + debug!("Set system power state for port {port:?}: {state:?}"); + Ok(()) + } +} + +impl type_c_interface::controller::retimer::Retimer for Controller<'_> { + async fn get_rt_fw_update_status(&mut self, _port: LocalPortId) -> Result { + debug!("Get retimer fw update status"); + Ok(RetimerFwUpdateState::Inactive) + } + + async fn set_rt_fw_update_state(&mut self, _port: LocalPortId) -> Result<(), PdError> { + debug!("Set retimer fw update state"); + Ok(()) + } + + async fn clear_rt_fw_update_state(&mut self, _port: LocalPortId) -> Result<(), PdError> { + debug!("Clear retimer fw update state"); + Ok(()) + } + + async fn set_rt_compliance(&mut self, _port: LocalPortId) -> Result<(), PdError> { + debug!("Set retimer compliance"); + Ok(()) + } + + async fn reconfigure_retimer(&mut self, port: LocalPortId) -> Result<(), PdError> { + debug!("reconfigure_retimer(port: {port:?})"); Ok(()) } } diff --git a/type-c-interface/src/control/dp.rs b/type-c-interface/src/control/dp.rs new file mode 100644 index 000000000..21c616521 --- /dev/null +++ b/type-c-interface/src/control/dp.rs @@ -0,0 +1,32 @@ +//! DP-related control types +/// DisplayPort pin configuration +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpPinConfig { + /// 4L DP connection using USBC-USBC cable (Pin Assignment C) + pub pin_c: bool, + /// 2L USB + 2L DP connection using USBC-USBC cable (Pin Assignment D) + pub pin_d: bool, + /// 4L DP connection using USBC-DP cable (Pin Assignment E) + pub pin_e: bool, +} + +/// DisplayPort status data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpStatus { + /// DP alt-mode entered + pub alt_mode_entered: bool, + /// Get DP DFP pin config + pub dfp_d_pin_cfg: DpPinConfig, +} + +/// DisplayPort configuration data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpConfig { + /// DP alt-mode enabled + pub enable: bool, + /// Set DP DFP pin config + pub dfp_d_pin_cfg: DpPinConfig, +} diff --git a/type-c-interface/src/control/mod.rs b/type-c-interface/src/control/mod.rs new file mode 100644 index 000000000..05607decc --- /dev/null +++ b/type-c-interface/src/control/mod.rs @@ -0,0 +1,9 @@ +//! Shared types for controlling a PD port +pub mod dp; +pub mod pd; +pub mod power; +pub mod retimer; +pub mod tbt; +pub mod type_c; +pub mod usb; +pub mod vdm; diff --git a/type-c-interface/src/control/pd.rs b/type-c-interface/src/control/pd.rs new file mode 100644 index 000000000..a4b9c8a2a --- /dev/null +++ b/type-c-interface/src/control/pd.rs @@ -0,0 +1,84 @@ +//! Control types for core PD functionality + +use embedded_usb_pd::{ + DataRole, PlugOrientation, PowerRole, + pdinfo::{AltMode, PowerPathStatus}, + type_c::ConnectionState, +}; + +/// Port status +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PortStatus { + /// Current available source contract + pub available_source_contract: Option, + /// Current available sink contract + pub available_sink_contract: Option, + /// Current connection state + pub connection_state: Option, + /// Port partner supports dual-power roles + pub dual_power: bool, + /// plug orientation + pub plug_orientation: PlugOrientation, + /// power role + pub power_role: PowerRole, + /// data role + pub data_role: DataRole, + /// Active alt-modes + pub alt_mode: AltMode, + /// Power path status + pub power_path: PowerPathStatus, + /// EPR mode active + pub epr: bool, + /// Port partner is unconstrained + pub unconstrained_power: bool, +} + +impl PortStatus { + /// Create a new blank port status + /// Needed because default() is not const + pub const fn new() -> Self { + Self { + available_source_contract: None, + available_sink_contract: None, + connection_state: None, + dual_power: false, + plug_orientation: PlugOrientation::CC1, + power_role: PowerRole::Sink, + data_role: DataRole::Dfp, + alt_mode: AltMode::none(), + power_path: PowerPathStatus::none(), + epr: false, + unconstrained_power: false, + } + } + + /// Check if the port is connected + pub fn is_connected(&self) -> bool { + matches!( + self.connection_state, + Some(ConnectionState::Attached) + | Some(ConnectionState::DebugAccessory) + | Some(ConnectionState::AudioAccessory) + ) + } + + /// Check if a debug accessory is connected + pub fn is_debug_accessory(&self) -> bool { + matches!(self.connection_state, Some(ConnectionState::DebugAccessory)) + } +} + +impl Default for PortStatus { + fn default() -> Self { + Self::new() + } +} + +/// PD state-machine configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Default, Copy, PartialEq)] +pub struct PdStateMachineConfig { + /// Enable or disable the PD state-machine + pub enabled: bool, +} diff --git a/type-c-interface/src/control/power.rs b/type-c-interface/src/control/power.rs new file mode 100644 index 000000000..e4388e95c --- /dev/null +++ b/type-c-interface/src/control/power.rs @@ -0,0 +1,20 @@ +//! General power related control types + +/// System power state +/// +/// Used to notify the PD controller of the current system power state, +/// which triggers Application Configuration updates (e.g., crossbar reconfiguration). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SystemPowerState { + /// S0 - System fully running + S0, + /// S3 - Suspend to RAM + S3, + /// S4 - Hibernate + S4, + /// S5 - Soft off + S5, + /// S0ix - Modern standby / Connected standby + S0ix, +} diff --git a/type-c-interface/src/control/retimer.rs b/type-c-interface/src/control/retimer.rs new file mode 100644 index 000000000..e3460638d --- /dev/null +++ b/type-c-interface/src/control/retimer.rs @@ -0,0 +1,11 @@ +//! Retimer related control types + +/// Retimer update state +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RetimerFwUpdateState { + /// Retimer FW Update Inactive + Inactive, + /// Retimer FW Update Active + Active, +} diff --git a/type-c-interface/src/control/tbt.rs b/type-c-interface/src/control/tbt.rs new file mode 100644 index 000000000..192ed6c23 --- /dev/null +++ b/type-c-interface/src/control/tbt.rs @@ -0,0 +1,9 @@ +//! Thunderbolt-related control types + +/// Thunderbolt control configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Default, Copy, PartialEq)] +pub struct TbtConfig { + /// Enable Thunderbolt + pub tbt_enabled: bool, +} diff --git a/type-c-interface/src/control/type_c.rs b/type-c-interface/src/control/type_c.rs new file mode 100644 index 000000000..0e13c7b86 --- /dev/null +++ b/type-c-interface/src/control/type_c.rs @@ -0,0 +1,15 @@ +//! Type-C related control types + +/// TypeC State Machine +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TypeCStateMachineState { + /// Sink state machine only + Sink, + /// Source state machine only + Source, + /// DRP state machine + Drp, + /// Disabled + Disabled, +} diff --git a/type-c-interface/src/control/usb.rs b/type-c-interface/src/control/usb.rs new file mode 100644 index 000000000..237eb947d --- /dev/null +++ b/type-c-interface/src/control/usb.rs @@ -0,0 +1,23 @@ +//! USB related control types + +/// USB control configuration +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct UsbControlConfig { + /// Enable USB2 data path + pub usb2_enabled: bool, + /// Enable USB3 data path + pub usb3_enabled: bool, + /// Enable USB4 data path + pub usb4_enabled: bool, +} + +impl Default for UsbControlConfig { + fn default() -> Self { + Self { + usb2_enabled: true, + usb3_enabled: true, + usb4_enabled: true, + } + } +} diff --git a/type-c-interface/src/control/vdm.rs b/type-c-interface/src/control/vdm.rs new file mode 100644 index 000000000..1bd2ebe3b --- /dev/null +++ b/type-c-interface/src/control/vdm.rs @@ -0,0 +1,93 @@ +//! VDM-related control types + +/// Length of the Other VDM data +pub const OTHER_VDM_LEN: usize = 29; +/// Length of the Attention VDM data +pub const ATTN_VDM_LEN: usize = 9; +/// maximum number of data objects in a VDM +pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each + +/// Other Vdm data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OtherVdm { + /// Other VDM data + pub data: [u8; OTHER_VDM_LEN], +} + +impl Default for OtherVdm { + fn default() -> Self { + Self { + data: [0; OTHER_VDM_LEN], + } + } +} + +impl From for [u8; OTHER_VDM_LEN] { + fn from(vdm: OtherVdm) -> Self { + vdm.data + } +} + +impl From<[u8; OTHER_VDM_LEN]> for OtherVdm { + fn from(data: [u8; OTHER_VDM_LEN]) -> Self { + Self { data } + } +} + +/// Attention Vdm data +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AttnVdm { + /// Attention VDM data + pub data: [u8; ATTN_VDM_LEN], +} + +impl Default for AttnVdm { + fn default() -> Self { + Self { + data: [0; ATTN_VDM_LEN], + } + } +} + +impl From for [u8; ATTN_VDM_LEN] { + fn from(vdm: AttnVdm) -> Self { + vdm.data + } +} + +impl From<[u8; ATTN_VDM_LEN]> for AttnVdm { + fn from(data: [u8; ATTN_VDM_LEN]) -> Self { + Self { data } + } +} + +/// Send VDM data +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SendVdm { + /// initiating a VDM sequence + pub initiator: bool, + /// VDO count + pub vdo_count: u8, + /// VDO data + pub vdo_data: [u32; MAX_NUM_DATA_OBJECTS], +} + +impl SendVdm { + /// Create a new blank VDM + pub const fn new() -> Self { + Self { + initiator: false, + vdo_count: 0, + vdo_data: [0; MAX_NUM_DATA_OBJECTS], + } + } +} + +impl Default for SendVdm { + fn default() -> Self { + Self::new() + } +} diff --git a/type-c-interface/src/controller/electrical_disconnect.rs b/type-c-interface/src/controller/electrical_disconnect.rs new file mode 100644 index 000000000..f4d54df74 --- /dev/null +++ b/type-c-interface/src/controller/electrical_disconnect.rs @@ -0,0 +1,17 @@ +use core::num::NonZeroU8; + +use embedded_usb_pd::{LocalPortId, PdError}; + +use crate::controller::pd::Pd; + +pub trait ElectricalDisconnect: Pd { + /// Execute an electrical disconnect on the given port, if supported by the controller. + /// + /// If `reconnect_time_s` is provided, the controller should automatically reconnect the port after the specified time + /// has elapsed. If `reconnect_time_s` is [`None`], the port should remain disconnected until manually reconnected. + fn execute_electrical_disconnect( + &mut self, + port: LocalPortId, + reconnect_time_s: Option, + ) -> impl Future>; +} diff --git a/type-c-interface/src/controller/max_sink_voltage.rs b/type-c-interface/src/controller/max_sink_voltage.rs new file mode 100644 index 000000000..706282331 --- /dev/null +++ b/type-c-interface/src/controller/max_sink_voltage.rs @@ -0,0 +1,15 @@ +use embedded_usb_pd::{LocalPortId, PdError}; + +use crate::controller::pd::Pd; + +/// Functionality related to setting the maximum sink voltage for a port. +pub trait MaxSinkVoltage: Pd { + /// Set the maximum sink voltage for the given port + /// + /// This may trigger a renegotiation + fn set_max_sink_voltage( + &mut self, + port: LocalPortId, + voltage_mv: Option, + ) -> impl Future>; +} diff --git a/type-c-interface/src/controller/mod.rs b/type-c-interface/src/controller/mod.rs new file mode 100644 index 000000000..d149d5da6 --- /dev/null +++ b/type-c-interface/src/controller/mod.rs @@ -0,0 +1,21 @@ +//! Module for PD controller related code + +use embedded_usb_pd::PdError; + +pub mod electrical_disconnect; +pub mod max_sink_voltage; +pub mod pd; +pub mod power; +pub mod retimer; +pub mod type_c; + +/// Controller ID +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControllerId(pub u8); + +/// PD controller trait +pub trait Controller { + /// 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 new file mode 100644 index 000000000..f909f4847 --- /dev/null +++ b/type-c-interface/src/controller/pd.rs @@ -0,0 +1,65 @@ +use embedded_usb_pd::{LocalPortId, PdError, ado::Ado}; + +use crate::control::{ + dp::{DpConfig, DpStatus}, + pd::{PdStateMachineConfig, PortStatus}, + tbt::TbtConfig, + usb::UsbControlConfig, + vdm::{AttnVdm, OtherVdm, SendVdm}, +}; + +/// Trait for basic functionality from the PD spec. +pub trait Pd { + /// Returns the port status + fn get_port_status(&mut self, port: LocalPortId) -> impl Future>; + + /// Clear the dead battery flag for the given port. + fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> impl Future>; + + /// Enable or disable sink path + fn enable_sink_path(&mut self, port: LocalPortId, enable: bool) -> impl Future>; + + /// Get current PD alert + fn get_pd_alert(&mut self, port: LocalPortId) -> impl Future, PdError>>; + + /// Set port unconstrained status + fn set_unconstrained_power( + &mut self, + port: LocalPortId, + unconstrained: bool, + ) -> impl Future>; + + /// Get the Rx Other VDM data for the given port + fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>; + /// Get the Rx Attention VDM data for the given port + fn get_attn_vdm(&mut self, port: LocalPortId) -> impl Future>; + /// Send a VDM to the given port + fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> impl Future>; + /// Execute PD Data Reset for the given port + fn execute_drst(&mut self, port: LocalPortId) -> impl Future>; + + /// Get DisplayPort status for the given port + fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>; + /// Set DisplayPort configuration for the given port + fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> impl Future>; + + /// Set Thunderbolt configuration for the given port + fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> impl Future>; + + /// Set USB control configuration for the given port + fn set_usb_control( + &mut self, + port: LocalPortId, + config: UsbControlConfig, + ) -> impl Future>; +} + +/// PD state machine related controller functionality +pub trait StateMachine: Pd { + /// Set PD state-machine configuration for the given port + fn set_pd_state_machine_config( + &mut self, + port: LocalPortId, + config: PdStateMachineConfig, + ) -> impl Future>; +} diff --git a/type-c-interface/src/controller/power.rs b/type-c-interface/src/controller/power.rs new file mode 100644 index 000000000..238ce82ca --- /dev/null +++ b/type-c-interface/src/controller/power.rs @@ -0,0 +1,14 @@ +use embedded_usb_pd::{LocalPortId, PdError}; + +/// System power state related controller functionality +pub trait SystemPowerStateStatus { + /// Set the system power state on the given port. + /// + /// This notifies the PD controller of the current system power state, + /// which triggers Application Configuration updates (e.g., crossbar reconfiguration). + fn set_system_power_state_status( + &mut self, + port: LocalPortId, + state: crate::control::power::SystemPowerState, + ) -> impl Future>; +} diff --git a/type-c-interface/src/controller/retimer.rs b/type-c-interface/src/controller/retimer.rs new file mode 100644 index 000000000..843a53b76 --- /dev/null +++ b/type-c-interface/src/controller/retimer.rs @@ -0,0 +1,20 @@ +use embedded_usb_pd::{LocalPortId, PdError}; + +use crate::control::retimer::RetimerFwUpdateState; + +/// Retimer-related functionality +pub trait Retimer { + /// Returns the retimer fw update state + fn get_rt_fw_update_status( + &mut self, + port: LocalPortId, + ) -> impl Future>; + /// Set retimer fw update state + fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>; + /// Clear retimer fw update state + fn clear_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>; + /// Set retimer compliance + fn set_rt_compliance(&mut self, port: LocalPortId) -> impl Future>; + /// Reconfigure the retimer for the given port. + fn reconfigure_retimer(&mut self, port: LocalPortId) -> impl Future>; +} diff --git a/type-c-interface/src/controller/type_c.rs b/type-c-interface/src/controller/type_c.rs new file mode 100644 index 000000000..fd7271c55 --- /dev/null +++ b/type-c-interface/src/controller/type_c.rs @@ -0,0 +1,13 @@ +use embedded_usb_pd::{LocalPortId, PdError}; + +use crate::{control::type_c::TypeCStateMachineState, controller::pd::Pd}; + +/// Type-C state machine related controller functionality +pub trait StateMachine: Pd { + /// Set Type-C state-machine configuration for the given port + fn set_type_c_state_machine_config( + &mut self, + port: LocalPortId, + state: TypeCStateMachineState, + ) -> impl Future>; +} diff --git a/type-c-interface/src/lib.rs b/type-c-interface/src/lib.rs index 8c5e96b62..aaa86cafa 100644 --- a/type-c-interface/src/lib.rs +++ b/type-c-interface/src/lib.rs @@ -1,4 +1,7 @@ //! Interface for type-C service. #![no_std] +pub mod control; +pub mod controller; pub mod port; pub mod service; +pub mod ucsi; diff --git a/type-c-interface/src/port/electrical_disconnect.rs b/type-c-interface/src/port/electrical_disconnect.rs new file mode 100644 index 000000000..d0b62d437 --- /dev/null +++ b/type-c-interface/src/port/electrical_disconnect.rs @@ -0,0 +1,16 @@ +use core::num::NonZeroU8; + +use embedded_usb_pd::PdError; + +use crate::port::pd::Pd; + +pub trait ElectricalDisconnect: Pd { + /// Execute an electrical disconnect on this port, if supported by the controller. + /// + /// If `reconnect_time_s` is provided, the controller should automatically reconnect the port after the specified time + /// has elapsed. If `reconnect_time_s` is [`None`], the port should remain disconnected until manually reconnected. + fn execute_electrical_disconnect( + &mut self, + reconnect_time_s: Option, + ) -> impl Future>; +} diff --git a/type-c-interface/src/port/max_sink_voltage.rs b/type-c-interface/src/port/max_sink_voltage.rs new file mode 100644 index 000000000..a37c8a116 --- /dev/null +++ b/type-c-interface/src/port/max_sink_voltage.rs @@ -0,0 +1,11 @@ +use embedded_usb_pd::PdError; + +use crate::port::pd::Pd; + +/// Functionality related to setting the maximum sink voltage for a port. +pub trait MaxSinkVoltage: Pd { + /// Set the maximum sink voltage for this port + /// + /// This may trigger a renegotiation + fn set_max_sink_voltage(&mut self, voltage_mv: Option) -> impl Future>; +} diff --git a/type-c-interface/src/port/mod.rs b/type-c-interface/src/port/mod.rs index cd4877660..ffc8e8bb8 100644 --- a/type-c-interface/src/port/mod.rs +++ b/type-c-interface/src/port/mod.rs @@ -1,273 +1,28 @@ //! PD controller related code -use core::future::Future; -use core::num::NonZeroU8; - use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use embedded_usb_pd::ucsi::lpm; -use embedded_usb_pd::{ - DataRole, Error, GlobalPortId, LocalPortId, PdError, PlugOrientation, PowerRole, - ado::Ado, - pdinfo::{AltMode, PowerPathStatus}, - type_c::ConnectionState, -}; +use embedded_usb_pd::{GlobalPortId, LocalPortId, PdError, ado::Ado}; use embedded_services::ipc::deferred; use embedded_services::{GlobalRawMutex, intrusive_list}; +pub mod electrical_disconnect; pub mod event; - +pub mod max_sink_voltage; +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; -/// Length of the Other VDM data -pub const OTHER_VDM_LEN: usize = 29; -/// Length of the Attention VDM data -pub const ATTN_VDM_LEN: usize = 9; -/// maximum number of data objects in a VDM -pub const MAX_NUM_DATA_OBJECTS: usize = 7; // 7 VDOs of 4 bytes each - -/// Controller ID -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerId(pub u8); - -/// Port status -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PortStatus { - /// Current available source contract - pub available_source_contract: Option, - /// Current available sink contract - pub available_sink_contract: Option, - /// Current connection state - pub connection_state: Option, - /// Port partner supports dual-power roles - pub dual_power: bool, - /// plug orientation - pub plug_orientation: PlugOrientation, - /// power role - pub power_role: PowerRole, - /// data role - pub data_role: DataRole, - /// Active alt-modes - pub alt_mode: AltMode, - /// Power path status - pub power_path: PowerPathStatus, - /// EPR mode active - pub epr: bool, - /// Port partner is unconstrained - pub unconstrained_power: bool, -} - -impl PortStatus { - /// Create a new blank port status - /// Needed because default() is not const - pub const fn new() -> Self { - Self { - available_source_contract: None, - available_sink_contract: None, - connection_state: None, - dual_power: false, - plug_orientation: PlugOrientation::CC1, - power_role: PowerRole::Sink, - data_role: DataRole::Dfp, - alt_mode: AltMode::none(), - power_path: PowerPathStatus::none(), - epr: false, - unconstrained_power: false, - } - } - - /// Check if the port is connected - pub fn is_connected(&self) -> bool { - matches!( - self.connection_state, - Some(ConnectionState::Attached) - | Some(ConnectionState::DebugAccessory) - | Some(ConnectionState::AudioAccessory) - ) - } - - /// Check if a debug accessory is connected - pub fn is_debug_accessory(&self) -> bool { - matches!(self.connection_state, Some(ConnectionState::DebugAccessory)) - } -} - -impl Default for PortStatus { - fn default() -> Self { - Self::new() - } -} - -/// Other Vdm data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OtherVdm { - /// Other VDM data - pub data: [u8; OTHER_VDM_LEN], -} - -impl Default for OtherVdm { - fn default() -> Self { - Self { - data: [0; OTHER_VDM_LEN], - } - } -} - -impl From for [u8; OTHER_VDM_LEN] { - fn from(vdm: OtherVdm) -> Self { - vdm.data - } -} - -impl From<[u8; OTHER_VDM_LEN]> for OtherVdm { - fn from(data: [u8; OTHER_VDM_LEN]) -> Self { - Self { data } - } -} - -/// Attention Vdm data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AttnVdm { - /// Attention VDM data - pub data: [u8; ATTN_VDM_LEN], -} - -/// DisplayPort pin configuration -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpPinConfig { - /// 4L DP connection using USBC-USBC cable (Pin Assignment C) - pub pin_c: bool, - /// 2L USB + 2L DP connection using USBC-USBC cable (Pin Assignment D) - pub pin_d: bool, - /// 4L DP connection using USBC-DP cable (Pin Assignment E) - pub pin_e: bool, -} - -/// DisplayPort status data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpStatus { - /// DP alt-mode entered - pub alt_mode_entered: bool, - /// Get DP DFP pin config - pub dfp_d_pin_cfg: DpPinConfig, -} - -/// DisplayPort configuration data -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DpConfig { - /// DP alt-mode enabled - pub enable: bool, - /// Set DP DFP pin config - pub dfp_d_pin_cfg: DpPinConfig, -} - -impl Default for AttnVdm { - fn default() -> Self { - Self { - data: [0; ATTN_VDM_LEN], - } - } -} - -impl From for [u8; ATTN_VDM_LEN] { - fn from(vdm: AttnVdm) -> Self { - vdm.data - } -} - -impl From<[u8; ATTN_VDM_LEN]> for AttnVdm { - fn from(data: [u8; ATTN_VDM_LEN]) -> Self { - Self { data } - } -} - -/// Send VDM data -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SendVdm { - /// initiating a VDM sequence - pub initiator: bool, - /// VDO count - pub vdo_count: u8, - /// VDO data - pub vdo_data: [u32; MAX_NUM_DATA_OBJECTS], -} - -impl SendVdm { - /// Create a new blank port status - pub const fn new() -> Self { - Self { - initiator: false, - vdo_count: 0, - vdo_data: [0; MAX_NUM_DATA_OBJECTS], - } - } -} - -impl Default for SendVdm { - fn default() -> Self { - Self::new() - } -} - -/// USB control configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct UsbControlConfig { - /// Enable USB2 data path - pub usb2_enabled: bool, - /// Enable USB3 data path - pub usb3_enabled: bool, - /// Enable USB4 data path - pub usb4_enabled: bool, -} - -impl Default for UsbControlConfig { - fn default() -> Self { - Self { - usb2_enabled: true, - usb3_enabled: true, - usb4_enabled: true, - } - } -} - -/// Thunderbolt control configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] -pub struct TbtConfig { - /// Enable Thunderbolt - pub tbt_enabled: bool, -} - -/// PD state-machine configuration -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Default, Copy, PartialEq)] -pub struct PdStateMachineConfig { - /// Enable or disable the PD state-machine - pub enabled: bool, -} - -/// TypeC State Machine -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum TypeCStateMachineState { - /// Sink state machine only - Sink, - /// Source state machine only - Source, - /// DRP state machine - Drp, - /// Disabled - Disabled, -} - /// Port-specific command data #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -322,16 +77,6 @@ pub struct PortCommand { pub data: PortCommandData, } -/// PD controller command-specific data -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RetimerFwUpdateState { - /// Retimer FW Update Inactive - Inactive, - /// Retimer FW Update Active - Active, -} - /// Port-specific response data #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -365,33 +110,12 @@ impl PortResponseData { /// Port-specific command response pub type PortResponse = Result; -/// System power state for Sx App Config register. -/// -/// Used to notify the PD controller of the current system power state, -/// which triggers Application Configuration updates (e.g., crossbar reconfiguration). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum SystemPowerState { - /// S0 - System fully running - S0, - /// S3 - Suspend to RAM - S3, - /// S4 - Hibernate - S4, - /// S5 - Soft off - S5, - /// S0ix - Modern standby / Connected standby - S0ix, -} - /// PD controller command-specific data #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InternalCommandData { /// Reset the PD controller Reset, - /// Get controller status - Status, /// Sync controller state SyncState, } @@ -409,40 +133,24 @@ pub enum Command { /// Controller-specific response data #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InternalResponseData<'a> { +pub enum InternalResponseData { /// Command complete Complete, - /// Controller status - Status(ControllerStatus<'a>), } /// Response for controller-specific commands -pub type InternalResponse<'a> = Result, PdError>; +pub type InternalResponse = Result; /// PD controller command response #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Response<'a> { +pub enum Response { /// Controller response - Controller(InternalResponse<'a>), + Controller(InternalResponse), /// Port response Port(PortResponse), } -/// Controller status -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControllerStatus<'a> { - /// Current controller mode - pub mode: &'a str, - /// True if we did not have to boot from a backup FW bank - pub valid_fw_bank: bool, - /// FW version 0 - pub fw_version0: u32, - /// FW version 1 - pub fw_version1: u32, -} - /// Per-port registration info pub struct PortRegistration { /// Global port ID of the port @@ -459,7 +167,7 @@ pub struct Device<'a> { id: ControllerId, pub ports: &'a [PortRegistration], num_ports: usize, - command: deferred::Channel>, + command: deferred::Channel, } impl intrusive_list::NodeContainer for Device<'static> { @@ -486,7 +194,7 @@ impl<'a> Device<'a> { } /// Send a command to this controller - pub async fn execute_command(&self, command: Command) -> Response<'_> { + pub async fn execute_command(&self, command: Command) -> Response { self.command.execute(command).await } @@ -512,7 +220,7 @@ impl<'a> Device<'a> { /// Create a command handler for this controller /// /// DROP SAFETY: Direct call to deferred channel primitive - pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, Command, Response<'static>> { + pub async fn receive(&self) -> deferred::Request<'_, GlobalRawMutex, Command, Response> { self.command.receive().await } @@ -521,141 +229,3 @@ impl<'a> Device<'a> { self.num_ports } } - -/// PD controller trait that device drivers may use to integrate with internal messaging system -pub trait Controller { - /// Type of error returned by the bus - type BusError; - - /// Returns the port status - fn get_port_status(&mut self, port: LocalPortId) - -> impl Future>>; - - /// Reset the controller - fn reset_controller(&mut self) -> impl Future>>; - - /// Returns the retimer fw update state - fn get_rt_fw_update_status( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Set retimer fw update state - fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> impl Future>>; - /// Clear retimer fw update state - fn clear_rt_fw_update_state( - &mut self, - port: LocalPortId, - ) -> impl Future>>; - /// Set retimer compliance - fn set_rt_compliance(&mut self, port: LocalPortId) -> impl Future>>; - - /// Reconfigure the retimer for the given port. - fn reconfigure_retimer(&mut self, port: LocalPortId) -> impl Future>>; - - /// Clear the dead battery flag for the given port. - fn clear_dead_battery_flag(&mut self, port: LocalPortId) - -> impl Future>>; - - /// Enable or disable sink path - fn enable_sink_path( - &mut self, - port: LocalPortId, - enable: bool, - ) -> impl Future>>; - /// Get current controller status - fn get_controller_status( - &mut self, - ) -> impl Future, Error>>; - /// Get current PD alert - fn get_pd_alert(&mut self, port: LocalPortId) -> impl Future, Error>>; - /// Set the maximum sink voltage for the given port - /// - /// This may trigger a renegotiation - fn set_max_sink_voltage( - &mut self, - port: LocalPortId, - voltage_mv: Option, - ) -> impl Future>>; - /// Set port unconstrained status - fn set_unconstrained_power( - &mut self, - port: LocalPortId, - unconstrained: bool, - ) -> impl Future>>; - - /// Get the Rx Other VDM data for the given port - fn get_other_vdm(&mut self, port: LocalPortId) -> impl Future>>; - /// Get the Rx Attention VDM data for the given port - fn get_attn_vdm(&mut self, port: LocalPortId) -> impl Future>>; - /// Send a VDM to the given port - fn send_vdm( - &mut self, - port: LocalPortId, - tx_vdm: SendVdm, - ) -> impl Future>>; - - /// Set USB control configuration for the given port - fn set_usb_control( - &mut self, - port: LocalPortId, - config: UsbControlConfig, - ) -> impl Future>>; - - /// Get DisplayPort status for the given port - fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>>; - /// Set DisplayPort configuration for the given port - fn set_dp_config( - &mut self, - port: LocalPortId, - config: DpConfig, - ) -> impl Future>>; - /// Execute PD Data Reset for the given port - fn execute_drst(&mut self, port: LocalPortId) -> impl Future>>; - - /// Set Thunderbolt configuration for the given port - fn set_tbt_config( - &mut self, - port: LocalPortId, - config: TbtConfig, - ) -> impl Future>>; - - /// Set PD state-machine configuration for the given port - fn set_pd_state_machine_config( - &mut self, - port: LocalPortId, - config: PdStateMachineConfig, - ) -> impl Future>>; - - /// Set Type-C state-machine configuration for the given port - fn set_type_c_state_machine_config( - &mut self, - port: LocalPortId, - state: TypeCStateMachineState, - ) -> impl Future>>; - - /// Execute the given UCSI command - fn execute_ucsi_command( - &mut self, - command: lpm::LocalCommand, - ) -> impl Future, Error>>; - - /// Execute an electrical disconnect on the given port, if supported by the controller. - /// - /// If `reconnect_time_s` is provided, the controller should automatically reconnect the port after the specified time - /// has elapsed. If `reconnect_time_s` is [`None`], the port should remain disconnected until manually reconnected. - fn execute_electrical_disconnect( - &mut self, - port: LocalPortId, - reconnect_time_s: Option, - ) -> impl Future>>; - - /// Set the system power state on the given port. - /// - /// This notifies the PD controller of the current system power state, - /// which triggers Application Configuration updates (e.g., crossbar reconfiguration). - fn set_power_state( - &mut self, - port: LocalPortId, - state: SystemPowerState, - ) -> impl Future>>; -} diff --git a/type-c-interface/src/port/pd.rs b/type-c-interface/src/port/pd.rs new file mode 100644 index 000000000..2f00ca68d --- /dev/null +++ b/type-c-interface/src/port/pd.rs @@ -0,0 +1,56 @@ +use embedded_usb_pd::{PdError, ado::Ado}; + +use crate::control::{ + dp::{DpConfig, DpStatus}, + pd::{PdStateMachineConfig, PortStatus}, + tbt::TbtConfig, + usb::UsbControlConfig, + vdm::{AttnVdm, OtherVdm, SendVdm}, +}; + +/// Trait for basic functionality from the PD spec. +pub trait Pd { + /// Returns the port status + fn get_port_status(&mut self) -> impl Future>; + + /// Clear the dead battery flag for this port. + fn clear_dead_battery_flag(&mut self) -> impl Future>; + + /// Enable or disable sink path + fn enable_sink_path(&mut self, enable: bool) -> impl Future>; + + /// Get current PD alert + fn get_pd_alert(&mut self) -> impl Future, PdError>>; + + /// Set port unconstrained status + fn set_unconstrained_power(&mut self, unconstrained: bool) -> impl Future>; + + /// Get the Rx Other VDM data for this port + fn get_other_vdm(&mut self) -> impl Future>; + /// Get the Rx Attention VDM data for this port + fn get_attn_vdm(&mut self) -> impl Future>; + /// Send a VDM to this port + fn send_vdm(&mut self, tx_vdm: SendVdm) -> impl Future>; + /// Execute PD Data Reset for this port + fn execute_drst(&mut self) -> impl Future>; + + /// Get DisplayPort status for this port + fn get_dp_status(&mut self) -> impl Future>; + /// Set DisplayPort configuration for this port + fn set_dp_config(&mut self, config: DpConfig) -> impl Future>; + + /// Set Thunderbolt configuration for this port + fn set_tbt_config(&mut self, config: TbtConfig) -> impl Future>; + + /// Set USB control configuration for this port + fn set_usb_control(&mut self, config: UsbControlConfig) -> impl Future>; +} + +/// PD state machine related controller functionality +pub trait StateMachine: Pd { + /// Set PD state-machine configuration for this port + fn set_pd_state_machine_config( + &mut self, + config: PdStateMachineConfig, + ) -> impl Future>; +} diff --git a/type-c-interface/src/port/power.rs b/type-c-interface/src/port/power.rs new file mode 100644 index 000000000..4701328cc --- /dev/null +++ b/type-c-interface/src/port/power.rs @@ -0,0 +1,13 @@ +use embedded_usb_pd::PdError; + +/// System power state related controller functionality +pub trait SystemPowerStateStatus { + /// Set the system power state on this port. + /// + /// This notifies the PD controller of the current system power state, + /// which triggers Application Configuration updates (e.g., crossbar reconfiguration). + fn set_system_power_state_status( + &mut self, + state: crate::control::power::SystemPowerState, + ) -> impl Future>; +} diff --git a/type-c-interface/src/port/retimer.rs b/type-c-interface/src/port/retimer.rs new file mode 100644 index 000000000..d64962c2c --- /dev/null +++ b/type-c-interface/src/port/retimer.rs @@ -0,0 +1,17 @@ +use embedded_usb_pd::PdError; + +use crate::control::retimer::RetimerFwUpdateState; + +/// Retimer-related functionality +pub trait Retimer { + /// Returns the retimer fw update state + fn get_rt_fw_update_status(&mut self) -> impl Future>; + /// Set retimer fw update state + fn set_rt_fw_update_state(&mut self) -> impl Future>; + /// Clear retimer fw update state + fn clear_rt_fw_update_state(&mut self) -> impl Future>; + /// Set retimer compliance + fn set_rt_compliance(&mut self) -> impl Future>; + /// Reconfigure the retimer for this port. + fn reconfigure_retimer(&mut self) -> impl Future>; +} diff --git a/type-c-interface/src/port/type_c.rs b/type-c-interface/src/port/type_c.rs new file mode 100644 index 000000000..5d24fb528 --- /dev/null +++ b/type-c-interface/src/port/type_c.rs @@ -0,0 +1,12 @@ +use embedded_usb_pd::PdError; + +use crate::{control::type_c::TypeCStateMachineState, port::pd::Pd}; + +/// Type-C state machine related controller functionality +pub trait StateMachine: Pd { + /// Set Type-C state-machine configuration for this port + fn set_type_c_state_machine_config( + &mut self, + state: TypeCStateMachineState, + ) -> impl Future>; +} diff --git a/type-c-interface/src/service/context.rs b/type-c-interface/src/service/context.rs index 6c877f5b0..4d2217833 100644 --- a/type-c-interface/src/service/context.rs +++ b/type-c-interface/src/service/context.rs @@ -2,11 +2,17 @@ use embassy_time::{Duration, with_timeout}; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{GlobalPortId, PdError}; -use crate::port::ControllerId; +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::{ - AttnVdm, Command, ControllerStatus, Device, DpConfig, DpStatus, InternalCommandData, InternalResponseData, - OtherVdm, PdStateMachineConfig, PortCommand, PortCommandData, PortResponseData, Response, RetimerFwUpdateState, - SendVdm, TbtConfig, TypeCStateMachineState, UsbControlConfig, + Command, Device, InternalCommandData, InternalResponseData, PortCommand, PortCommandData, PortResponseData, + Response, }; use crate::service; use crate::service::event::{Event, PortEvent}; @@ -58,7 +64,7 @@ impl Context { &self, controller_id: ControllerId, command: InternalCommandData, - ) -> Result, PdError> { + ) -> Result { let node = self .controllers .into_iter() @@ -90,7 +96,7 @@ impl Context { &self, controller_id: ControllerId, command: InternalCommandData, - ) -> Result, PdError> { + ) -> Result { match with_timeout( DEFAULT_TIMEOUT, self.send_controller_command_no_timeout(controller_id, command), @@ -238,23 +244,6 @@ impl Context { } } - /// Get current controller status - pub async fn get_controller_status( - &self, - controller_id: ControllerId, - ) -> Result, PdError> { - match self - .send_controller_command(controller_id, InternalCommandData::Status) - .await? - { - InternalResponseData::Status(status) => Ok(status), - r => { - error!("Invalid response: expected controller status, got {:?}", r); - Err(PdError::InvalidResponse) - } - } - } - /// Set unconstrained power for the given port pub async fn set_unconstrained_power(&self, port: GlobalPortId, unconstrained: bool) -> Result<(), PdError> { match self @@ -273,10 +262,6 @@ impl Context { .await? { InternalResponseData::Complete => Ok(()), - r => { - error!("Invalid response: expected controller status, got {:?}", r); - Err(PdError::InvalidResponse) - } } } diff --git a/type-c-interface/src/service/event.rs b/type-c-interface/src/service/event.rs index 88b4e7ea6..e8ca1bf1c 100644 --- a/type-c-interface/src/service/event.rs +++ b/type-c-interface/src/service/event.rs @@ -2,9 +2,9 @@ use embedded_usb_pd::{GlobalPortId, ado::Ado}; -use crate::port::{ - DpStatus, PortStatus, - event::{PortStatusEventBitfield, VdmData}, +use crate::{ + control::{dp::DpStatus, pd::PortStatus}, + port::event::{PortStatusEventBitfield, VdmData}, }; /// Struct containing data for a [`PortEventData::StatusChanged`] event diff --git a/type-c-interface/src/ucsi.rs b/type-c-interface/src/ucsi.rs new file mode 100644 index 000000000..2ed23175b --- /dev/null +++ b/type-c-interface/src/ucsi.rs @@ -0,0 +1,10 @@ +use embedded_usb_pd::{PdError, ucsi::lpm}; + +/// UCSI LPM command execution trait +pub trait Lpm { + /// Execute the given LPM command + fn execute_lpm_command( + &mut self, + command: lpm::LocalCommand, + ) -> impl Future, PdError>>; +} diff --git a/type-c-service/src/bridge/event_receiver.rs b/type-c-service/src/bridge/event_receiver.rs index aebad0541..da1b4be1c 100644 --- a/type-c-service/src/bridge/event_receiver.rs +++ b/type-c-service/src/bridge/event_receiver.rs @@ -1,14 +1,14 @@ use embedded_services::{GlobalRawMutex, ipc::deferred}; use type_c_interface::port; -pub type ControllerCommand<'a> = deferred::Request<'a, GlobalRawMutex, port::Command, port::Response<'static>>; +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<'static>, + pub response: port::Response, } pub struct EventReceiver { diff --git a/type-c-service/src/bridge/mod.rs b/type-c-service/src/bridge/mod.rs index dbf9f0257..a4e8bf7f8 100644 --- a/type-c-service/src/bridge/mod.rs +++ b/type-c-service/src/bridge/mod.rs @@ -1,19 +1,32 @@ //! Temporary bridge between a controller and the type-C service use embedded_services::{debug, sync::Lockable}; -use embedded_usb_pd::{Error, PdError, ucsi::lpm}; -use type_c_interface::port::{self, Controller as _, InternalResponseData, Response}; +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, Controller: Lockable> { - controller: &'device Controller, +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, Controller: Lockable> Bridge<'device, Controller> { - pub fn new(controller: &'device Controller, registration: &'static port::Device<'static>) -> Self { +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, @@ -21,7 +34,7 @@ impl<'device, Controller: Lockable> Bridge<'device, Con } /// Handle a port command - pub async fn process_port_command(&mut self, command: &port::PortCommand) -> Response<'static> { + 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 { @@ -31,178 +44,91 @@ impl<'device, Controller: Lockable> Bridge<'device, Con let mut controller = self.controller.lock().await; port::Response::Port(match command.data { - port::PortCommandData::RetimerFwUpdateGetState => { - match controller.get_rt_fw_update_status(local_port).await { - Ok(status) => Ok(port::PortResponseData::RtFwUpdateStatus(status)), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::RetimerFwUpdateSetState => { - match controller.set_rt_fw_update_state(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::RetimerFwUpdateClearState => { - match controller.clear_rt_fw_update_state(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::SetRetimerCompliance => match controller.set_rt_compliance(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::ReconfigureRetimer => match controller.reconfigure_retimer(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, + 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) => { - match controller.set_unconstrained_power(local_port, unconstrained).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::ClearDeadBatteryFlag => match controller.clear_dead_battery_flag(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::GetOtherVdm => match controller.get_other_vdm(local_port).await { - Ok(vdm) => { - debug!("Port{}: Other VDM: {:?}", local_port.0, vdm); - Ok(port::PortResponseData::OtherVdm(vdm)) - } - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::GetAttnVdm => match controller.get_attn_vdm(local_port).await { - Ok(vdm) => { - debug!("Port{}: Attention VDM: {:?}", local_port.0, vdm); - Ok(port::PortResponseData::AttnVdm(vdm)) - } - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SendVdm(tx_vdm) => match controller.send_vdm(local_port, tx_vdm).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetUsbControl(config) => { - match controller.set_usb_control(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::GetDpStatus => match controller.get_dp_status(local_port).await { - Ok(status) => { - debug!("Port{}: DP Status: {:?}", local_port.0, status); - Ok(port::PortResponseData::DpStatus(status)) - } - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetDpConfig(config) => match controller.set_dp_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::ExecuteDrst => match controller.execute_drst(local_port).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetTbtConfig(config) => match controller.set_tbt_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - }, - port::PortCommandData::SetPdStateMachineConfig(config) => { - match controller.set_pd_state_machine_config(local_port, config).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } - port::PortCommandData::SetTypeCStateMachineConfig(state) => { - match controller.set_type_c_state_machine_config(local_port, state).await { - Ok(()) => Ok(port::PortResponseData::Complete), - Err(e) => match e { - Error::Bus(_) => Err(PdError::Failed), - Error::Pd(e) => Err(e), - }, - } - } + 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_ucsi_command(lpm::Command::new(local_port, command_data)) - .await - .map_err(|e| match e { - Error::Bus(_) => PdError::Failed, - Error::Pd(e) => e, - }), + .execute_lpm_command(lpm::Command::new(local_port, command_data)) + .await, )), }) } - pub async fn process_controller_command(&mut self, command: &port::InternalCommandData) -> Response<'static> { + pub async fn process_controller_command(&mut self, command: &port::InternalCommandData) -> Response { let mut controller = self.controller.lock().await; match command { - port::InternalCommandData::Status => { - let status = controller.get_controller_status().await; - port::Response::Controller(status.map(InternalResponseData::Status).map_err(|_| PdError::Failed)) - } - // This isn't sent by the type-C service, disable it for the transition 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) - .map_err(|_| PdError::Failed), - ) + port::Response::Controller(result.map(|_| InternalResponseData::Complete)) } } } diff --git a/type-c-service/src/controller/electrical_disconnect.rs b/type-c-service/src/controller/electrical_disconnect.rs new file mode 100644 index 000000000..d4d59df6b --- /dev/null +++ b/type-c-service/src/controller/electrical_disconnect.rs @@ -0,0 +1,27 @@ +//! Electrical disconnect port trait implementation +use core::num::NonZeroU8; + +use embedded_services::{event::Sender, sync::Lockable}; +use embedded_usb_pd::PdError; +use type_c_interface::controller::electrical_disconnect::ElectricalDisconnect; + +use super::*; +use crate::controller::state::SharedState; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::electrical_disconnect::ElectricalDisconnect + for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn execute_electrical_disconnect(&mut self, reconnect_time_s: Option) -> Result<(), PdError> { + self.controller + .lock() + .await + .execute_electrical_disconnect(self.port, reconnect_time_s) + .await + } +} diff --git a/type-c-service/src/controller/max_sink_voltage.rs b/type-c-service/src/controller/max_sink_voltage.rs new file mode 100644 index 000000000..8cee77252 --- /dev/null +++ b/type-c-service/src/controller/max_sink_voltage.rs @@ -0,0 +1,24 @@ +//! Max sink voltage port trait implementation +use embedded_services::{event::Sender, sync::Lockable}; +use embedded_usb_pd::PdError; +use type_c_interface::controller::max_sink_voltage::MaxSinkVoltage; + +use super::*; +use crate::controller::state::SharedState; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::max_sink_voltage::MaxSinkVoltage for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn set_max_sink_voltage(&mut self, voltage_mv: Option) -> Result<(), PdError> { + self.controller + .lock() + .await + .set_max_sink_voltage(self.port, voltage_mv) + .await + } +} diff --git a/type-c-service/src/controller/mod.rs b/type-c-service/src/controller/mod.rs index dc6bf0d84..bc739d278 100644 --- a/type-c-service/src/controller/mod.rs +++ b/type-c-service/src/controller/mod.rs @@ -1,11 +1,11 @@ //! 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::{Error, GlobalPortId, LocalPortId, PdError}; +use embedded_usb_pd::{GlobalPortId, 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::{ - Controller, PortStatus, event::PortEvent as InterfacePortEvent, event::PortStatusEventBitfield, -}; +use type_c_interface::port::{event::PortEvent as InterfacePortEvent, event::PortStatusEventBitfield}; use type_c_interface::service::event::{ PortEvent as ServicePortEvent, PortEventData as ServicePortEventData, StatusChangedData, }; @@ -14,16 +14,21 @@ use crate::controller::event::{Event, Loopback}; use crate::controller::state::SharedState; pub mod config; +pub mod electrical_disconnect; pub mod event; pub mod event_receiver; pub mod macros; +pub mod max_sink_voltage; mod pd; mod power; +pub mod retimer; pub mod state; +pub mod type_c; +pub mod ucsi; pub struct Port< 'device, - C: Lockable, + C: Lockable, Shared: Lockable, PowerSender: Sender, LoopbackSender: Sender, @@ -54,7 +59,7 @@ pub struct Port< impl< 'device, - C: Lockable, + C: Lockable, Shared: Lockable, PowerSender: Sender, LoopbackSender: Sender, @@ -90,20 +95,14 @@ impl< } /// Top-level processing function - pub async fn process_event( - &mut self, - event: Event, - ) -> Result, Error<::BusError>> { + pub async fn process_event(&mut self, event: Event) -> Result, PdError> { match event { Event::PortEvent(port_event) => self.process_port_event(port_event).await, } } /// Process a port notification - async fn process_port_event( - &mut self, - event: InterfacePortEvent, - ) -> Result, Error<::BusError>> { + async fn process_port_event(&mut self, event: InterfacePortEvent) -> Result, PdError> { match event { InterfacePortEvent::StatusChanged(status_event) => { self.process_port_status_changed(status_event).await.map(Some) @@ -123,7 +122,7 @@ impl< async fn process_port_status_changed( &mut self, status_event: PortStatusEventBitfield, - ) -> Result::BusError>> { + ) -> Result { let new_status = self.controller.lock().await.get_port_status(self.port).await?; debug!("({}) status: {:#?}", self.name, new_status); debug!("({}) status events: {:#?}", self.name, status_event); @@ -159,16 +158,12 @@ impl< port: self.global_port, event, }) - .await - .map_err(Error::Pd)?; + .await?; Ok(event) } /// Handle a plug event - async fn process_plug_event( - &mut self, - new_status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + async fn process_plug_event(&mut self, new_status: &PortStatus) -> Result<(), PdError> { info!("Plug event"); if new_status.is_connected() { info!("Plug inserted"); @@ -180,7 +175,7 @@ impl< if let Err(e) = self.psu_state.attach() { // This should never happen because we should have detached above error!("Failed to attach PSU: {:?}", e); - return Err(Error::Pd(PdError::Failed)); + return Err(PdError::Failed); } self.power_policy_sender @@ -203,7 +198,7 @@ impl< } /// Synchronize the state between the controller and the internal state - pub async fn sync_state(&mut self) -> Result<(), Error<::BusError>> { + pub async fn sync_state(&mut self) -> Result<(), PdError> { let status = self.controller.lock().await.get_port_status(self.port).await?; let mut event = PortEventBitfield::none(); @@ -230,7 +225,7 @@ impl< impl< 'device, - C: Lockable, + C: Lockable, Shared: Lockable, PowerSender: Sender, LoopbackSender: Sender, diff --git a/type-c-service/src/controller/pd.rs b/type-c-service/src/controller/pd.rs index 07d71c9f4..0cfe2bc6d 100644 --- a/type-c-service/src/controller/pd.rs +++ b/type-c-service/src/controller/pd.rs @@ -1,9 +1,16 @@ //! PD functionality unrelated to power contracts and general port status use embedded_services::{event::Sender, sync::Lockable}; -use type_c_interface::port::{ - Controller, - event::{VdmData, VdmNotification}, +use embedded_usb_pd::PdError; +use embedded_usb_pd::ado::Ado; +use type_c_interface::control::{ + dp::{DpConfig, DpStatus}, + pd::{PdStateMachineConfig, PortStatus}, + tbt::TbtConfig, + usb::UsbControlConfig, + vdm::{AttnVdm, OtherVdm, SendVdm}, }; +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 super::*; @@ -11,17 +18,14 @@ use crate::controller::state::SharedState; impl< 'device, - C: Lockable, + C: Lockable, Shared: Lockable, PowerSender: Sender, LoopbackSender: Sender, > Port<'device, C, Shared, 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::BusError>> { + pub(super) async fn process_vdm_event(&mut self, event: VdmNotification) -> Result { debug!("({}): Processing VDM event: {:?}", self.name, event); let vdm_data = { let mut controller = self.controller.lock().await; @@ -45,9 +49,7 @@ impl< } /// Process a DisplayPort status update by retrieving the current DP status from the `controller` for the appropriate `port`. - pub(super) async fn process_dp_status_update( - &mut self, - ) -> Result::BusError>> { + pub(super) async fn process_dp_status_update(&mut self) -> Result { 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); @@ -61,9 +63,7 @@ impl< Ok(event) } - pub(super) async fn process_pd_alert( - &mut self, - ) -> Result, Error<::BusError>> { + pub(super) async fn process_pd_alert(&mut self) -> Result, PdError> { let ado = self.controller.lock().await.get_pd_alert(self.port).await?; debug!("({}): PD alert: {:#?}", self.name, ado); if let Some(ado) = ado { @@ -82,3 +82,85 @@ impl< } } } + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::pd::Pd for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn get_port_status(&mut self) -> Result { + self.controller.lock().await.get_port_status(self.port).await + } + + async fn clear_dead_battery_flag(&mut self) -> Result<(), PdError> { + self.controller.lock().await.clear_dead_battery_flag(self.port).await + } + + async fn enable_sink_path(&mut self, enable: bool) -> Result<(), PdError> { + self.controller.lock().await.enable_sink_path(self.port, enable).await + } + + async fn get_pd_alert(&mut self) -> Result, PdError> { + self.controller.lock().await.get_pd_alert(self.port).await + } + + async fn set_unconstrained_power(&mut self, unconstrained: bool) -> Result<(), PdError> { + self.controller + .lock() + .await + .set_unconstrained_power(self.port, unconstrained) + .await + } + + async fn get_other_vdm(&mut self) -> Result { + self.controller.lock().await.get_other_vdm(self.port).await + } + + async fn get_attn_vdm(&mut self) -> Result { + self.controller.lock().await.get_attn_vdm(self.port).await + } + + async fn send_vdm(&mut self, tx_vdm: SendVdm) -> Result<(), PdError> { + self.controller.lock().await.send_vdm(self.port, tx_vdm).await + } + + async fn execute_drst(&mut self) -> Result<(), PdError> { + self.controller.lock().await.execute_drst(self.port).await + } + + async fn get_dp_status(&mut self) -> Result { + self.controller.lock().await.get_dp_status(self.port).await + } + + async fn set_dp_config(&mut self, config: DpConfig) -> Result<(), PdError> { + self.controller.lock().await.set_dp_config(self.port, config).await + } + + async fn set_tbt_config(&mut self, config: TbtConfig) -> Result<(), PdError> { + self.controller.lock().await.set_tbt_config(self.port, config).await + } + + async fn set_usb_control(&mut self, config: UsbControlConfig) -> Result<(), PdError> { + self.controller.lock().await.set_usb_control(self.port, config).await + } +} + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::pd::StateMachine for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn set_pd_state_machine_config(&mut self, config: PdStateMachineConfig) -> Result<(), PdError> { + self.controller + .lock() + .await + .set_pd_state_machine_config(self.port, config) + .await + } +} diff --git a/type-c-service/src/controller/power.rs b/type-c-service/src/controller/power.rs index 8fd0bd6e0..9e477e410 100644 --- a/type-c-service/src/controller/power.rs +++ b/type-c-service/src/controller/power.rs @@ -9,25 +9,22 @@ use power_policy_interface::{ capability::{ConsumerPowerCapability, ProviderPowerCapability, PsuType}, psu::{Error as PsuError, Psu, State}, }; -use type_c_interface::port::Controller; +use type_c_interface::controller::power::SystemPowerStateStatus; -use crate::{controller::config::UnconstrainedSink, util::power_policy_error_from_pd_bus_error}; +use crate::{controller::config::UnconstrainedSink, util::power_policy_error_from_pd_error}; use super::*; impl< 'device, - C: Lockable, + C: Lockable, Shared: Lockable, PowerSender: Sender, LoopbackSender: Sender, > Port<'device, C, Shared, PowerSender, LoopbackSender> { /// Handle a new contract as consumer - pub(super) async fn process_new_consumer_contract( - &mut self, - new_status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + pub(super) async fn process_new_consumer_contract(&mut self, new_status: &PortStatus) -> Result<(), PdError> { info!("Process new consumer contract"); let available_sink_contract = new_status.available_sink_contract.map(|c| { let mut c: ConsumerPowerCapability = c.into(); @@ -43,7 +40,7 @@ impl< if let Err(e) = self.psu_state.update_consumer_power_capability(available_sink_contract) { error!("Failed to update consumer power capability: {:?}", e); - return Err(Error::Pd(PdError::Failed)); + return Err(PdError::Failed); } self.power_policy_sender .send(power_policy_interface::psu::event::EventData::UpdatedConsumerCapability(available_sink_contract)) @@ -52,10 +49,7 @@ impl< } /// Handle a new contract as provider - pub(super) async fn process_new_provider_contract( - &mut self, - new_status: &PortStatus, - ) -> Result<(), Error<::BusError>> { + pub(super) async fn process_new_provider_contract(&mut self, new_status: &PortStatus) -> Result<(), PdError> { info!("Process New provider contract"); let capability = new_status.available_source_contract.map(|caps| { let mut caps = ProviderPowerCapability::from(caps); @@ -64,7 +58,7 @@ impl< }); if let Err(e) = self.psu_state.update_requested_provider_power_capability(capability) { error!("Failed to update requested provider power capability: {:?}", e); - return Err(Error::Pd(PdError::Failed)); + return Err(PdError::Failed); } self.power_policy_sender .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) @@ -118,7 +112,7 @@ impl< impl< 'device, - C: Lockable, + C: Lockable, Shared: Lockable, PowerSender: Sender, LoopbackSender: Sender, @@ -132,7 +126,7 @@ impl< .await .map_err(|e| { error!("({}): Error disabling sink path", self.name); - power_policy_error_from_pd_bus_error(e) + power_policy_error_from_pd_error(e) })?; self.psu_state.disconnect(false) } @@ -157,7 +151,7 @@ impl< .await .map_err(|e| { error!("({}): Error enabling sink path", self.name); - power_policy_error_from_pd_bus_error(e) + power_policy_error_from_pd_error(e) })?; self.psu_state.connect_consumer(capability) } @@ -170,3 +164,23 @@ impl< &mut self.psu_state } } + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::power::SystemPowerStateStatus for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn set_system_power_state_status( + &mut self, + state: type_c_interface::control::power::SystemPowerState, + ) -> Result<(), PdError> { + self.controller + .lock() + .await + .set_system_power_state_status(self.port, state) + .await + } +} diff --git a/type-c-service/src/controller/retimer.rs b/type-c-service/src/controller/retimer.rs new file mode 100644 index 000000000..cdb9a4a0a --- /dev/null +++ b/type-c-service/src/controller/retimer.rs @@ -0,0 +1,37 @@ +//! Retimer port trait implementation +use embedded_services::{event::Sender, sync::Lockable}; +use embedded_usb_pd::PdError; +use type_c_interface::control::retimer::RetimerFwUpdateState; +use type_c_interface::controller::retimer::Retimer; + +use super::*; +use crate::controller::state::SharedState; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::retimer::Retimer for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn get_rt_fw_update_status(&mut self) -> Result { + self.controller.lock().await.get_rt_fw_update_status(self.port).await + } + + async fn set_rt_fw_update_state(&mut self) -> Result<(), PdError> { + self.controller.lock().await.set_rt_fw_update_state(self.port).await + } + + async fn clear_rt_fw_update_state(&mut self) -> Result<(), PdError> { + self.controller.lock().await.clear_rt_fw_update_state(self.port).await + } + + async fn set_rt_compliance(&mut self) -> Result<(), PdError> { + self.controller.lock().await.set_rt_compliance(self.port).await + } + + async fn reconfigure_retimer(&mut self) -> Result<(), PdError> { + self.controller.lock().await.reconfigure_retimer(self.port).await + } +} diff --git a/type-c-service/src/controller/type_c.rs b/type-c-service/src/controller/type_c.rs new file mode 100644 index 000000000..663212e65 --- /dev/null +++ b/type-c-service/src/controller/type_c.rs @@ -0,0 +1,25 @@ +//! Type-C state machine port trait implementation +use embedded_services::{event::Sender, sync::Lockable}; +use embedded_usb_pd::PdError; +use type_c_interface::control::type_c::TypeCStateMachineState; +use type_c_interface::controller::type_c::StateMachine; + +use super::*; +use crate::controller::state::SharedState; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::port::type_c::StateMachine for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn set_type_c_state_machine_config(&mut self, state: TypeCStateMachineState) -> Result<(), PdError> { + self.controller + .lock() + .await + .set_type_c_state_machine_config(self.port, state) + .await + } +} diff --git a/type-c-service/src/controller/ucsi.rs b/type-c-service/src/controller/ucsi.rs new file mode 100644 index 000000000..b899720be --- /dev/null +++ b/type-c-service/src/controller/ucsi.rs @@ -0,0 +1,23 @@ +//! UCSI LPM port trait implementation +use embedded_services::{event::Sender, sync::Lockable}; +use embedded_usb_pd::PdError; +use type_c_interface::ucsi::Lpm as UcsiLpm; + +use super::*; +use crate::controller::state::SharedState; + +impl< + 'device, + C: Lockable, + Shared: Lockable, + PowerSender: Sender, + LoopbackSender: Sender, +> type_c_interface::ucsi::Lpm for Port<'device, C, Shared, PowerSender, LoopbackSender> +{ + async fn execute_lpm_command( + &mut self, + command: embedded_usb_pd::ucsi::lpm::LocalCommand, + ) -> Result, PdError> { + self.controller.lock().await.execute_lpm_command(command).await + } +} diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index 906a298ea..1560cd706 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -29,17 +29,22 @@ use tps6699x::command::{ use tps6699x::fw_update::UpdateConfig as FwUpdateConfig; use tps6699x::registers::field_sets::IntEventBus1; use tps6699x::registers::port_config::TypeCStateMachine; +use type_c_interface::control::dp::{DpConfig, DpPinConfig, DpStatus}; +use type_c_interface::control::pd::{PdStateMachineConfig, PortStatus}; +use type_c_interface::control::power::SystemPowerState; +use type_c_interface::control::retimer::RetimerFwUpdateState; +use type_c_interface::control::tbt::TbtConfig; +use type_c_interface::control::type_c::TypeCStateMachineState; +use type_c_interface::control::usb::UsbControlConfig; +use type_c_interface::control::vdm::{ATTN_VDM_LEN, AttnVdm, OtherVdm, SendVdm}; +use type_c_interface::controller::Controller; +use type_c_interface::controller::pd::Pd; +use type_c_interface::controller::retimer::Retimer; use type_c_interface::port::event::PortEventBitfield; -use type_c_interface::port::{ - ATTN_VDM_LEN, DpConfig, DpStatus, PdStateMachineConfig, RetimerFwUpdateState, SystemPowerState, -}; -use type_c_interface::port::{ - AttnVdm, Controller, ControllerStatus, DpPinConfig, OtherVdm, PortStatus, SendVdm, TbtConfig, - TypeCStateMachineState, UsbControlConfig, -}; -use crate::util::power_capability_try_from_contract; -use crate::util::{basic_fw_update_error_from_pd_bus_error, power_capability_from_current}; +use crate::util::{ + basic_fw_update_error_from_pd_error, power_capability_from_current, power_capability_try_from_contract, +}; type Updater<'a, M, B> = BorrowedUpdaterInProgress>; @@ -116,18 +121,23 @@ impl<'a, M: RawMutex, B: I2c> Tps6699x<'a, M, B> { } } - fn log_error(&self, e: Error) -> Error { + fn log_error(&self, e: Error) -> PdError { match e { - Error::Bus(_) => error!("({}): Bus error", self.name()), - Error::Pd(pd_error) => error!("({}): PD error: {:#?}", self.name(), pd_error), + Error::Bus(_) => { + error!("({}): Bus error", self.name()); + PdError::Failed + } + Error::Pd(pd_error) => { + error!("({}): PD error: {:#?}", self.name(), pd_error); + pd_error + } } - e } /// Returns a busy error if a FW update is currently in progress - fn guard_no_fw_update_active(&self) -> Result<(), Error> { + fn guard_no_fw_update_active(&self) -> Result<(), PdError> { if self.update_state.is_some() { - Err(Error::Pd(PdError::Busy)) + Err(PdError::Busy) } else { Ok(()) } @@ -223,7 +233,7 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { self.tps6699x .get_customer_use() .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?, + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e)))?, ); Ok(customer_use.custom_fw_version()) } @@ -246,17 +256,17 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { // Disable all interrupts on both ports, use guards[1] to ensure that this set of guards is dropped last disable_all_interrupts::>(&mut [&mut self.tps6699x], &mut guards[1..]) .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?; + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e)))?; let in_progress = updater .start_fw_update(&mut [&mut self.tps6699x], &mut delay) .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?; + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e)))?; // Re-enable interrupts on port 0 only if let Err(e) = enable_port0_interrupts::>(&mut [&mut self.tps6699x], &mut guards[0..1]) .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e))) + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e))) { error!("Failed to enable port 0 interrupts, aborting update: {:#?}", e); in_progress.abort_fw_update(&mut [&mut self.tps6699x], &mut delay).await; @@ -279,7 +289,7 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { .tps6699x .get_mode() .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))? + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e)))? == tps6699x::Mode::F211 { let mut delay = Delay; @@ -296,7 +306,7 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { self.tps6699x .fw_update_mode_exit(&mut delay) .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e))) + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e))) } } else { // Not in FW update mode, don't need to do anything @@ -314,7 +324,7 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { .updater .complete_fw_update(&mut [&mut self.tps6699x], &mut delay) .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e))) + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e))) } else { Err(BasicFwUpdateError::NeedsActiveUpdate) } @@ -327,7 +337,7 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { .updater .write_bytes(&mut [&mut self.tps6699x], &mut delay, data) .await - .map_err(|e| basic_fw_update_error_from_pd_bus_error(self.log_error(e)))?; + .map_err(|e| basic_fw_update_error_from_pd_error(self.log_error(e)))?; Ok(()) } else { Err(BasicFwUpdateError::NeedsActiveUpdate) @@ -336,27 +346,35 @@ impl<'a, M: RawMutex, B: I2c> BasicFwUpdate for Tps6699x<'a, M, B> { } impl Controller for Tps6699x<'_, M, B> { - type BusError = B::Error; - /// Controller reset - async fn reset_controller(&mut self) -> Result<(), Error> { + async fn reset_controller(&mut self) -> Result<(), PdError> { self.guard_no_fw_update_active()?; let mut delay = Delay; - self.tps6699x.reset(&mut delay).await?; + self.tps6699x.reset(&mut delay).await.map_err(|e| self.log_error(e))?; Ok(()) } +} +impl Pd for Tps6699x<'_, M, B> { /// Returns the current status of the port - async fn get_port_status(&mut self, port: LocalPortId) -> Result> { + async fn get_port_status(&mut self, port: LocalPortId) -> Result { self.guard_no_fw_update_active()?; - let status = self.tps6699x.get_port_status(port).await?; + let status = self + .tps6699x + .get_port_status(port) + .await + .map_err(|e| self.log_error(e))?; debug!("Port{} status: {:#?}", port.0, status); - let pd_status = self.tps6699x.get_pd_status(port).await?; + let pd_status = self.tps6699x.get_pd_status(port).await.map_err(|e| self.log_error(e))?; debug!("Port{} PD status: {:#?}", port.0, pd_status); - let port_control = self.tps6699x.get_port_control(port).await?; + let port_control = self + .tps6699x + .get_port_control(port) + .await + .map_err(|e| self.log_error(e))?; debug!("Port{} control: {:#?}", port.0, port_control); let mut port_status = PortStatus::default(); @@ -369,16 +387,26 @@ impl Controller for Tps6699x<'_, M, B> { if port_status.is_connected() { // Determine current contract if any - let pdo_raw = self.tps6699x.get_active_pdo_contract(port).await?.active_pdo(); + let pdo_raw = self + .tps6699x + .get_active_pdo_contract(port) + .await + .map_err(|e| self.log_error(e))? + .active_pdo(); trace!("Raw PDO: {:#X}", pdo_raw); - let rdo_raw = self.tps6699x.get_active_rdo_contract(port).await?.active_rdo(); + let rdo_raw = self + .tps6699x + .get_active_rdo_contract(port) + .await + .map_err(|e| self.log_error(e))? + .active_rdo(); trace!("Raw RDO: {:#X}", rdo_raw); if pdo_raw != 0 && rdo_raw != 0 { // Got a valid explicit contract if pd_status.is_source() { - let pdo = source::Pdo::try_from(pdo_raw).map_err(|_| Error::from(PdError::InvalidParams))?; - let rdo = Rdo::for_pdo(rdo_raw, pdo).ok_or(Error::Pd(PdError::InvalidParams))?; + let pdo = source::Pdo::try_from(pdo_raw)?; + let rdo = Rdo::for_pdo(rdo_raw, pdo).ok_or(PdError::InvalidParams)?; debug!("PDO: {:#?}", pdo); debug!("RDO: {:#?}", rdo); port_status.available_source_contract = @@ -388,21 +416,23 @@ impl Controller for Tps6699x<'_, M, B> { // active_rdo_contract doesn't contain the full picture let mut source_pdos: [source::Pdo; 1] = [source::Pdo::default()]; // Read 5V fixed supply source PDO, guaranteed to be present as the first SPR PDO - let (num_sprs, _) = self - .tps6699x - .lock_inner() - .await - .get_rx_src_caps(port, &mut source_pdos[..], &mut []) - .await?; + let (num_sprs, _) = { + self.tps6699x + .lock_inner() + .await + .get_rx_src_caps(port, &mut source_pdos[..], &mut []) + .await + } + .map_err(|e| self.log_error(e.into()))?; if num_sprs == 0 { // USB PD spec requires at least one source PDO be present, something is really wrong error!("Port{} no source PDOs found", port.0); - return Err(PdError::InvalidParams.into()); + return Err(PdError::InvalidParams); } - let pdo = sink::Pdo::try_from(pdo_raw).map_err(|_| Error::from(PdError::InvalidParams))?; - let rdo = Rdo::for_pdo(rdo_raw, pdo).ok_or(Error::Pd(PdError::InvalidParams))?; + let pdo = sink::Pdo::try_from(pdo_raw)?; + let rdo = Rdo::for_pdo(rdo_raw, pdo).ok_or(PdError::InvalidParams)?; debug!("PDO: {:#?}", pdo); debug!("RDO: {:#?}", rdo); port_status.available_sink_contract = @@ -413,7 +443,7 @@ impl Controller for Tps6699x<'_, M, B> { } else if status.port_role() { // port_role is true for source // Implicit source contract - let current = TypecCurrent::try_from(port_control.typec_current()).map_err(Error::Pd)?; + let current = TypecCurrent::try_from(port_control.typec_current())?; debug!("Port{} type-C source current: {:#?}", port.0, current); port_status.available_source_contract = Some(power_capability_from_current(current)); } else { @@ -424,7 +454,7 @@ impl Controller for Tps6699x<'_, M, B> { debug!("Port{} no pull up", port.0); None } else { - let current = TypecCurrent::try_from(pd_status.cc_pull_up()).map_err(Error::Pd)?; + let current = TypecCurrent::try_from(pd_status.cc_pull_up())?; debug!("Port{} type-C sink current: {:#?}", port.0, current); Some(power_capability_from_current(current)) }; @@ -448,12 +478,20 @@ impl Controller for Tps6699x<'_, M, B> { }; // Update alt-mode status - let alt_mode = self.tps6699x.get_alt_mode_status(port).await?; + let alt_mode = self + .tps6699x + .get_alt_mode_status(port) + .await + .map_err(|e| self.log_error(e))?; debug!("Port{} alt mode: {:#?}", port.0, alt_mode); port_status.alt_mode = alt_mode; // Update power path status - let power_path = self.tps6699x.get_power_path_status(port).await?; + let power_path = self + .tps6699x + .get_power_path_status(port) + .await + .map_err(|e| self.log_error(e))?; trace!("Port{} power source: {:#?}", port.0, power_path); port_status.power_path = match port { PORT0 => PowerPathStatus::new( @@ -472,113 +510,51 @@ impl Controller for Tps6699x<'_, M, B> { Ok(port_status) } - async fn get_rt_fw_update_status( - &mut self, - port: LocalPortId, - ) -> Result> { - self.guard_no_fw_update_active()?; - match self.tps6699x.get_rt_fw_update_status(port).await { - Ok(true) => Ok(RetimerFwUpdateState::Active), - Ok(false) => Ok(RetimerFwUpdateState::Inactive), - Err(e) => Err(e), - } - } - - async fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> Result<(), Error> { - self.guard_no_fw_update_active()?; - self.tps6699x.set_rt_fw_update_state(port).await - } - - async fn clear_rt_fw_update_state(&mut self, port: LocalPortId) -> Result<(), Error> { - self.guard_no_fw_update_active()?; - self.tps6699x.clear_rt_fw_update_state(port).await - } - - async fn set_rt_compliance(&mut self, port: LocalPortId) -> Result<(), Error> { + async fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> Result<(), PdError> { self.guard_no_fw_update_active()?; - self.tps6699x.set_rt_compliance(port).await - } - - async fn reconfigure_retimer(&mut self, port: LocalPortId) -> Result<(), Error> { - self.guard_no_fw_update_active()?; - let input = { - let mut input = tps6699x::command::muxr::Input(0); - input.set_en_retry_on_target_addr_tbt(true); - input - }; - - match self.tps6699x.execute_muxr(port, input).await? { + match self.tps6699x.execute_dbfg(port).await.map_err(|e| self.log_error(e))? { ReturnValue::Success => Ok(()), r => { - debug!("Error executing MuxR on port {}: {:#?}", port.0, r); - Err(Error::Pd(PdError::InvalidResponse)) + error!("Error executing DBfg on port {}: {:#?}", port.0, r); + Err(PdError::InvalidResponse) } } } - async fn clear_dead_battery_flag(&mut self, port: LocalPortId) -> Result<(), Error> { - self.guard_no_fw_update_active()?; - match self.tps6699x.execute_dbfg(port).await? { - ReturnValue::Success => Ok(()), - r => { - debug!("Error executing DBfg on port {}: {:#?}", port.0, r); - Err(Error::Pd(PdError::InvalidResponse)) - } - } - } - - async fn enable_sink_path(&mut self, port: LocalPortId, enable: bool) -> Result<(), Error> { + async fn enable_sink_path(&mut self, port: LocalPortId, enable: bool) -> Result<(), PdError> { self.guard_no_fw_update_active()?; debug!("Port{} enable sink path: {}", port.0, enable); - self.tps6699x.enable_sink_path(port, enable).await - } - - async fn get_pd_alert(&mut self, port: LocalPortId) -> Result, Error> { - self.guard_no_fw_update_active()?; - self.tps6699x.get_rx_ado(port).await.map_err(Error::from) - } - - async fn get_controller_status(&mut self) -> Result, Error> { - self.guard_no_fw_update_active()?; - let boot_flags = self.tps6699x.get_boot_flags().await?; - let customer_use = CustomerUse(self.tps6699x.get_customer_use().await?); - - Ok(ControllerStatus { - mode: self.tps6699x.get_mode().await?.into(), - valid_fw_bank: (boot_flags.active_bank() == 0 && boot_flags.bank0_valid() != 0) - || (boot_flags.active_bank() == 1 && boot_flags.bank1_valid() != 0), - fw_version0: customer_use.ti_fw_version(), - fw_version1: customer_use.custom_fw_version(), - }) + self.tps6699x + .enable_sink_path(port, enable) + .await + .map_err(|e| self.log_error(e)) } - async fn set_unconstrained_power( - &mut self, - port: LocalPortId, - unconstrained: bool, - ) -> Result<(), Error> { + async fn get_pd_alert(&mut self, port: LocalPortId) -> Result, PdError> { self.guard_no_fw_update_active()?; - self.tps6699x.set_unconstrained_power(port, unconstrained).await + self.tps6699x + .get_rx_ado(port) + .await + .map_err(|e| self.log_error(e.into())) } - async fn set_max_sink_voltage( - &mut self, - port: LocalPortId, - voltage_mv: Option, - ) -> Result<(), Error> { + async fn set_unconstrained_power(&mut self, port: LocalPortId, unconstrained: bool) -> Result<(), PdError> { self.guard_no_fw_update_active()?; - self.tps6699x.set_autonegotiate_sink_max_voltage(port, voltage_mv).await + self.tps6699x + .set_unconstrained_power(port, unconstrained) + .await + .map_err(|e| self.log_error(e)) } - async fn get_other_vdm(&mut self, port: LocalPortId) -> Result> { + async fn get_other_vdm(&mut self, port: LocalPortId) -> Result { self.guard_no_fw_update_active()?; match self.tps6699x.get_rx_other_vdm(port).await { Ok(vdm) => Ok((*vdm.as_bytes()).into()), - Err(e) => Err(e), + Err(e) => Err(self.log_error(e)), } } - async fn get_attn_vdm(&mut self, port: LocalPortId) -> Result> { + async fn get_attn_vdm(&mut self, port: LocalPortId) -> Result { self.guard_no_fw_update_active()?; match self.tps6699x.get_rx_attn_vdm(port).await { Ok(vdm) => { @@ -586,11 +562,11 @@ impl Controller for Tps6699x<'_, M, B> { let attn_vdm: AttnVdm = buf.into(); Ok(attn_vdm) } - Err(e) => Err(e), + Err(e) => Err(self.log_error(e)), } } - async fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> Result<(), Error> { + async fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> Result<(), PdError> { self.guard_no_fw_update_active()?; let input = { let mut input = tps6699x::command::vdms::Input::default(); @@ -611,21 +587,22 @@ impl Controller for Tps6699x<'_, M, B> { input }; - match self.tps6699x.send_vdms(port, input).await? { + match self + .tps6699x + .send_vdms(port, input) + .await + .map_err(|e| self.log_error(e))? + { ReturnValue::Success => Ok(()), r => { - debug!("Error executing VDMs on port {}: {:#?}", port.0, r); - Err(Error::Pd(PdError::InvalidResponse)) + error!("Error executing VDMs on port {}: {:#?}", port.0, r); + Err(PdError::InvalidResponse) } } } /// Set USB control configuration for the given port - async fn set_usb_control( - &mut self, - port: LocalPortId, - config: UsbControlConfig, - ) -> Result<(), Error> { + async fn set_usb_control(&mut self, port: LocalPortId, config: UsbControlConfig) -> Result<(), PdError> { self.guard_no_fw_update_active()?; match self.config.usb_control_method { UsbControlMethod::TxIdentity => { @@ -639,7 +616,8 @@ impl Controller for Tps6699x<'_, M, B> { identity.set_dfp1_vdo(dfp_vdo.0); identity.clone() }) - .await?; + .await + .map_err(|e| self.log_error(e))?; } UsbControlMethod::DpConfig => { use tps6699x::registers::DpUsbDataPath; @@ -654,7 +632,8 @@ impl Controller for Tps6699x<'_, M, B> { dp_config.set_usb_data_path(usb_data_path); *dp_config }) - .await?; + .await + .map_err(|e| self.log_error(e))?; } UsbControlMethod::TbtConfig => { use tps6699x::registers::TbtUsbDataPath; @@ -664,23 +643,26 @@ impl Controller for Tps6699x<'_, M, B> { TbtUsbDataPath::NotRequired }; - self.tps6699x - .lock_inner() - .await - .modify_tbt_config(port, |tbt_config| { - tbt_config.set_usb_data_path(usb_data_path); - *tbt_config - }) - .await?; + { + self.tps6699x + .lock_inner() + .await + .modify_tbt_config(port, |tbt_config| { + tbt_config.set_usb_data_path(usb_data_path); + *tbt_config + }) + .await + } + .map_err(|e| self.log_error(e))?; } } Ok(()) } - async fn get_dp_status(&mut self, port: LocalPortId) -> Result> { + async fn get_dp_status(&mut self, port: LocalPortId) -> Result { self.guard_no_fw_update_active()?; - let dp_status = self.tps6699x.get_dp_status(port).await?; + let dp_status = self.tps6699x.get_dp_status(port).await.map_err(|e| self.log_error(e))?; debug!("Port{} DP status: {:#?}", port.0, dp_status); let alt_mode_entered = dp_status.dp_mode_active() != 0; @@ -695,11 +677,11 @@ impl Controller for Tps6699x<'_, M, B> { }) } - async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), Error> { + async fn set_dp_config(&mut self, port: LocalPortId, config: DpConfig) -> Result<(), PdError> { self.guard_no_fw_update_active()?; debug!("Port{} setting DP config: {:#?}", port.0, config); - let mut dp_config_reg = self.tps6699x.get_dp_config(port).await?; + let mut dp_config_reg = self.tps6699x.get_dp_config(port).await.map_err(|e| self.log_error(e))?; debug!("Current DP config: {:#?}", dp_config_reg); @@ -707,57 +689,124 @@ impl Controller for Tps6699x<'_, M, B> { let cfg_raw: PdDpPinConfig = config.dfp_d_pin_cfg.into(); dp_config_reg.set_dfpd_pin_assignment(cfg_raw.bits()); - self.tps6699x.set_dp_config(port, dp_config_reg).await?; + self.tps6699x + .set_dp_config(port, dp_config_reg) + .await + .map_err(|e| self.log_error(e))?; Ok(()) } - async fn execute_drst(&mut self, port: LocalPortId) -> Result<(), Error> { + async fn execute_drst(&mut self, port: LocalPortId) -> Result<(), PdError> { self.guard_no_fw_update_active()?; - match self.tps6699x.execute_drst(port).await? { + match self.tps6699x.execute_drst(port).await.map_err(|e| self.log_error(e))? { ReturnValue::Success => Ok(()), r => { error!("Error executing DRST on port {}: {:#?}", port.0, r); - Err(Error::Pd(PdError::InvalidResponse)) + Err(PdError::InvalidResponse) } } } - async fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> Result<(), Error> { + async fn set_tbt_config(&mut self, port: LocalPortId, config: TbtConfig) -> Result<(), PdError> { self.guard_no_fw_update_active()?; debug!("Port{} setting TBT config: {:#?}", port.0, config); - let mut config_reg = self.tps6699x.lock_inner().await.get_tbt_config(port).await?; + let mut config_reg = + { self.tps6699x.lock_inner().await.get_tbt_config(port).await }.map_err(|e| self.log_error(e))?; config_reg.set_tbt_vid_en(config.tbt_enabled); config_reg.set_tbt_mode_en(config.tbt_enabled); - self.tps6699x.lock_inner().await.set_tbt_config(port, config_reg).await + { self.tps6699x.lock_inner().await.set_tbt_config(port, config_reg).await }.map_err(|e| self.log_error(e)) } +} +impl Retimer for Tps6699x<'_, M, B> { + async fn get_rt_fw_update_status(&mut self, port: LocalPortId) -> Result { + self.guard_no_fw_update_active()?; + match self.tps6699x.get_rt_fw_update_status(port).await { + Ok(true) => Ok(RetimerFwUpdateState::Active), + Ok(false) => Ok(RetimerFwUpdateState::Inactive), + Err(e) => Err(self.log_error(e)), + } + } + + async fn set_rt_fw_update_state(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.guard_no_fw_update_active()?; + self.tps6699x + .set_rt_fw_update_state(port) + .await + .map_err(|e| self.log_error(e)) + } + + async fn clear_rt_fw_update_state(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.guard_no_fw_update_active()?; + self.tps6699x + .clear_rt_fw_update_state(port) + .await + .map_err(|e| self.log_error(e)) + } + + async fn set_rt_compliance(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.guard_no_fw_update_active()?; + self.tps6699x + .set_rt_compliance(port) + .await + .map_err(|e| self.log_error(e)) + } + + async fn reconfigure_retimer(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.guard_no_fw_update_active()?; + let input = { + let mut input = tps6699x::command::muxr::Input(0); + input.set_en_retry_on_target_addr_tbt(true); + input + }; + + match self + .tps6699x + .execute_muxr(port, input) + .await + .map_err(|e| self.log_error(e))? + { + ReturnValue::Success => Ok(()), + r => { + error!("Error executing MuxR on port {}: {:#?}", port.0, r); + Err(PdError::InvalidResponse) + } + } + } +} + +impl type_c_interface::controller::pd::StateMachine for Tps6699x<'_, M, B> { async fn set_pd_state_machine_config( &mut self, port: LocalPortId, config: PdStateMachineConfig, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { self.guard_no_fw_update_active()?; debug!("Port{} setting PD state machine config: {:#?}", port.0, config); - let mut config_reg = self.tps6699x.lock_inner().await.get_port_config(port).await?; + let mut config_reg = + { self.tps6699x.lock_inner().await.get_port_config(port).await }.map_err(|e| self.log_error(e))?; config_reg.set_disable_pd(!config.enabled); - self.tps6699x.lock_inner().await.set_port_config(port, config_reg).await + { self.tps6699x.lock_inner().await.set_port_config(port, config_reg).await }.map_err(|e| self.log_error(e)) } +} +impl type_c_interface::controller::type_c::StateMachine for Tps6699x<'_, M, B> { async fn set_type_c_state_machine_config( &mut self, port: LocalPortId, state: TypeCStateMachineState, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { self.guard_no_fw_update_active()?; debug!("Port{} setting Type-C state machine state: {:#?}", port.0, state); - let mut config_reg = self.tps6699x.lock_inner().await.get_port_config(port).await?; + let mut config_reg = + { self.tps6699x.lock_inner().await.get_port_config(port).await }.map_err(|e| self.log_error(e))?; let typec_state = match state { TypeCStateMachineState::Sink => TypeCStateMachine::Sink, TypeCStateMachineState::Source => TypeCStateMachine::Source, @@ -766,38 +815,51 @@ impl Controller for Tps6699x<'_, M, B> { }; config_reg.set_typec_state_machine(typec_state); - self.tps6699x.lock_inner().await.set_port_config(port, config_reg).await + { self.tps6699x.lock_inner().await.set_port_config(port, config_reg).await }.map_err(|e| self.log_error(e)) } +} - async fn execute_ucsi_command( - &mut self, - command: lpm::LocalCommand, - ) -> Result, Error> { +impl type_c_interface::ucsi::Lpm for Tps6699x<'_, M, B> { + async fn execute_lpm_command(&mut self, command: lpm::LocalCommand) -> Result, PdError> { self.guard_no_fw_update_active()?; - self.tps6699x.execute_ucsi_command(&command).await + self.tps6699x + .execute_ucsi_command(&command) + .await + .map_err(|e| self.log_error(e)) } +} +impl type_c_interface::controller::electrical_disconnect::ElectricalDisconnect + for Tps6699x<'_, M, B> +{ async fn execute_electrical_disconnect( &mut self, port: LocalPortId, reconnect_time_s: Option, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { self.guard_no_fw_update_active()?; let reconnect_time_s = reconnect_time_s.map(|t| t.get()); - match self.tps6699x.execute_disc(port, reconnect_time_s).await? { + match self + .tps6699x + .execute_disc(port, reconnect_time_s) + .await + .map_err(|e| self.log_error(e))? + { ReturnValue::Success => Ok(()), r => { - debug!("Error executing DISC on port {}: {:#?}", port.0, r); - Err(Error::Pd(PdError::InvalidResponse)) + error!("Error executing DISC on port {}: {:#?}", port.0, r); + Err(PdError::InvalidResponse) } } } +} - async fn set_power_state( +impl type_c_interface::controller::power::SystemPowerStateStatus for Tps6699x<'_, M, B> { + async fn set_system_power_state_status( &mut self, port: LocalPortId, state: SystemPowerState, - ) -> Result<(), Error> { + ) -> Result<(), PdError> { self.guard_no_fw_update_active()?; use tps6699x::registers::SystemPowerState as DriverSystemPowerState; @@ -809,7 +871,20 @@ impl Controller for Tps6699x<'_, M, B> { SystemPowerState::S0ix => DriverSystemPowerState::S0Ix, }; - self.tps6699x.set_sx_app_config(port, driver_state).await + self.tps6699x + .set_sx_app_config(port, driver_state) + .await + .map_err(|e| self.log_error(e)) + } +} + +impl type_c_interface::controller::max_sink_voltage::MaxSinkVoltage for Tps6699x<'_, M, B> { + async fn set_max_sink_voltage(&mut self, port: LocalPortId, voltage_mv: Option) -> Result<(), PdError> { + self.guard_no_fw_update_active()?; + self.tps6699x + .set_autonegotiate_sink_max_voltage(port, voltage_mv) + .await + .map_err(|e| self.log_error(e)) } } diff --git a/type-c-service/src/service/mod.rs b/type-c-service/src/service/mod.rs index 70e7f11e6..a0ff5ec8b 100644 --- a/type-c-service/src/service/mod.rs +++ b/type-c-service/src/service/mod.rs @@ -8,10 +8,11 @@ use embedded_services::{debug, error, event::Receiver, 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::Device; use type_c_interface::port::event::PortStatusEventBitfield; -use type_c_interface::port::{Device, PortStatus}; use type_c_interface::service::event; pub mod config; diff --git a/type-c-service/src/service/vdm.rs b/type-c-service/src/service/vdm.rs index d92590709..d238c61cf 100644 --- a/type-c-service/src/service/vdm.rs +++ b/type-c-service/src/service/vdm.rs @@ -1,7 +1,7 @@ //! VDM (Vendor Defined Messages) related functionality. use embedded_usb_pd::{GlobalPortId, PdError}; -use type_c_interface::port::{AttnVdm, OtherVdm}; +use type_c_interface::control::vdm::{AttnVdm, OtherVdm}; use super::Service; From 20e86d2d8ac75c7366b0fc319423141b95943f11 Mon Sep 17 00:00:00 2001 From: RobertZ2011 <33537514+RobertZ2011@users.noreply.github.com> Date: Thu, 14 May 2026 13:30:31 -0700 Subject: [PATCH 77/79] type-c-service: Migrate to async function calls (#833) * Remove bridge code and last bit of messaging * Final move away from `Context` type, application code is now responsible for creating event senders/receivers * Remove almost all `GlobalPortId` usage --- Cargo.lock | 2 - examples/rt685s-evk/Cargo.lock | 2 - examples/rt685s-evk/src/bin/type_c.rs | 126 ++--- examples/rt685s-evk/src/bin/type_c_cfu.rs | 123 ++--- examples/std/Cargo.lock | 2 - examples/std/Cargo.toml | 4 - examples/std/src/bin/type_c/basic.rs | 127 ----- examples/std/src/bin/type_c/service.rs | 92 ++-- examples/std/src/bin/type_c/ucsi.rs | 132 ++---- examples/std/src/bin/type_c/unconstrained.rs | 169 +++---- .../std/src/lib/type_c/mock_controller.rs | 13 +- type-c-interface/Cargo.toml | 12 +- type-c-interface/src/controller/mod.rs | 3 +- type-c-interface/src/controller/pd.rs | 3 +- type-c-interface/src/controller/power.rs | 3 +- type-c-interface/src/controller/retimer.rs | 3 +- type-c-interface/src/port/event.rs | 3 +- type-c-interface/src/port/mod.rs | 225 +-------- type-c-interface/src/port/pd.rs | 3 +- type-c-interface/src/port/power.rs | 3 +- type-c-interface/src/port/retimer.rs | 3 +- type-c-interface/src/service/context.rs | 433 ------------------ type-c-interface/src/service/event.rs | 41 +- type-c-interface/src/service/mod.rs | 1 - type-c-interface/src/ucsi.rs | 3 +- type-c-service/src/bridge/event_receiver.rs | 32 -- type-c-service/src/bridge/mod.rs | 147 ------ .../src/controller/electrical_disconnect.rs | 3 +- type-c-service/src/controller/macros.rs | 30 +- .../src/controller/max_sink_voltage.rs | 4 +- type-c-service/src/controller/mod.rs | 34 +- type-c-service/src/controller/pd.rs | 35 +- type-c-service/src/controller/power.rs | 10 +- type-c-service/src/controller/retimer.rs | 3 +- type-c-service/src/controller/type_c.rs | 3 +- type-c-service/src/controller/ucsi.rs | 10 +- type-c-service/src/lib.rs | 1 - type-c-service/src/service/event_receiver.rs | 100 ++++ type-c-service/src/service/mod.rs | 243 ++++------ type-c-service/src/service/power.rs | 54 ++- type-c-service/src/service/registration.rs | 67 +++ type-c-service/src/service/ucsi.rs | 143 +++--- type-c-service/src/service/vdm.rs | 18 - type-c-service/src/task.rs | 14 +- 44 files changed, 727 insertions(+), 1755 deletions(-) delete mode 100644 examples/std/src/bin/type_c/basic.rs delete mode 100644 type-c-interface/src/service/context.rs delete mode 100644 type-c-service/src/bridge/event_receiver.rs delete mode 100644 type-c-service/src/bridge/mod.rs create mode 100644 type-c-service/src/service/event_receiver.rs create mode 100644 type-c-service/src/service/registration.rs delete mode 100644 type-c-service/src/service/vdm.rs diff --git a/Cargo.lock b/Cargo.lock index 3c5e577dd..cd2d69a3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2259,8 +2259,6 @@ version = "0.1.0" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", - "embassy-sync", - "embassy-time", "embedded-services", "embedded-usb-pd", "log", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 24458c167..ab6776280 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -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..9efa5a928 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::create( + 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..4f484753a 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::create( + 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..16c0a12bb 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -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..3b5bbbc26 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::create( + 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..3797ce498 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,7 +307,7 @@ async fn task(spawner: Spawner) { ))); // Create type-c service - static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); + static TYPE_C_SERVICE: StaticCell> = StaticCell::new(); let type_c_service = TYPE_C_SERVICE.init(Mutex::new(Service::create( Config { ucsi_capabilities: UcsiCapabilities { @@ -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..039690745 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::create( + 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/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/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..e20f6bf26 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,17 +44,15 @@ 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)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct DebugAccessory { - /// Port - pub port: GlobalPortId, +pub struct DebugAccessoryData { /// Connected pub connected: bool, } @@ -58,7 +60,7 @@ pub struct DebugAccessory { /// UCSI connector change message #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct UsciChangeIndicator { +pub struct UsciChangeIndicatorData { /// Port pub port: GlobalPortId, /// Notify OPM @@ -68,9 +70,24 @@ pub struct UsciChangeIndicator { /// Top-level comms message #[derive(Copy, Clone, Debug)] #[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/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..699a4c276 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 create(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<&'port 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..5e443713c 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,22 @@ 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; 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; 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..ce34a84d4 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))] @@ -41,26 +45,26 @@ pub(super) struct State { pub(super) 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 { @@ -154,22 +164,31 @@ impl Service<'_> { /// Acknowledge the current connector change and move to the next if present async fn ack_connector_change(&mut self, 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() { - debug!("ACK_CCI processed, next pending port: {:?}", next_port); - self.context - .broadcast_message(Event::UcsiCci(UsciChangeIndicator { - port: *next_port, - // False here because the OPM gets notified by the CCI, don't need a separate notification - notify_opm: false, - })) - .await; - } else { - debug!("ACK_CCI processed, no more pending ports"); - } - } else { + let Some(_current_port) = self.ucsi.pending_ports.pop_front() else { warn!("Received ACK_CCI with no pending connector changes"); - } + return; + }; + + let Some(next_port) = self.ucsi.pending_ports.front() else { + debug!("ACK_CCI processed, no more pending ports"); + return; + }; + + debug!("ACK_CCI processed, next pending port: {:?}", next_port); + let Ok(port) = self.lookup_port(*next_port) else { + error!("Invalid port ID in pending ports: {:?}", next_port); + return; + }; + + 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; self.set_cci_connector_change(cci); } @@ -188,7 +207,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,14 +251,14 @@ 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); } @@ -262,7 +281,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 +302,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 +328,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 +380,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"); From 1e820b18c47636d695948f6c1385e796345c7399 Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Thu, 14 May 2026 13:35:21 -0700 Subject: [PATCH 78/79] Bump in-tree mctp-rs to dymk/mctp-rs @ 1b8b7f5 (#844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Bump in-tree mctp-rs to dymk/mctp-rs @ 1b8b7f5 Bumps the in-tree `mctp-rs/` source from `3d941ba` (current `v0.2.0` baseline imported in [#823](https://github.com/OpenDevicePartnership/embedded-services/pull/823)) to `1b8b7f5` (the head of `dymk/mctp-rs main`). This brings the in-tree copy to parity with the dymk fork's main, after which `dymk/mctp-rs` will be archived. The high-level `MctpPacketContext::deserialize_packet`/`serialize_packet` API signatures are **identical** between `3d941ba` and `1b8b7f5` (only internal `MctpMedium::deserialize/serialize` cursor types changed). All 4 in-tree consumers (`uart-service`, `espi-service`, `embedded-service`, plus macro re-exports) use the high-level `MctpPacketContext` API, so **zero consumer source changes** were required. Consumer test counts are unchanged at 128. ## What's picked up - **`BufferEncoding` trait + `EncodingDecoder`/`EncodingEncoder` cursor wrappers** (`src/buffer_encoding.rs`, NEW) — stateless cross-medium encoding abstraction; threads through `MctpMedium::deserialize/serialize` internals - **`MctpSerialMedium` DSP0253 byte-stuffed serial framer with CRC-16/X-25 FCS** (`src/medium/serial.rs`, NEW; gated behind opt-in `serial` feature) - **`wire_size_of` chunk-sizing fix** in `src/serialize.rs` (correctly accounts for encoding overhead during packet chunking) - **`SerialEncoding` byte-stuffing implementation** (in `medium/serial.rs`) ## Source lineage ``` Source: dymk/mctp-rs main @ 1b8b7f5 Bumps from: 3d941ba (v0.2.0 baseline imported in #823) ``` ## Carry-forwards from #823 These edits are preserved across the bump: - Dropped `cargo-husky` from `[dev-dependencies]` (workspace-root build script incompatibility; the source at `1b8b7f5` still has cargo-husky declared but the in-tree copy must drop it) - Did NOT re-introduce per-crate `mctp-rs/rustfmt.toml` (workspace style governs; source at `1b8b7f5` has a `rustfmt.toml` for nightly rustfmt, but the workspace uses stable rustfmt with `max_width = 120`) - `[package.metadata.cargo-machete] ignored` block updated to add `"crc"` (now an opt-in optional dep behind the `serial` feature; cargo-machete would otherwise false-positive at default features) - Source reformatted under the workspace style (separate fmt-only commit; no semantic change) ## Test results | Suite | Pre-bump | Post-bump | Delta | |-------|---------:|----------:|------:| | Workspace total `passed` | 178 | 185 | +7 | | `mctp-rs` unit tests | ~47 | 54 | +7 | | `mctp-rs` doctests | 3 | 3 | 0 | | Consumer crates (sum) | 128 | 128 | **0** | `cargo test --workspace` exits 0. New mctp-rs tests come from `buffer_encoding.rs` cursor unit tests at default features. The 41+ `medium/serial.rs` golden-fixture tests live behind opt-in `--features serial` and are exercised by the `hack-clippy` cargo-hack feature-powerset job in `check.yml`. ## Cargo.lock churn - Workspace `Cargo.lock` regenerated via `cargo build --workspace`. `mctp-rs` entry remains source-less (path-pinned). - **NEW transitive entries in `Cargo.lock`:** `crc 3.4.0` and `crc-catalog 2.4.0` (both gated behind opt-in `serial`; neither is reachable from `cargo build --workspace` at default features). - `cargo tree -p mctp-rs` is **byte-identical** pre/post at default features (verified via `diff`). - Example lockfiles regenerated via `cd examples/ && cargo update -p mctp-rs`: `examples/std`, `examples/rt685s-evk`, `examples/pico-de-gallo` updated. `examples/rt633` does not depend on `mctp-rs` and is untouched. ## cargo-vet `cargo vet --locked` reports: `Vetting Succeeded (220 fully audited, 1 partially audited, 15 exempted)`. Added **one** new exemption for `crc 3.4.0` as `safe-to-deploy` in `supply-chain/config.toml` (alphabetically interleaved via `cargo vet fmt`). `crc-catalog 2.4.0` did not require a new exemption — already audited via `imports.lock` (OpenDevicePartnership-imported audit by Jerry Xie, Microsoft). ## CI matrix coverage (`hack-clippy`) `check.yml`'s `hack-clippy` job runs `cargo hack --feature-powerset --mutually-exclusive-features=log,defmt,defmt-timestamp-uptime` workspace-wide across `{stable,beta} × {x86_64-unknown-linux-gnu, thumbv8m.main-none-eabihf}`. Since `mctp-rs` is a workspace member, this matrix automatically exercises the new `serial` feature (16 feature combinations total). --------- Co-authored-by: Jerry Xie <139205137+jerrysxie@users.noreply.github.com> --- Cargo.lock | 5 +- examples/pico-de-gallo/Cargo.lock | 2 +- examples/rt685s-evk/Cargo.lock | 2 +- examples/std/Cargo.lock | 2 +- mctp-rs/Cargo.toml | 4 +- mctp-rs/src/buffer_encoding.rs | 248 ++++++++++ mctp-rs/src/deserialize.rs | 52 ++- mctp-rs/src/lib.rs | 35 +- mctp-rs/src/mctp_packet_context.rs | 28 +- mctp-rs/src/medium/mod.rs | 40 +- mctp-rs/src/medium/serial.rs | 716 +++++++++++++++++++++++++++++ mctp-rs/src/medium/smbus_espi.rs | 218 +++++---- mctp-rs/src/serialize.rs | 109 +++-- mctp-rs/src/test_util.rs | 24 +- supply-chain/config.toml | 4 + 15 files changed, 1310 insertions(+), 179 deletions(-) create mode 100644 mctp-rs/src/buffer_encoding.rs create mode 100644 mctp-rs/src/medium/serial.rs diff --git a/Cargo.lock b/Cargo.lock index cd2d69a3d..4f5c1a898 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -1302,6 +1302,7 @@ name = "mctp-rs" version = "0.1.0" dependencies = [ "bit-register", + "crc", "defmt 0.3.100", "embedded-batteries", "espi-device", diff --git a/examples/pico-de-gallo/Cargo.lock b/examples/pico-de-gallo/Cargo.lock index 8ac8715f9..375ba9bb9 100644 --- a/examples/pico-de-gallo/Cargo.lock +++ b/examples/pico-de-gallo/Cargo.lock @@ -619,7 +619,7 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#0d21aa34fd8691c6544533e6c72fe41824dc2fa8" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#09eda26a729738adbd177231600acdb981690375" dependencies = [ "bit-register", "bitflags", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index ab6776280..342fa1dc9 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -688,7 +688,7 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#0d21aa34fd8691c6544533e6c72fe41824dc2fa8" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#09eda26a729738adbd177231600acdb981690375" dependencies = [ "bit-register", "bitflags 2.11.1", diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 16c0a12bb..5d6227827 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -675,7 +675,7 @@ dependencies = [ [[package]] name = "espi-device" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#0d21aa34fd8691c6544533e6c72fe41824dc2fa8" +source = "git+https://github.com/OpenDevicePartnership/haf-ec-service#09eda26a729738adbd177231600acdb981690375" dependencies = [ "bit-register", "bitflags 2.11.1", diff --git a/mctp-rs/Cargo.toml b/mctp-rs/Cargo.toml index 7e5d382b7..7d16b1c5c 100644 --- a/mctp-rs/Cargo.toml +++ b/mctp-rs/Cargo.toml @@ -6,16 +6,18 @@ edition = "2024" [package.metadata.cargo-machete] # Optional deps gated by features — cargo-machete sees them as unused at # default features but they ARE consumed when the relevant feature is on. -ignored = ["embedded-batteries", "espi-device", "uuid"] +ignored = ["embedded-batteries", "espi-device", "uuid", "crc"] [features] default = [] espi = ["dep:espi-device"] defmt = ["dep:defmt", "embedded-batteries/defmt"] +serial = ["dep:crc"] [dependencies] espi-device = { git = "https://github.com/OpenDevicePartnership/haf-ec-service", optional = true } bit-register = { git = "https://github.com/OpenDevicePartnership/odp-utilities", package = "bit-register" } +crc = { version = "3.4", default-features = false, optional = true } num_enum = { version = "0.7.4", default-features = false } smbus-pec = "1.0.1" thiserror = { version = "2.0.16", default-features = false } diff --git a/mctp-rs/src/buffer_encoding.rs b/mctp-rs/src/buffer_encoding.rs new file mode 100644 index 000000000..96908e5a7 --- /dev/null +++ b/mctp-rs/src/buffer_encoding.rs @@ -0,0 +1,248 @@ +//! Stateless byte-level buffer-encoding transform for MCTP media. +//! +//! Most media (SMBus/eSPI) ship MCTP packets verbatim — wire bytes ARE +//! payload bytes. Some media (DSP0253 serial) need byte-stuffing: an +//! escape character expands certain payload bytes into 2-byte sequences +//! on the wire, and decode reverses that transform. +//! +//! [`BufferEncoding`] is the byte-stuffing layer ONLY. It is stateless: +//! [`write_byte`](BufferEncoding::write_byte) and +//! [`read_byte`](BufferEncoding::read_byte) are associated functions with +//! no `self` and no struct state. Higher-level framing concerns +//! (start/end delimiters, FCS / CRC) live on the medium type, not here. + +use core::marker::PhantomData; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EncodeError { + /// `wire_buf` did not have room for the encoded bytes (1 for plain, + /// up to 2 for an escape sequence). The caller should advance no + /// cursors and treat the encode as failed. + BufferFull, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DecodeError { + /// `wire_buf` was empty or ended mid-escape-sequence. Indicates the + /// caller asked to decode past the end of valid wire data. + PrematureEnd, + /// An escape byte was followed by a byte not in the medium's + /// accept-list (strict-XOR rule per RFC1662 §4.2 / DSP0253 §6.4). + /// The caller should reject the entire frame. Reachable via + /// `SerialEncoding` when the byte following an escape (`0x7D`) is + /// neither `0x5E` nor `0x5D`. + InvalidEscape, +} + +/// Stateless byte-stuffing transform. Implementors define how a single +/// logical (payload) byte maps to one or more wire bytes (encode) and +/// how a wire-byte prefix maps back to a single payload byte (decode). +/// +/// All methods are associated functions — there is no `self` and no +/// struct state. Callers own the buffers and the read/write cursors. +pub trait BufferEncoding { + /// Encode one logical payload byte into `wire_buf` starting at + /// index 0. Returns the number of wire bytes written (1 for plain, + /// 2 for an escape sequence). The caller advances their write + /// cursor by the returned count. + fn write_byte(wire_buf: &mut [u8], byte: u8) -> Result; + + /// Decode the next logical payload byte from `wire_buf` starting at + /// index 0. Returns `(decoded_byte, wire_bytes_consumed)`. The + /// caller advances their read cursor by `wire_bytes_consumed`. + fn read_byte(wire_buf: &[u8]) -> Result<(u8, usize), DecodeError>; + + /// Wire-byte footprint of `decoded` under this encoding. Must equal + /// the sum of `write_byte(_, b)` lengths for each `b` in `decoded`. + /// NO default impl: every encoding declares its sizing rule + /// explicitly. + fn wire_size_of(decoded: &[u8]) -> usize; +} + +/// No-op encoding: wire bytes ARE payload bytes. Used by media that do +/// not byte-stuff (SMBus/eSPI, test fixtures). +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PassthroughEncoding; + +impl BufferEncoding for PassthroughEncoding { + fn write_byte(wire_buf: &mut [u8], byte: u8) -> Result { + match wire_buf.first_mut() { + Some(slot) => { + *slot = byte; + Ok(1) + } + None => Err(EncodeError::BufferFull), + } + } + + fn read_byte(wire_buf: &[u8]) -> Result<(u8, usize), DecodeError> { + match wire_buf.first() { + Some(&byte) => Ok((byte, 1)), + None => Err(DecodeError::PrematureEnd), + } + } + + fn wire_size_of(decoded: &[u8]) -> usize { + decoded.len() + } +} + +/// Stateful cursor over a `&[u8]` wire buffer that reads decoded bytes +/// through `E: BufferEncoding`. Constructed by [`MctpMedium::deserialize`] +/// and handed to higher layers so they cannot bypass the encoding by +/// slicing the underlying buffer directly. +/// +/// [`MctpMedium::deserialize`]: crate::medium::MctpMedium::deserialize +pub struct EncodingDecoder<'buf, E: BufferEncoding> { + buf: &'buf [u8], + wire_pos: usize, + _phantom: PhantomData, +} + +impl<'buf, E: BufferEncoding> EncodingDecoder<'buf, E> { + /// Wrap a wire-byte buffer for stateful encoding-mediated reads. + pub fn new(buf: &'buf [u8]) -> Self { + Self { + buf, + wire_pos: 0, + _phantom: PhantomData, + } + } + + /// Read one decoded byte. Advances the wire cursor by the encoding's + /// per-byte wire footprint. Returns `DecodeError::PrematureEnd` when + /// the wire buffer is exhausted (or ends mid-escape) and + /// `DecodeError::InvalidEscape` for malformed escape sequences. + pub fn read(&mut self) -> Result { + let (byte, n) = E::read_byte(&self.buf[self.wire_pos..])?; + self.wire_pos += n; + Ok(byte) + } +} + +/// Stateful cursor over a `&mut [u8]` wire buffer that writes decoded +/// bytes through `E: BufferEncoding`. Constructed by +/// [`MctpMedium::serialize`] and handed to the caller's `message_writer` +/// closure so the closure cannot bypass the encoding. +/// +/// [`MctpMedium::serialize`]: crate::medium::MctpMedium::serialize +pub struct EncodingEncoder<'buf, E: BufferEncoding> { + buf: &'buf mut [u8], + wire_pos: usize, + _phantom: PhantomData, +} + +impl<'buf, E: BufferEncoding> EncodingEncoder<'buf, E> { + /// Wrap a wire-byte buffer for stateful encoding-mediated writes. + pub fn new(buf: &'buf mut [u8]) -> Self { + Self { + buf, + wire_pos: 0, + _phantom: PhantomData, + } + } + + /// Write one decoded byte. Advances the wire cursor by the encoding's + /// per-byte wire footprint. Returns `EncodeError::BufferFull` when + /// the underlying wire buffer cannot fit the encoded representation. + pub fn write(&mut self, byte: u8) -> Result<(), EncodeError> { + let n = E::write_byte(&mut self.buf[self.wire_pos..], byte)?; + self.wire_pos += n; + Ok(()) + } + + /// Write a contiguous slice of decoded bytes; aborts on the first + /// encode error. Equivalent to a `for &b in bytes { self.write(b)? }` + /// loop, but more concise at call sites that just splat a byte slice. + pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), EncodeError> { + for &b in bytes { + self.write(b)?; + } + Ok(()) + } + + /// Wire bytes written so far (the size of the produced wire frame). + pub fn wire_position(&self) -> usize { + self.wire_pos + } + + /// Wire bytes remaining in the underlying buffer. + pub fn remaining_wire(&self) -> usize { + self.buf.len() - self.wire_pos + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn passthrough_write_byte_writes_one_byte() { + let mut buf = [0u8; 4]; + let n = PassthroughEncoding::write_byte(&mut buf, 0xAB).unwrap(); + assert_eq!(n, 1); + assert_eq!(buf, [0xAB, 0, 0, 0]); + } + + #[test] + fn passthrough_write_byte_full_buffer() { + let mut buf = []; + let err = PassthroughEncoding::write_byte(&mut buf, 0xAB).unwrap_err(); + assert_eq!(err, EncodeError::BufferFull); + } + + #[test] + fn passthrough_read_byte_reads_one_byte() { + let buf = [0xAB, 0xCD]; + let (b, n) = PassthroughEncoding::read_byte(&buf).unwrap(); + assert_eq!(b, 0xAB); + assert_eq!(n, 1); + } + + #[test] + fn passthrough_read_byte_premature_end() { + let buf = []; + let err = PassthroughEncoding::read_byte(&buf).unwrap_err(); + assert_eq!(err, DecodeError::PrematureEnd); + } + + #[test] + fn decoder_reads_all_bytes_via_passthrough() { + let buf = [0xAA, 0xBB, 0xCC, 0xDD]; + let mut decoder = EncodingDecoder::::new(&buf); + assert_eq!(decoder.read().unwrap(), 0xAA); + assert_eq!(decoder.read().unwrap(), 0xBB); + assert_eq!(decoder.read().unwrap(), 0xCC); + assert_eq!(decoder.read().unwrap(), 0xDD); + assert_eq!(decoder.read().unwrap_err(), DecodeError::PrematureEnd); + } + + #[test] + fn encoder_writes_all_bytes_via_passthrough() { + let mut buf = [0u8; 4]; + { + let mut encoder = EncodingEncoder::::new(&mut buf); + assert_eq!(encoder.wire_position(), 0); + assert_eq!(encoder.remaining_wire(), 4); + encoder.write(0x11).unwrap(); + encoder.write(0x22).unwrap(); + encoder.write(0x33).unwrap(); + encoder.write(0x44).unwrap(); + assert_eq!(encoder.wire_position(), 4); + assert_eq!(encoder.remaining_wire(), 0); + assert_eq!(encoder.write(0x55).unwrap_err(), EncodeError::BufferFull); + } + assert_eq!(buf, [0x11, 0x22, 0x33, 0x44]); + } + + #[test] + fn passthrough_wire_size_of_returns_input_len() { + assert_eq!(PassthroughEncoding::wire_size_of(&[]), 0); + assert_eq!(PassthroughEncoding::wire_size_of(&[0xAB]), 1); + let buf = [0u8; 64]; + assert_eq!(PassthroughEncoding::wire_size_of(&buf), 64); + } +} diff --git a/mctp-rs/src/deserialize.rs b/mctp-rs/src/deserialize.rs index 0a0ddd630..f1e0b3f59 100644 --- a/mctp-rs/src/deserialize.rs +++ b/mctp-rs/src/deserialize.rs @@ -1,25 +1,45 @@ use crate::{ - MctpMessageBuffer, MctpPacketError, error::MctpPacketResult, mctp_transport_header::MctpTransportHeader, + MctpMessageBuffer, MctpPacketError, + buffer_encoding::{DecodeError, EncodingDecoder}, + error::MctpPacketResult, + mctp_transport_header::MctpTransportHeader, medium::MctpMedium, }; +pub(crate) fn map_decode_err( + e: DecodeError, + on_premature: &'static str, + on_escape: &'static str, +) -> MctpPacketError { + match e { + DecodeError::PrematureEnd => MctpPacketError::HeaderParseError(on_premature), + DecodeError::InvalidEscape => MctpPacketError::HeaderParseError(on_escape), + } +} + pub(crate) fn parse_transport_header( - packet: &[u8], -) -> MctpPacketResult<(MctpTransportHeader, &[u8]), M> { - if packet.len() < 4 { - return Err(MctpPacketError::HeaderParseError( - "Packet is too small, cannot parse transport header", - )); + decoder: &mut EncodingDecoder<'_, M::Encoding>, +) -> MctpPacketResult { + // Read 4 decoded bytes through the encoding-aware decoder. We do NOT + // pre-check `decoder.remaining_wire() < 4` because for stuffing + // encodings wire length is not decoded length; PrematureEnd from + // `read()` is the canonical "ran out of bytes while decoding the + // header" signal — it correctly handles BOTH the Passthrough case + // (wire < 4) AND the stuffing case (wire >= 4 but yields < 4 decoded + // bytes). + let mut header_bytes = [0u8; 4]; + for slot in header_bytes.iter_mut() { + *slot = decoder.read().map_err(|e| { + map_decode_err::( + e, + "Packet is too small, cannot parse transport header", + "Invalid encoding escape sequence in transport header", + ) + })?; } - let transport_header_value = u32::from_be_bytes( - packet[0..4] - .try_into() - .map_err(|_| MctpPacketError::HeaderParseError("Packet is too small, cannot parse transport header"))?, - ); - let transport_header = MctpTransportHeader::try_from(transport_header_value) - .map_err(|_| MctpPacketError::HeaderParseError("Invalid transport header"))?; - let packet = &packet[4..]; - Ok((transport_header, packet)) + let transport_header_value = u32::from_be_bytes(header_bytes); + MctpTransportHeader::try_from(transport_header_value) + .map_err(|_| MctpPacketError::HeaderParseError("Invalid transport header")) } pub(crate) fn parse_message_body( diff --git a/mctp-rs/src/lib.rs b/mctp-rs/src/lib.rs index 718fc22ec..7fa1809b3 100644 --- a/mctp-rs/src/lib.rs +++ b/mctp-rs/src/lib.rs @@ -14,10 +14,10 @@ //! # use mctp_rs::*; //! # #[derive(Debug, Clone, Copy)] struct MyMedium { mtu: usize } //! # #[derive(Debug, Clone, Copy)] struct MyMediumFrame { packet_size: usize } -//! # impl MctpMedium for MyMedium { type Frame=MyMediumFrame; type Error=&'static str; type ReplyContext=(); +//! # impl MctpMedium for MyMedium { type Frame=MyMediumFrame; type Error=&'static str; type ReplyContext=(); type Encoding=PassthroughEncoding; //! # fn max_message_body_size(&self)->usize{self.mtu} -//! # fn deserialize<'b>(&self,p:&'b [u8])->MctpPacketResult<(Self::Frame,&'b [u8]),Self>{Ok((MyMediumFrame{packet_size:p.len()},p))} -//! # fn serialize<'b,F>(&self,_:Self::ReplyContext,b:&'b mut [u8],w:F)->MctpPacketResult<&'b [u8],Self> where F: for<'a> FnOnce(&'a mut [u8])->MctpPacketResult{let n=w(b)?;Ok(&b[..n])}} +//! # fn deserialize<'b>(&self,p:&'b [u8])->MctpPacketResult<(Self::Frame,EncodingDecoder<'b,Self::Encoding>),Self>{Ok((MyMediumFrame{packet_size:p.len()},EncodingDecoder::new(p)))} +//! # fn serialize<'b,F>(&self,_:Self::ReplyContext,b:&'b mut [u8],w:F)->MctpPacketResult<&'b [u8],Self> where F: for<'a> FnOnce(&mut EncodingEncoder<'a,Self::Encoding>)->MctpPacketResult<(),Self>{let n={let mut e=EncodingEncoder::::new(b);w(&mut e)?;e.wire_position()};Ok(&b[..n])}} //! # impl MctpMediumFrame for MyMediumFrame { fn packet_size(&self)->usize{self.packet_size} fn reply_context(&self)->(){()}} //! let mut assembly_buffer = [0u8; 1024]; //! let medium = MyMedium { mtu: 256 }; @@ -59,9 +59,9 @@ //! # use mctp_rs::*; //! # #[derive(Debug, Clone, Copy)] struct MyMedium { mtu: usize } //! # #[derive(Debug, Clone, Copy)] struct MyMediumFrame { packet_size: usize } -//! # impl MctpMedium for MyMedium { type Frame=MyMediumFrame; type Error=&'static str; type ReplyContext=(); fn max_message_body_size(&self)->usize{self.mtu} -//! # fn deserialize<'b>(&self,p:&'b [u8])->MctpPacketResult<(Self::Frame,&'b [u8]),Self>{Ok((MyMediumFrame{packet_size:p.len()},p))} -//! # fn serialize<'b,F>(&self,_:Self::ReplyContext,b:&'b mut [u8],w:F)->MctpPacketResult<&'b [u8],Self> where F: for<'a> FnOnce(&'a mut [u8])->MctpPacketResult{let n=w(b)?;Ok(&b[..n])}} +//! # impl MctpMedium for MyMedium { type Frame=MyMediumFrame; type Error=&'static str; type ReplyContext=(); type Encoding=PassthroughEncoding; fn max_message_body_size(&self)->usize{self.mtu} +//! # fn deserialize<'b>(&self,p:&'b [u8])->MctpPacketResult<(Self::Frame,EncodingDecoder<'b,Self::Encoding>),Self>{Ok((MyMediumFrame{packet_size:p.len()},EncodingDecoder::new(p)))} +//! # fn serialize<'b,F>(&self,_:Self::ReplyContext,b:&'b mut [u8],w:F)->MctpPacketResult<&'b [u8],Self> where F: for<'a> FnOnce(&mut EncodingEncoder<'a,Self::Encoding>)->MctpPacketResult<(),Self>{let n={let mut e=EncodingEncoder::::new(b);w(&mut e)?;e.wire_position()};Ok(&b[..n])}} //! # impl MctpMediumFrame for MyMediumFrame { fn packet_size(&self)->usize{self.packet_size} fn reply_context(&self)->(){()}} //! let mut buf = [0u8; 1024]; //! let mut ctx = MctpPacketContext::new(MyMedium { mtu: 64 }, &mut buf); @@ -109,6 +109,7 @@ //! type Frame = MyMediumFrame; //! type Error = &'static str; //! type ReplyContext = (); +//! type Encoding = PassthroughEncoding; //! //! fn max_message_body_size(&self) -> usize { //! self.mtu @@ -117,13 +118,13 @@ //! fn deserialize<'buf>( //! &self, //! packet: &'buf [u8], -//! ) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { +//! ) -> MctpPacketResult<(Self::Frame, EncodingDecoder<'buf, Self::Encoding>), Self> { //! // Strip/validate transport headers as needed for your bus and return MCTP payload slice //! Ok(( //! MyMediumFrame { //! packet_size: packet.len(), //! }, -//! packet, +//! EncodingDecoder::new(packet), //! )) //! } //! @@ -134,11 +135,17 @@ //! message_writer: F, //! ) -> MctpPacketResult<&'buf [u8], Self> //! where -//! F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, +//! F: for<'a> FnOnce( +//! &mut EncodingEncoder<'a, Self::Encoding>, +//! ) -> MctpPacketResult<(), Self>, //! { //! // Prepend transport headers as needed, then ask the writer to write MCTP payload -//! let message_len = message_writer(buffer)?; -//! Ok(&buffer[..message_len]) +//! let written = { +//! let mut encoder = EncodingEncoder::::new(buffer); +//! message_writer(&mut encoder)?; +//! encoder.wire_position() +//! }; +//! Ok(&buffer[..written]) //! } //! } //! @@ -152,6 +159,7 @@ //! } //! ``` +mod buffer_encoding; mod deserialize; mod endpoint_id; pub mod error; @@ -167,11 +175,16 @@ mod serialize; #[cfg(test)] mod test_util; +pub use buffer_encoding::{ + BufferEncoding, DecodeError, EncodeError, EncodingDecoder, EncodingEncoder, PassthroughEncoding, +}; pub use endpoint_id::EndpointId; pub use error::{MctpPacketError, MctpPacketResult}; pub use mctp_message_tag::MctpMessageTag; pub use mctp_packet_context::{MctpPacketContext, MctpReplyContext}; pub use mctp_sequence_number::MctpSequenceNumber; +#[cfg(feature = "serial")] +pub use medium::serial::{CONST_MTU, EC_EID, MctpSerialMedium, MctpSerialMediumFrame, SP_EID, SerialEncoding}; pub use medium::*; pub use message_type::*; diff --git a/mctp-rs/src/mctp_packet_context.rs b/mctp-rs/src/mctp_packet_context.rs index f22af6fd2..893b333c6 100644 --- a/mctp-rs/src/mctp_packet_context.rs +++ b/mctp-rs/src/mctp_packet_context.rs @@ -1,6 +1,6 @@ use crate::{ MctpMessage, MctpMessageHeaderTrait, MctpMessageTrait, MctpPacketError, - deserialize::{parse_message_body, parse_transport_header}, + deserialize::{map_decode_err, parse_message_body, parse_transport_header}, endpoint_id::EndpointId, error::{MctpPacketResult, ProtocolError}, mctp_message_tag::MctpMessageTag, @@ -40,8 +40,8 @@ impl<'buf, M: MctpMedium> MctpPacketContext<'buf, M> { } pub fn deserialize_packet(&mut self, packet: &[u8]) -> MctpPacketResult>, M> { - let (medium_frame, packet) = self.medium.deserialize(packet)?; - let (transport_header, packet) = parse_transport_header::(packet)?; + let (medium_frame, mut decoder) = self.medium.deserialize(packet)?; + let transport_header = parse_transport_header::(&mut decoder)?; let mut state = match self.assembly_state { AssemblyState::Idle => { @@ -100,16 +100,28 @@ impl<'buf, M: MctpMedium> MctpPacketContext<'buf, M> { )); } let packet_size = packet_size - 4; // to account for the transport header - if packet.len() < packet_size { - return Err(MctpPacketError::HeaderParseError("packet.len() < packet_size")); - } - // Check bounds to prevent buffer overflow + // Check assembly buffer bounds (decoded bytes destination) if buffer_idx + packet_size > self.packet_assembly_buffer.len() { return Err(MctpPacketError::HeaderParseError( "packet assembly buffer overflow - insufficient space", )); } - self.packet_assembly_buffer[buffer_idx..buffer_idx + packet_size].copy_from_slice(&packet[..packet_size]); + // Decode `packet_size` payload bytes from the (possibly stuffed) wire + // buffer into the assembly buffer one byte at a time via the + // medium-supplied decoder. We do NOT pre-check + // `decoder.remaining_wire() < packet_size` because for stuffing + // encodings wire length is not decoded length; PrematureEnd from + // `read()` is the canonical "ran out of bytes while decoding the + // body" signal. + for i in 0..packet_size { + self.packet_assembly_buffer[buffer_idx + i] = decoder.read().map_err(|e| { + map_decode_err::( + e, + "packet body too short to extract expected decoded bytes", + "Invalid encoding escape sequence in packet body", + ) + })?; + } state.packet_assembly_buffer_index += packet_size; let message = if transport_header.end_of_message == 1 { diff --git a/mctp-rs/src/medium/mod.rs b/mctp-rs/src/medium/mod.rs index fb49a9adc..693deda46 100644 --- a/mctp-rs/src/medium/mod.rs +++ b/mctp-rs/src/medium/mod.rs @@ -1,8 +1,14 @@ -use crate::error::MctpPacketResult; +use crate::{ + buffer_encoding::{BufferEncoding, EncodingDecoder, EncodingEncoder}, + error::MctpPacketResult, +}; pub mod smbus_espi; mod util; +#[cfg(feature = "serial")] +pub mod serial; + pub trait MctpMedium: Sized { /// the medium specific header and trailer for the packet type Frame: MctpMediumFrame; @@ -13,14 +19,34 @@ pub trait MctpMedium: Sized { // the type used for the data needed to send a reply to a request type ReplyContext: core::fmt::Debug + Copy + Clone + PartialEq + Eq; + /// the byte-stuffing transform used by this medium when (de)serializing + /// wire bytes. Stateless — see [`crate::buffer_encoding`]. Most media + /// use [`PassthroughEncoding`](crate::buffer_encoding::PassthroughEncoding) + /// (no transform); media that need byte-stuffing (e.g., DSP0253 serial) + /// supply their own impl. + type Encoding: BufferEncoding; + /// the maximum transmission unit for the medium fn max_message_body_size(&self) -> usize; - /// deserialize the packet into the medium specific header and remainder of the packet - - /// this includes the mctp transport header, and mctp packet payload - fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self>; - - /// serialize the packet into the medium specific header and the payload + /// Deserialize a packet into the medium-specific header (frame) and an + /// [`EncodingDecoder`] that wraps the inner stuffed-region bytes. + /// Higher layers (e.g., `parse_transport_header`, the payload copy + /// loop in `MctpPacketContext`) read decoded bytes through the + /// returned decoder and physically cannot bypass the medium's + /// encoding by slicing the underlying buffer directly. + fn deserialize<'buf>( + &self, + packet: &'buf [u8], + ) -> MctpPacketResult<(Self::Frame, EncodingDecoder<'buf, Self::Encoding>), Self>; + + /// Serialize a packet by allowing the caller's `message_writer` + /// closure to write decoded bytes into the medium's stuffed region + /// through an [`EncodingEncoder`]. The medium owns its outer framing + /// (e.g., SMBus header + PEC, DSP0253 start/end flags + FCS) and + /// inspects the encoder's + /// [`wire_position`](EncodingEncoder::wire_position) after the + /// closure returns to size headers/trailers and compute checksums. fn serialize<'buf, F>( &self, reply_context: Self::ReplyContext, @@ -28,7 +54,7 @@ pub trait MctpMedium: Sized { message_writer: F, ) -> MctpPacketResult<&'buf [u8], Self> where - F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult; + F: for<'a> FnOnce(&mut EncodingEncoder<'a, Self::Encoding>) -> MctpPacketResult<(), Self>; } pub trait MctpMediumFrame: Clone + Copy { diff --git a/mctp-rs/src/medium/serial.rs b/mctp-rs/src/medium/serial.rs new file mode 100644 index 000000000..9075ba1f3 --- /dev/null +++ b/mctp-rs/src/medium/serial.rs @@ -0,0 +1,716 @@ +//! DSP0253 byte-stuffed serial medium for MCTP. +//! +//! Two-layer split: +//! - [`SerialEncoding`]: stateless byte-stuffing (0x7E, 0x7D escape pair). +//! - [`MctpSerialMedium`]: framing (revision byte, byte_count, body, FCS-16, end-flag). +//! +//! Both layers are gated behind the `serial` cargo feature. + +use crate::{ + MctpPacketError, + buffer_encoding::{BufferEncoding, DecodeError, EncodeError, EncodingDecoder, EncodingEncoder}, + error::MctpPacketResult, + medium::{MctpMedium, MctpMediumFrame}, +}; + +/// DSP0253 byte-stuffing transform. Stateless ZST. +/// +/// Encode: `0x7E -> [0x7D, 0x5E]`, `0x7D -> [0x7D, 0x5D]`, any other +/// byte -> `[b]`. +/// Decode: `0x7D 0x5E -> 0x7E`, `0x7D 0x5D -> 0x7D`, `0x7D ` -> +/// `InvalidEscape`. +/// +/// Raw `0x7E` in the wire stream is NOT rejected here — that's a +/// framing concern owned by `MctpSerialMedium::deserialize`, which +/// checks the body region for stray flags. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SerialEncoding; + +impl BufferEncoding for SerialEncoding { + fn write_byte(wire_buf: &mut [u8], byte: u8) -> Result { + match byte { + 0x7E => { + if wire_buf.len() < 2 { + return Err(EncodeError::BufferFull); + } + wire_buf[0] = 0x7D; + wire_buf[1] = 0x5E; + Ok(2) + } + 0x7D => { + if wire_buf.len() < 2 { + return Err(EncodeError::BufferFull); + } + wire_buf[0] = 0x7D; + wire_buf[1] = 0x5D; + Ok(2) + } + b => match wire_buf.first_mut() { + Some(slot) => { + *slot = b; + Ok(1) + } + None => Err(EncodeError::BufferFull), + }, + } + } + + fn read_byte(wire_buf: &[u8]) -> Result<(u8, usize), DecodeError> { + match wire_buf.first().copied() { + None => Err(DecodeError::PrematureEnd), + Some(0x7D) => match wire_buf.get(1).copied() { + None => Err(DecodeError::PrematureEnd), + Some(0x5E) => Ok((0x7E, 2)), + Some(0x5D) => Ok((0x7D, 2)), + Some(_) => Err(DecodeError::InvalidEscape), + }, + // Raw 0x7E falls through here as a 1-byte read; the framing + // layer (`MctpSerialMedium::deserialize`) rejects bare + // 0x7E inside the body region. + Some(b) => Ok((b, 1)), + } + } + + fn wire_size_of(decoded: &[u8]) -> usize { + decoded + .iter() + .map(|&b| if b == 0x7E || b == 0x7D { 2 } else { 1 }) + .sum() + } +} + +/// SP MCTP endpoint id per CONTEXT D-D-06. +pub const SP_EID: crate::endpoint_id::EndpointId = crate::endpoint_id::EndpointId::Id(0x08); +/// EC MCTP endpoint id per CONTEXT D-D-06. +pub const EC_EID: crate::endpoint_id::EndpointId = crate::endpoint_id::EndpointId::Id(0x0A); +/// Maximum DSP0253 packet body size (DECODED bytes, before stuffing). +pub const CONST_MTU: usize = 251; + +const SERIAL_REVISION: u8 = 0x01; +const END_FLAG: u8 = 0x7E; +/// Header bytes: revision + byte_count (decoded body byte count). +const HEADER_LEN: usize = 2; +/// Worst-case trailer wire bytes: 2 stuffed FCS bytes (each may +/// expand 1 -> 2) + 1 end-flag. +const MAX_TRAILER_WIRE: usize = 5; + +// CRC-16/X-25 per DSP0253 §8 (poly 0x1021, init 0xFFFF, refin/refout, +// xorout 0xFFFF). Algorithm catalog entry locked in CONTEXT D-D-02. +// FCS bytes on the wire are MSB-first per DSP0253 §5.2 (overrides +// RFC1662's LSB-first PPP convention). +const FCS_ALGO: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpSerialMediumFrame { + pub revision: u8, + /// DECODED body byte count per DSP0253 §6.2 (NOT the wire byte + /// count). Cap = `CONST_MTU` = 251; max u8 = 255, fits comfortably. + pub byte_count: u8, + pub fcs: u16, +} + +impl MctpMediumFrame for MctpSerialMediumFrame { + fn packet_size(&self) -> usize { + // packet_size is the DECODED body byte count — the contract + // used by `MctpPacketContext::deserialize_packet`, which then + // subtracts 4 for the transport header. + self.byte_count as usize + } + + fn reply_context(&self) {} +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MctpSerialMedium; + +impl MctpMedium for MctpSerialMedium { + type Frame = MctpSerialMediumFrame; + type Error = &'static str; + type ReplyContext = (); + type Encoding = SerialEncoding; + + fn max_message_body_size(&self) -> usize { + CONST_MTU + } + + fn deserialize<'buf>( + &self, + packet: &'buf [u8], + ) -> MctpPacketResult<(Self::Frame, EncodingDecoder<'buf, Self::Encoding>), Self> { + // Minimum frame: 2 header + 0 body + 2 FCS (unstuffed) + 1 end-flag = 5 bytes. + if packet.len() < HEADER_LEN + 3 { + return Err(MctpPacketError::MediumError("packet too short for serial frame")); + } + let revision = packet[0]; + if revision != SERIAL_REVISION { + return Err(MctpPacketError::MediumError("unsupported serial revision")); + } + let byte_count = packet[1]; + if (byte_count as usize) > CONST_MTU { + return Err(MctpPacketError::MediumError("byte_count exceeds MTU")); + } + + // Single forward walk: un-stuff body bytes (count must equal + // `byte_count`), un-stuff 2 FCS bytes, expect end-flag, compare + // CRC. + let body_wire_start = HEADER_LEN; + let mut decoded = [0u8; CONST_MTU]; + let mut decoded_len = 0usize; + let mut wire_pos = 0usize; // offset from body_wire_start + + while decoded_len < byte_count as usize { + let (b, n) = SerialEncoding::read_byte(&packet[body_wire_start + wire_pos..]).map_err(|e| match e { + DecodeError::PrematureEnd => MctpPacketError::MediumError("premature end in body"), + DecodeError::InvalidEscape => MctpPacketError::MediumError("invalid escape in body"), + })?; + if b == END_FLAG && n == 1 { + // Bare (unstuffed) 0x7E inside the body region is a + // protocol error (MEDIUM-05). A decoded 0x7E whose wire + // representation was the stuffed pair `0x7D 0x5E` + // (n==2) is a legitimate payload byte and is kept. + return Err(MctpPacketError::MediumError("unexpected 0x7E in body")); + } + decoded[decoded_len] = b; + decoded_len += 1; + wire_pos += n; + } + let body_wire_end = body_wire_start + wire_pos; + + // Un-stuff 2 FCS bytes (DSP0253 §7.1 stuffing applies to FCS). + let (fcs_msb, n_msb) = SerialEncoding::read_byte(&packet[body_wire_end..]) + .map_err(|_| MctpPacketError::MediumError("invalid escape in fcs"))?; + let (fcs_lsb, n_lsb) = SerialEncoding::read_byte(&packet[body_wire_end + n_msb..]) + .map_err(|_| MctpPacketError::MediumError("invalid escape in fcs"))?; + let trailer_pos = body_wire_end + n_msb + n_lsb; + + if trailer_pos >= packet.len() || packet[trailer_pos] != END_FLAG { + return Err(MctpPacketError::MediumError("missing end flag")); + } + if trailer_pos + 1 != packet.len() { + return Err(MctpPacketError::MediumError("trailing bytes after end flag")); + } + + // FCS-16/X-25 over un-stuffed (revision || byte_count || decoded body). + let mut digest = FCS_ALGO.digest(); + digest.update(&[revision, byte_count]); + digest.update(&decoded[..decoded_len]); + let computed_fcs = digest.finalize(); + // DSP0253 §5.2: MSB first on wire. + let wire_fcs = u16::from_be_bytes([fcs_msb, fcs_lsb]); + if wire_fcs != computed_fcs { + return Err(MctpPacketError::MediumError("fcs mismatch")); + } + + Ok(( + MctpSerialMediumFrame { + revision, + byte_count, + fcs: wire_fcs, + }, + EncodingDecoder::::new(&packet[body_wire_start..body_wire_end]), + )) + } + + fn serialize<'buf, F>( + &self, + _reply_context: Self::ReplyContext, + buffer: &'buf mut [u8], + message_writer: F, + ) -> MctpPacketResult<&'buf [u8], Self> + where + F: for<'a> FnOnce(&mut EncodingEncoder<'a, Self::Encoding>) -> MctpPacketResult<(), Self>, + { + if buffer.len() < HEADER_LEN + MAX_TRAILER_WIRE { + return Err(MctpPacketError::MediumError("buffer too small for serial frame")); + } + let buffer_len = buffer.len(); + + // Run closure over body region (reserve worst-case 5-byte + // trailer). The encoder stuffs body bytes via + // `SerialEncoding::write_byte` automatically. + let body_wire_len = { + let body_buf = &mut buffer[HEADER_LEN..buffer_len - MAX_TRAILER_WIRE]; + let mut encoder = EncodingEncoder::::new(body_buf); + message_writer(&mut encoder)?; + encoder.wire_position() + }; + + // Re-decode body to recover DECODED bytes + decoded count for + // `byte_count` and FCS. CONTEXT D-B-02 acknowledges the + // double-walk; ~250 bytes max, no_std, cheap. + let mut decoded = [0u8; CONST_MTU]; + let mut decoded_len = 0usize; + let mut wire_pos = 0usize; + while wire_pos < body_wire_len { + let (b, n) = SerialEncoding::read_byte(&buffer[HEADER_LEN + wire_pos..HEADER_LEN + body_wire_len]) + .map_err(|_| MctpPacketError::MediumError("internal: failed to re-decode body"))?; + if decoded_len >= CONST_MTU { + return Err(MctpPacketError::MediumError("body exceeds MTU")); + } + decoded[decoded_len] = b; + decoded_len += 1; + wire_pos += n; + } + // Should not fire — `EncodingEncoder::write` returns + // `BufferFull` long before decoded_len could exceed 251. + if decoded_len > u8::MAX as usize { + return Err(MctpPacketError::MediumError("body exceeds byte_count u8 cap")); + } + let byte_count = decoded_len as u8; + + // FCS-16/X-25 over un-stuffed (revision || byte_count || decoded body). + let mut digest = FCS_ALGO.digest(); + digest.update(&[SERIAL_REVISION, byte_count]); + digest.update(&decoded[..decoded_len]); + let fcs = digest.finalize(); + // DSP0253 §5.2: MSB first on wire. + let [fcs_msb, fcs_lsb] = fcs.to_be_bytes(); + + // Header: revision + byte_count emitted directly (NOT stuffed), + // matching `SmbusEspiMedium`'s header pattern. See PLAN + // note for the conformance caveat when byte_count + // happens to equal 0x7E or 0x7D — round-trips cleanly through + // this implementation's deserialize. + buffer[0] = SERIAL_REVISION; + buffer[1] = byte_count; + + // Stuff and write FCS bytes via SerialEncoding (DSP0253 §7.1 + + // CONTEXT D-B-02 — deserialize un-stuffs FCS, so serialize + // must stuff). + let fcs_start = HEADER_LEN + body_wire_len; + let n_msb = SerialEncoding::write_byte(&mut buffer[fcs_start..], fcs_msb) + .map_err(|_| MctpPacketError::MediumError("internal: failed to encode fcs"))?; + let n_lsb = SerialEncoding::write_byte(&mut buffer[fcs_start + n_msb..], fcs_lsb) + .map_err(|_| MctpPacketError::MediumError("internal: failed to encode fcs"))?; + let end_pos = fcs_start + n_msb + n_lsb; + + // End-flag is written directly (flags are NOT stuffed by + // definition). + buffer[end_pos] = END_FLAG; + + Ok(&buffer[..end_pos + 1]) + } +} + +#[cfg(test)] +mod encoding_tests { + use super::*; + use crate::buffer_encoding::EncodingDecoder; + + #[test] + fn write_byte_stuffs_7e() { + let mut buf = [0u8; 4]; + let n = SerialEncoding::write_byte(&mut buf, 0x7E).unwrap(); + assert_eq!(n, 2); + assert_eq!(&buf[..2], &[0x7D, 0x5E]); + } + + #[test] + fn write_byte_stuffs_7d() { + let mut buf = [0u8; 4]; + let n = SerialEncoding::write_byte(&mut buf, 0x7D).unwrap(); + assert_eq!(n, 2); + assert_eq!(&buf[..2], &[0x7D, 0x5D]); + } + + #[test] + fn write_byte_passthrough_plain() { + let mut buf = [0u8; 1]; + let n = SerialEncoding::write_byte(&mut buf, 0x41).unwrap(); + assert_eq!(n, 1); + assert_eq!(buf, [0x41]); + } + + #[test] + fn write_byte_full_buffer_plain() { + let mut buf = []; + assert_eq!( + SerialEncoding::write_byte(&mut buf, 0x41).unwrap_err(), + EncodeError::BufferFull + ); + } + + #[test] + fn write_byte_full_buffer_escape() { + let mut buf = [0u8; 1]; + assert_eq!( + SerialEncoding::write_byte(&mut buf, 0x7E).unwrap_err(), + EncodeError::BufferFull + ); + } + + #[test] + fn read_byte_unstuffs_7e() { + assert_eq!(SerialEncoding::read_byte(&[0x7D, 0x5E]).unwrap(), (0x7E, 2)); + } + + #[test] + fn read_byte_unstuffs_7d() { + assert_eq!(SerialEncoding::read_byte(&[0x7D, 0x5D]).unwrap(), (0x7D, 2)); + } + + #[test] + fn read_byte_passthrough_plain() { + assert_eq!(SerialEncoding::read_byte(&[0x41]).unwrap(), (0x41, 1)); + } + + #[test] + fn read_byte_raw_7e_passes_through() { + // Raw 0x7E is NOT rejected at the encoding layer — framing is + // the framing layer's concern. + assert_eq!(SerialEncoding::read_byte(&[0x7E]).unwrap(), (0x7E, 1)); + } + + #[test] + fn read_byte_premature_end_empty() { + assert_eq!(SerialEncoding::read_byte(&[]).unwrap_err(), DecodeError::PrematureEnd); + } + + #[test] + fn read_byte_premature_end_after_escape() { + assert_eq!( + SerialEncoding::read_byte(&[0x7D]).unwrap_err(), + DecodeError::PrematureEnd + ); + } + + #[test] + fn read_byte_invalid_escape() { + assert_eq!( + SerialEncoding::read_byte(&[0x7D, 0xAA]).unwrap_err(), + DecodeError::InvalidEscape + ); + } + + #[test] + fn wire_size_of_mixed() { + assert_eq!(SerialEncoding::wire_size_of(&[0x41, 0x7E, 0x42, 0x7D, 0x43]), 7); + } + + #[test] + fn wire_size_of_empty() { + assert_eq!(SerialEncoding::wire_size_of(&[]), 0); + } + + #[test] + fn roundtrip_all_byte_values() { + // 256-byte payload of every byte value, encoded into a 512-byte + // wire buffer (worst case is 2x expansion if every byte stuffs; + // actual expansion here is 256 + 2 = 258 wire bytes). + let mut decoded = [0u8; 256]; + for (i, slot) in decoded.iter_mut().enumerate() { + *slot = i as u8; + } + let mut wire = [0u8; 512]; + let mut wpos = 0usize; + for &b in &decoded { + wpos += SerialEncoding::write_byte(&mut wire[wpos..], b).unwrap(); + } + assert_eq!(wpos, SerialEncoding::wire_size_of(&decoded)); + let mut dec = EncodingDecoder::::new(&wire[..wpos]); + for &expected in &decoded { + assert_eq!(dec.read().unwrap(), expected); + } + assert_eq!(dec.read().unwrap_err(), DecodeError::PrematureEnd); + } +} + +#[cfg(test)] +mod fixtures { + //! Hand-authored DSP0253 serial frame fixtures (golden vectors). + //! + //! Layout per fixture (no leading flag — this implementation omits + //! the open `0x7E` per CONTEXT D-D-01; upstream UART layer supplies + //! it in Phase 27): + //! + //! `[REVISION=0x01, byte_count, ...stuffed body..., ...stuffed FCS-MSB..., ...stuffed + //! FCS-LSB..., 0x7E]` + //! + //! - Header bytes (REVISION, byte_count) are NOT stuffed (matches production serialize). + //! - Body bytes are stuffed per `SerialEncoding`. + //! - FCS-16/X-25 computed over un-stuffed `[REVISION, byte_count, ...decoded body...]`, emitted + //! MSB-first on wire (DSP0253 §5.2), each FCS byte then stuffed if equal to 0x7E or 0x7D. + //! - Trailing `0x7E` is the end-flag (not stuffed by definition). + + pub(crate) const FIXTURE_BASIC_RX: &[u8] = &[0x01, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, 0x6D, 0xA1, 0x7E]; + + pub(crate) const FIXTURE_PAYLOAD_CONTAINS_7E: &[u8] = &[0x01, 0x03, 0xAA, 0x7D, 0x5E, 0xCC, 0xFB, 0xE7, 0x7E]; + + pub(crate) const FIXTURE_PAYLOAD_CONTAINS_7D: &[u8] = &[0x01, 0x03, 0xAA, 0x7D, 0x5D, 0xCC, 0xD1, 0x8F, 0x7E]; + + pub(crate) const FIXTURE_PAYLOAD_CONTAINS_BOTH: &[u8] = + &[0x01, 0x03, 0x7D, 0x5E, 0x7D, 0x5D, 0x42, 0x50, 0x97, 0x7E]; + + /// 251-byte body `(0..251)` decoded; wire = 258 bytes after stuffing + /// the lone 0x7D (idx 125) and 0x7E (idx 126) inside the body. + pub(crate) const FIXTURE_MAX_MTU_FRAME: &[u8] = &[ + 0x01, 0xFB, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, + 0x7C, 0x7D, 0x5D, 0x7D, 0x5E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, + 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, + 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, + 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, + 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xF6, 0x07, 0x7E, + ]; + + pub(crate) const FIXTURE_EMPTY_PAYLOAD: &[u8] = &[0x01, 0x00, 0x16, 0x9F, 0x7E]; + + pub(crate) const FIXTURE_FCS_VALID: &[u8] = &[0x01, 0x03, 0x10, 0x20, 0x30, 0x76, 0xDB, 0x7E]; + + /// Same body as FCS_VALID but FCS-MSB byte XOR 0xFF (0x76 -> 0x89). + pub(crate) const FIXTURE_FCS_INVALID: &[u8] = &[0x01, 0x03, 0x10, 0x20, 0x30, 0x89, 0xDB, 0x7E]; + + /// byte_count=2 claims 2 decoded bytes; first body wire byte is + /// `0x7D 0xAA` (escape followed by non-`{0x5E,0x5D}`) -> rejected + /// as "invalid escape in body" before reaching FCS. + pub(crate) const FIXTURE_INVALID_ESCAPE: &[u8] = &[0x01, 0x02, 0x7D, 0xAA, 0x00, 0x00, 0x7E]; + + /// byte_count=3, body wire region is `[0xAA, 0x7E, 0xCC]` — the + /// raw 0x7E inside the body region is rejected before FCS. + pub(crate) const FIXTURE_PREMATURE_END_FLAG: &[u8] = &[0x01, 0x03, 0xAA, 0x7E, 0xCC, 0x00, 0x00, 0x7E]; +} + +#[cfg(test)] +mod medium_tests { + use super::{fixtures::*, *}; + + fn drain_decoder(mut dec: EncodingDecoder<'_, SerialEncoding>) -> ([u8; CONST_MTU], usize) { + let mut out = [0u8; CONST_MTU]; + let mut n = 0; + while let Ok(b) = dec.read() { + out[n] = b; + n += 1; + } + (out, n) + } + + #[test] + fn decode_basic_rx_succeeds() { + let (frame, dec) = MctpSerialMedium.deserialize(FIXTURE_BASIC_RX).unwrap(); + assert_eq!(frame.revision, 0x01); + assert_eq!(frame.byte_count, 4); + assert_eq!(frame.fcs, 0x6DA1); + let (decoded, n) = drain_decoder(dec); + assert_eq!(&decoded[..n], &[0xAA, 0xBB, 0xCC, 0xDD]); + } + + #[test] + fn decode_payload_contains_7e() { + let (frame, dec) = MctpSerialMedium.deserialize(FIXTURE_PAYLOAD_CONTAINS_7E).unwrap(); + assert_eq!(frame.byte_count, 3); + let (decoded, n) = drain_decoder(dec); + assert_eq!(&decoded[..n], &[0xAA, 0x7E, 0xCC]); + } + + #[test] + fn decode_payload_contains_7d() { + let (frame, dec) = MctpSerialMedium.deserialize(FIXTURE_PAYLOAD_CONTAINS_7D).unwrap(); + assert_eq!(frame.byte_count, 3); + let (decoded, n) = drain_decoder(dec); + assert_eq!(&decoded[..n], &[0xAA, 0x7D, 0xCC]); + } + + #[test] + fn decode_payload_contains_both() { + let (frame, dec) = MctpSerialMedium.deserialize(FIXTURE_PAYLOAD_CONTAINS_BOTH).unwrap(); + assert_eq!(frame.byte_count, 3); + let (decoded, n) = drain_decoder(dec); + assert_eq!(&decoded[..n], &[0x7E, 0x7D, 0x42]); + } + + #[test] + fn decode_max_mtu_frame() { + let (frame, dec) = MctpSerialMedium.deserialize(FIXTURE_MAX_MTU_FRAME).unwrap(); + assert_eq!(frame.byte_count as usize, CONST_MTU); + let (decoded, n) = drain_decoder(dec); + assert_eq!(n, CONST_MTU); + for (i, &b) in decoded[..n].iter().enumerate() { + assert_eq!(b, i as u8, "mismatch at idx {i}"); + } + } + + #[test] + fn decode_empty_payload() { + let (frame, dec) = MctpSerialMedium.deserialize(FIXTURE_EMPTY_PAYLOAD).unwrap(); + assert_eq!(frame.byte_count, 0); + let (_, n) = drain_decoder(dec); + assert_eq!(n, 0); + } + + #[test] + fn decode_fcs_valid() { + assert!(MctpSerialMedium.deserialize(FIXTURE_FCS_VALID).is_ok()); + } + + #[test] + fn decode_fcs_invalid_rejects() { + match MctpSerialMedium.deserialize(FIXTURE_FCS_INVALID) { + Err(crate::MctpPacketError::MediumError("fcs mismatch")) => {} + other => panic!("expected MediumError(\"fcs mismatch\"), got {:?}", other.err()), + } + } + + #[test] + fn decode_invalid_escape_rejects() { + match MctpSerialMedium.deserialize(FIXTURE_INVALID_ESCAPE) { + Err(crate::MctpPacketError::MediumError("invalid escape in body")) => {} + other => panic!( + "expected MediumError(\"invalid escape in body\"), got {:?}", + other.err() + ), + } + } + + #[test] + fn decode_premature_end_flag_rejects() { + match MctpSerialMedium.deserialize(FIXTURE_PREMATURE_END_FLAG) { + Err(crate::MctpPacketError::MediumError("unexpected 0x7E in body")) => {} + other => panic!( + "expected MediumError(\"unexpected 0x7E in body\"), got {:?}", + other.err() + ), + } + } + + fn fixture_roundtrip(wire: &[u8]) { + let m = MctpSerialMedium; + let (_frame, dec) = m.deserialize(wire).unwrap(); + let (decoded, n) = drain_decoder(dec); + let mut out = [0u8; 1024]; + let serialized = m + .serialize((), &mut out, |e| { + e.write_all(&decoded[..n]) + .map_err(|_| MctpPacketError::MediumError("write failed")) + }) + .unwrap(); + assert_eq!(serialized, wire); + } + + #[test] + fn fixture_roundtrip_basic_rx() { + fixture_roundtrip(FIXTURE_BASIC_RX); + } + + #[test] + fn fixture_roundtrip_payload_contains_7e() { + fixture_roundtrip(FIXTURE_PAYLOAD_CONTAINS_7E); + } + + #[test] + fn fixture_roundtrip_payload_contains_7d() { + fixture_roundtrip(FIXTURE_PAYLOAD_CONTAINS_7D); + } + + #[test] + fn fixture_roundtrip_payload_contains_both() { + fixture_roundtrip(FIXTURE_PAYLOAD_CONTAINS_BOTH); + } + + #[test] + fn fixture_roundtrip_max_mtu_frame() { + fixture_roundtrip(FIXTURE_MAX_MTU_FRAME); + } + + #[test] + fn fixture_roundtrip_empty_payload() { + fixture_roundtrip(FIXTURE_EMPTY_PAYLOAD); + } + + #[test] + fn fixture_roundtrip_fcs_valid() { + fixture_roundtrip(FIXTURE_FCS_VALID); + } + + #[test] + fn public_api_smoke() { + let _: crate::MctpSerialMedium = crate::MctpSerialMedium; + let _: crate::SerialEncoding = crate::SerialEncoding; + assert_eq!(crate::CONST_MTU, 251); + assert_eq!(crate::SP_EID, crate::EndpointId::Id(0x08)); + assert_eq!(crate::EC_EID, crate::EndpointId::Id(0x0A)); + } + + #[test] + fn packetize_with_stuffing_respects_mtu() { + // 251-byte payload of all 0x7E. Each byte stuffs to 2 wire + // bytes (0x7D 0x5E), so encoded body footprint per packet is + // 2x decoded length. The packet body MTU is 251 wire bytes; + // each MCTP packet also carries a 4-byte transport header + // which itself is `wire_size_of`-measured. Expect the message + // to split across multiple packets and no body region to + // exceed CONST_MTU wire bytes. + use crate::{ + endpoint_id::EndpointId, mctp_message_tag::MctpMessageTag, mctp_packet_context::MctpReplyContext, + mctp_sequence_number::MctpSequenceNumber, serialize::SerializePacketState, + }; + + let payload = [0x7E_u8; 251]; + let mut assembly = [0u8; 1024]; + let medium = MctpSerialMedium; + let reply_context = MctpReplyContext:: { + destination_endpoint_id: EndpointId::Id(0x0A), + source_endpoint_id: EndpointId::Id(0x08), + packet_sequence_number: MctpSequenceNumber::new(0), + message_tag: MctpMessageTag::default(), + medium_context: (), + }; + let mut state = SerializePacketState { + medium: &medium, + reply_context, + current_packet_num: 0, + serialized_message_header: false, + message_buffer: &payload[..], + assembly_buffer: &mut assembly[..], + }; + + let mut total_decoded_body = 0usize; + let mut packet_count = 0usize; + loop { + // We cannot iterate `state.next()` more than once because + // `next` mutably borrows the assembly buffer for each + // returned slice. Take one packet, process it, then break. + let pkt = match state.next() { + Some(Ok(pkt)) => { + let mut tmp = [0u8; 1024]; + tmp[..pkt.len()].copy_from_slice(pkt); + (tmp, pkt.len()) + } + Some(Err(e)) => panic!("serialize error: {e:?}"), + None => break, + }; + packet_count += 1; + // Deserialize the packet to recover the wire body length + // and the decoded body byte count. + let (frame, dec) = medium.deserialize(&pkt.0[..pkt.1]).unwrap(); + // Decoded body byte count INCLUDES the 4 transport-header + // bytes — subtract to get the actual payload bytes. + assert!(frame.byte_count as usize >= 4); + let payload_decoded = frame.byte_count as usize - 4; + total_decoded_body += payload_decoded; + // Wire body region (between header and FCS) MUST be <= + // CONST_MTU under MEDIUM-08 chunk-sizing. + let _ = dec; // decoder discard + let wire_body_len = pkt.1 - 2 /* hdr */ - 1 /* end-flag */; + // Subtract the (possibly stuffed) FCS bytes — they are 2 + // FCS bytes but each may stuff to 2 wire bytes. Worst case + // 4 bytes; lower bound on body wire = wire_body_len - 4. + assert!( + wire_body_len <= CONST_MTU + 4, + "packet {packet_count} body exceeds MTU + worst-case FCS: {wire_body_len}" + ); + } + assert!(packet_count >= 2, "expected multi-packet split, got {packet_count}"); + assert_eq!(total_decoded_body, payload.len()); + } +} diff --git a/mctp-rs/src/medium/smbus_espi.rs b/mctp-rs/src/medium/smbus_espi.rs index a54b9378a..eeeb5979f 100644 --- a/mctp-rs/src/medium/smbus_espi.rs +++ b/mctp-rs/src/medium/smbus_espi.rs @@ -2,6 +2,7 @@ use bit_register::{NumBytes, TryFromBits, TryIntoBits, bit_register}; use crate::{ MctpPacketError, + buffer_encoding::{EncodingDecoder, EncodingEncoder, PassthroughEncoding}, error::MctpPacketResult, medium::{ MctpMedium, MctpMediumFrame, @@ -24,8 +25,12 @@ impl MctpMedium for SmbusEspiMedium { type Frame = SmbusEspiMediumFrame; type Error = &'static str; type ReplyContext = SmbusEspiReplyContext; + type Encoding = PassthroughEncoding; - fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { + fn deserialize<'buf>( + &self, + packet: &'buf [u8], + ) -> MctpPacketResult<(Self::Frame, EncodingDecoder<'buf, Self::Encoding>), Self> { // Check if packet has enough bytes for header if packet.len() < 4 { return Err(MctpPacketError::MediumError("Packet too short to parse smbus header")); @@ -46,9 +51,9 @@ impl MctpMedium for SmbusEspiMedium { )); } let pec = packet[header.byte_count as usize]; - // strip off the PEC byte - let packet = &packet[..header.byte_count as usize]; - Ok((SmbusEspiMediumFrame { header, pec }, packet)) + // strip off the PEC byte; the inner stuffed region is the body bytes + let inner = &packet[..header.byte_count as usize]; + Ok((SmbusEspiMediumFrame { header, pec }, EncodingDecoder::new(inner))) } fn serialize<'buf, F>( @@ -58,40 +63,43 @@ impl MctpMedium for SmbusEspiMedium { message_writer: F, ) -> MctpPacketResult<&'buf [u8], Self> where - F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, + F: for<'a> FnOnce(&mut EncodingEncoder<'a, Self::Encoding>) -> MctpPacketResult<(), Self>, { // Reserve space for header (4 bytes) and PEC (1 byte) if buffer.len() < 5 { return Err(MctpPacketError::MediumError("Buffer too small for smbus frame")); } + let buffer_len = buffer.len(); + + // Write the body first via an encoder over the body region (reserve + // 4 leading header bytes and 1 trailing PEC byte). + let body_wire_len = { + let body_buf = &mut buffer[4..buffer_len - 1]; + let mut encoder = EncodingEncoder::::new(body_buf); + message_writer(&mut encoder)?; + encoder.wire_position() + }; - // split off a buffer where we will write the header, the rest is for body + PEC - let (header_slice, body) = buffer.split_at_mut(4); - - // Write the body first, but ensure we leave space for PEC - if body.is_empty() { - return Err(MctpPacketError::MediumError("No space for PEC byte")); - } - let available_body_len = body.len() - 1; // Reserve 1 byte for PEC - let body_len = message_writer(&mut body[..available_body_len])?; - - // with the body has been written, construct the header + // with the body has been written, construct the header. byte_count + // is the number of wire bytes that follow on the line per SMBus + // (PassthroughEncoding pairing means wire byte count == decoded + // byte count for SMBus today). let header = SmbusEspiMediumHeader { destination_slave_address: reply_context.source_slave_address, source_slave_address: reply_context.destination_slave_address, - byte_count: body_len as u8, + byte_count: body_wire_len as u8, command_code: SmbusCommandCode::Mctp, ..Default::default() }; let header_value = TryInto::::try_into(header).map_err(MctpPacketError::MediumError)?; - header_slice.copy_from_slice(&header_value.to_be_bytes()); + buffer[0..4].copy_from_slice(&header_value.to_be_bytes()); // with the header written, compute the PEC byte - let pec_value = smbus_pec::pec(&buffer[0..4 + body_len]); - buffer[4 + body_len] = pec_value; + let pec_value = smbus_pec::pec(&buffer[0..4 + body_wire_len]); + buffer[4 + body_wire_len] = pec_value; // add 4 for frame header, add 1 for PEC byte - Ok(&buffer[0..4 + body_len + 1]) + Ok(&buffer[0..4 + body_wire_len + 1]) } // TODO - this is a guess, need to find the actual value from spec @@ -170,7 +178,20 @@ impl MctpMediumFrame for SmbusEspiMediumFrame { #[cfg(test)] mod tests { extern crate std; + use std::vec::Vec; + use super::*; + use crate::buffer_encoding::DecodeError; + + /// Test-only helper: drain an `EncodingDecoder` to a `Vec` for + /// content assertions. Stops at the first error (e.g., `PrematureEnd`). + fn drain_to_vec(decoder: &mut EncodingDecoder<'_, PassthroughEncoding>) -> Vec { + let mut out = Vec::new(); + while let Ok(b) = decoder.read() { + out.push(b); + } + out + } #[test] fn test_deserialize_valid_packet() { @@ -200,14 +221,15 @@ mod tests { packet[8] = pec; let result = medium.deserialize(&packet).unwrap(); - let (frame, body) = result; + let (frame, mut decoder) = result; + let body = drain_to_vec(&mut decoder); assert_eq!(frame.header.destination_slave_address, 0x20); assert_eq!(frame.header.source_slave_address, 0x10); assert_eq!(frame.header.command_code, SmbusCommandCode::Mctp); assert_eq!(frame.header.byte_count, 4); assert_eq!(frame.pec, pec); - assert_eq!(body, &payload); + assert_eq!(body, payload); } #[test] @@ -215,10 +237,10 @@ mod tests { let medium = SmbusEspiMedium; let short_packet = [0x01, 0x02]; // Only 2 bytes, need at least 4 for header - let result = medium.deserialize(&short_packet); + let err = medium.deserialize(&short_packet).err().unwrap(); assert_eq!( - result, - Err(MctpPacketError::MediumError("Packet too short to parse smbus header")) + err, + MctpPacketError::MediumError("Packet too short to parse smbus header") ); } @@ -240,12 +262,10 @@ mod tests { packet[0..4].copy_from_slice(&header_bytes); packet[4..6].copy_from_slice(&short_payload); - let result = medium.deserialize(&packet); + let err = medium.deserialize(&packet).err().unwrap(); assert_eq!( - result, - Err(MctpPacketError::MediumError( - "Packet too short to parse smbus body and PEC" - )) + err, + MctpPacketError::MediumError("Packet too short to parse smbus body and PEC") ); } @@ -269,8 +289,8 @@ mod tests { packet[4..8].copy_from_slice(&payload); packet[8] = pec; - let result = medium.deserialize(&packet); - assert_eq!(result, Err(MctpPacketError::MediumError("Invalid smbus header"))); + let err = medium.deserialize(&packet).err().unwrap(); + assert_eq!(err, MctpPacketError::MediumError("Invalid smbus header")); } #[test] @@ -291,11 +311,11 @@ mod tests { packet[4] = pec; let result = medium.deserialize(&packet).unwrap(); - let (frame, body) = result; + let (frame, mut decoder) = result; assert_eq!(frame.header.byte_count, 0); assert_eq!(frame.pec, pec); - assert_eq!(body.len(), 0); + assert_eq!(decoder.read().unwrap_err(), DecodeError::PrematureEnd); } #[test] @@ -310,9 +330,10 @@ mod tests { let test_payload = [0xAA, 0xBB, 0xCC, 0xDD]; let result = medium - .serialize(reply_context, &mut buffer, |buf| { - buf[..test_payload.len()].copy_from_slice(&test_payload); - Ok(test_payload.len()) + .serialize(reply_context, &mut buffer, |encoder| { + encoder + .write_all(&test_payload) + .map_err(|_| MctpPacketError::SerializeError("encode error")) }) .unwrap(); @@ -348,12 +369,12 @@ mod tests { let mut small_buffer = [0u8; 4]; // Only 4 bytes, need at least 5 (header + PEC) - let result = medium.serialize(reply_context, &mut small_buffer, |_| Ok(0)); + let err = medium + .serialize(reply_context, &mut small_buffer, |_| Ok(())) + .err() + .unwrap(); - assert_eq!( - result, - Err(MctpPacketError::MediumError("Buffer too small for smbus frame")) - ); + assert_eq!(err, MctpPacketError::MediumError("Buffer too small for smbus frame")); } #[test] @@ -370,7 +391,7 @@ mod tests { .serialize( reply_context, &mut minimal_buffer, - |_| Ok(0), // No payload data + |_| Ok(()), // No payload data ) .unwrap(); @@ -399,10 +420,10 @@ mod tests { let mut buffer = [0u8; 260]; // 4 + 255 + 1 = header + max payload + PEC let result = medium - .serialize(reply_context, &mut buffer, |buf| { - let copy_len = max_payload.len().min(buf.len()); - buf[..copy_len].copy_from_slice(&max_payload[..copy_len]); - Ok(copy_len) + .serialize(reply_context, &mut buffer, |encoder| { + encoder + .write_all(&max_payload) + .map_err(|_| MctpPacketError::SerializeError("encode error")) }) .unwrap(); @@ -451,17 +472,19 @@ mod tests { // Serialize let serialized = medium - .serialize(original_context, &mut buffer, |buf| { - buf[..original_payload.len()].copy_from_slice(&original_payload); - Ok(original_payload.len()) + .serialize(original_context, &mut buffer, |encoder| { + encoder + .write_all(&original_payload) + .map_err(|_| MctpPacketError::SerializeError("encode error")) }) .unwrap(); // Deserialize - let (frame, deserialized_payload) = medium.deserialize(serialized).unwrap(); + let (frame, mut decoder) = medium.deserialize(serialized).unwrap(); + let deserialized_payload = drain_to_vec(&mut decoder); // Verify roundtrip correctness - assert_eq!(deserialized_payload, &original_payload); + assert_eq!(deserialized_payload, original_payload); assert_eq!(frame.header.destination_slave_address, 0x24); // swapped assert_eq!(frame.header.source_slave_address, 0x42); // swapped assert_eq!(frame.header.command_code, SmbusCommandCode::Mctp); @@ -553,9 +576,10 @@ mod tests { let mut buffer = [0u8; 32]; let result = medium - .serialize(reply_context, &mut buffer, |buf| { - buf[..test_data.len()].copy_from_slice(&test_data); - Ok(test_data.len()) + .serialize(reply_context, &mut buffer, |encoder| { + encoder + .write_all(&test_data) + .map_err(|_| MctpPacketError::SerializeError("encode error")) }) .unwrap(); @@ -581,7 +605,7 @@ mod tests { .serialize( reply_context, &mut buffer, - |_| Ok(0), // Empty payload + |_| Ok(()), // Empty payload ) .unwrap(); @@ -625,7 +649,7 @@ mod tests { let medium = SmbusEspiMedium; let mut buffer = [0u8; 16]; - let result = medium.serialize(reply_context, &mut buffer, |_| Ok(0)).unwrap(); + let result = medium.serialize(reply_context, &mut buffer, |_| Ok(())).unwrap(); let header_value = u32::from_be_bytes([result[0], result[1], result[2], result[3]]); let response_header = SmbusEspiMediumHeader::try_from(header_value).unwrap(); @@ -662,7 +686,8 @@ mod tests { let packet_slice = &packet[0..4 + byte_count as usize + 1]; let result = medium.deserialize(packet_slice).unwrap(); - let (frame, body) = result; + let (frame, mut decoder) = result; + let body = drain_to_vec(&mut decoder); assert_eq!(frame.header.byte_count, byte_count); assert_eq!(body.len(), byte_count as usize); @@ -689,12 +714,10 @@ mod tests { packet[4..6].copy_from_slice(&short_payload); packet[6] = 0x00; // PEC (doesn't matter for this test) - let result = medium.deserialize(&packet); + let err = medium.deserialize(&packet).err().unwrap(); assert_eq!( - result, - Err(MctpPacketError::MediumError( - "Packet too short to parse smbus body and PEC" - )) + err, + MctpPacketError::MediumError("Packet too short to parse smbus body and PEC") ); } @@ -709,14 +732,14 @@ mod tests { // Test with buffer smaller than minimum required (4 header + 1 PEC = 5 bytes) let mut tiny_buffer = [0u8; 4]; // Only 4 bytes, need at least 5 - let result = medium.serialize(reply_context, &mut tiny_buffer, |_| { - Ok(0) // No payload - }); + let err = medium + .serialize(reply_context, &mut tiny_buffer, |_| { + Ok(()) // No payload + }) + .err() + .unwrap(); - assert_eq!( - result, - Err(MctpPacketError::MediumError("Buffer too small for smbus frame")) - ); + assert_eq!(err, MctpPacketError::MediumError("Buffer too small for smbus frame")); } #[test] @@ -726,10 +749,10 @@ mod tests { // Test with packet shorter than header size (4 bytes) for packet_size in 0..4 { let short_packet = [0u8; 4]; - let result = medium.deserialize(&short_packet[..packet_size]); + let err = medium.deserialize(&short_packet[..packet_size]).err().unwrap(); assert_eq!( - result, - Err(MctpPacketError::MediumError("Packet too short to parse smbus header")) + err, + MctpPacketError::MediumError("Packet too short to parse smbus header") ); } } @@ -752,12 +775,10 @@ mod tests { packet[0..4].copy_from_slice(&header_bytes); packet[4..9].copy_from_slice(&payload); - let result = medium.deserialize(&packet); + let err = medium.deserialize(&packet).err().unwrap(); assert_eq!( - result, - Err(MctpPacketError::MediumError( - "Packet too short to parse smbus body and PEC" - )) + err, + MctpPacketError::MediumError("Packet too short to parse smbus body and PEC") ); } @@ -777,12 +798,10 @@ mod tests { let mut short_packet = [0u8; 4]; // Only header, no PEC short_packet.copy_from_slice(&header_bytes); - let result = medium.deserialize(&short_packet); + let err = medium.deserialize(&short_packet).err().unwrap(); assert_eq!( - result, - Err(MctpPacketError::MediumError( - "Packet too short to parse smbus body and PEC" - )) + err, + MctpPacketError::MediumError("Packet too short to parse smbus body and PEC") ); } @@ -799,28 +818,31 @@ mod tests { let max_payload = [0x55u8; 255]; let mut buffer = [0u8; 260]; // 4 + 255 + 1 = exactly enough - let result = medium.serialize(reply_context, &mut buffer, |buf| { - let copy_len = max_payload.len().min(buf.len()); - buf[..copy_len].copy_from_slice(&max_payload[..copy_len]); - Ok(copy_len) + let result = medium.serialize(reply_context, &mut buffer, |encoder| { + encoder + .write_all(&max_payload) + .map_err(|_| MctpPacketError::SerializeError("encode error")) }); assert!(result.is_ok()); let serialized = result.unwrap(); assert_eq!(serialized.len(), 260); // Should use exactly all available space - // Test with buffer one byte too small for maximum payload + // Test with buffer one byte too small for maximum payload. + // The encoder will hit BufferFull when trying to write the + // 255th payload byte (only 254 fit after header reservation), + // so this serialize call now returns an error rather than + // silently truncating. let mut small_buffer = [0u8; 259]; // One byte short for max payload - let result_small = medium.serialize(reply_context, &mut small_buffer, |buf| { - // Try to write max payload but buffer is too small - let copy_len = max_payload.len().min(buf.len()); - buf[..copy_len].copy_from_slice(&max_payload[..copy_len]); - Ok(copy_len) + let result_small = medium.serialize(reply_context, &mut small_buffer, |encoder| { + encoder + .write_all(&max_payload) + .map_err(|_| MctpPacketError::SerializeError("encode error")) }); - // Should still work but with truncated payload (254 bytes payload + 4 header + 1 PEC = 259) - assert!(result_small.is_ok()); - let serialized_small = result_small.unwrap(); - assert_eq!(serialized_small.len(), 259); // Uses all available space + assert_eq!( + result_small.err().unwrap(), + MctpPacketError::SerializeError("encode error") + ); } } diff --git a/mctp-rs/src/serialize.rs b/mctp-rs/src/serialize.rs index 0f2d3861e..69fac7773 100644 --- a/mctp-rs/src/serialize.rs +++ b/mctp-rs/src/serialize.rs @@ -1,6 +1,10 @@ use crate::{ - MctpPacketError, error::MctpPacketResult, mctp_packet_context::MctpReplyContext, - mctp_transport_header::MctpTransportHeader, medium::MctpMedium, + MctpPacketError, + buffer_encoding::{BufferEncoding, EncodeError, EncodingEncoder}, + error::MctpPacketResult, + mctp_packet_context::MctpReplyContext, + mctp_transport_header::MctpTransportHeader, + medium::MctpMedium, }; #[derive(Debug, PartialEq, Eq)] @@ -25,15 +29,53 @@ impl<'buf, M: MctpMedium> SerializePacketState<'buf, M> { let packet = self.medium.serialize( self.reply_context.medium_context, self.assembly_buffer, - |buffer: &mut [u8]| { - let max_packet_size = self.medium.max_message_body_size().min(buffer.len()); - if max_packet_size < TRANSPORT_HEADER_SIZE { + |encoder: &mut EncodingEncoder<'_, M::Encoding>| { + let max_wire = self.medium.max_message_body_size().min(encoder.remaining_wire()); + + // Build the transport header first (with end_of_message + // tentatively 0) so we can measure its wire footprint + // under the medium's encoding before chunking the body. + let start_of_message = if self.current_packet_num == 0 { 1 } else { 0 }; + let packet_sequence_number = self.reply_context.packet_sequence_number.inc(); + let mut transport_header_value: u32 = MctpTransportHeader { + reserved: 0, + header_version: 1, + start_of_message, + end_of_message: 0, + packet_sequence_number, + tag_owner: 0, + message_tag: self.reply_context.message_tag, + source_endpoint_id: self.reply_context.destination_endpoint_id, + destination_endpoint_id: self.reply_context.source_endpoint_id, + } + .try_into() + .map_err(MctpPacketError::SerializeError)?; + let mut header_bytes = transport_header_value.to_be_bytes(); + let header_wire_cost = M::Encoding::wire_size_of(&header_bytes); + if header_wire_cost > max_wire { return Err(MctpPacketError::SerializeError( "assembly buffer too small for mctp transport header", )); } - let message_size = (max_packet_size - TRANSPORT_HEADER_SIZE).min(self.message_buffer.len()); + // Walk decoded body bytes one at a time, accumulating + // their per-byte wire footprint via + // `M::Encoding::wire_size_of`. Stop when adding the + // next byte would exceed the wire budget. Correct for + // both passthrough and stuffing encodings (both shipped + // encodings are byte-additive — `wire_size_of(a ++ b) + // == wire_size_of(a) + wire_size_of(b)`). + let body_wire_budget = max_wire - header_wire_cost; + let mut consumed_wire = 0usize; + let mut message_size = 0usize; + for &b in self.message_buffer.iter() { + let cost = M::Encoding::wire_size_of(&[b]); + if consumed_wire + cost > body_wire_budget { + break; + } + consumed_wire += cost; + message_size += 1; + } // if there is no room for any of the body, and the body is not empty, // then return an error, otherwise we infinate loop sending packets with headers and @@ -47,30 +89,43 @@ impl<'buf, M: MctpMedium> SerializePacketState<'buf, M> { let body = &self.message_buffer[..message_size]; self.message_buffer = &self.message_buffer[message_size..]; - let start_of_message = if self.current_packet_num == 0 { 1 } else { 0 }; + // Now that we know whether this is the final chunk, + // rebuild the transport header if `end_of_message` + // flips to 1. Re-measure the wire cost — none of the + // EOM-bit bytes hit 0x7E or 0x7D under either shipped + // encoding in practice, but do not assume. let end_of_message = if self.message_buffer.is_empty() { 1 } else { 0 }; - let packet_sequence_number = self.reply_context.packet_sequence_number.inc(); - let transport_header: u32 = MctpTransportHeader { - reserved: 0, - header_version: 1, - start_of_message, - end_of_message, - packet_sequence_number, - tag_owner: 0, - message_tag: self.reply_context.message_tag, - source_endpoint_id: self.reply_context.destination_endpoint_id, - destination_endpoint_id: self.reply_context.source_endpoint_id, + if end_of_message == 1 { + transport_header_value = MctpTransportHeader { + reserved: 0, + header_version: 1, + start_of_message, + end_of_message, + packet_sequence_number, + tag_owner: 0, + message_tag: self.reply_context.message_tag, + source_endpoint_id: self.reply_context.destination_endpoint_id, + destination_endpoint_id: self.reply_context.source_endpoint_id, + } + .try_into() + .map_err(MctpPacketError::SerializeError)?; + header_bytes = transport_header_value.to_be_bytes(); + let rebuilt_header_wire_cost = M::Encoding::wire_size_of(&header_bytes); + if rebuilt_header_wire_cost + consumed_wire > max_wire { + return Err(MctpPacketError::SerializeError( + "assembly buffer too small after EOM bit set", + )); + } } - .try_into() - .map_err(MctpPacketError::SerializeError)?; - // write the transport header and message body - let mut cursor = 0; - buffer[cursor..cursor + TRANSPORT_HEADER_SIZE].copy_from_slice(&transport_header.to_be_bytes()); - cursor += TRANSPORT_HEADER_SIZE; - // message body is the rest of the buffer, up to the packet size - buffer[cursor..cursor + body.len()].copy_from_slice(body); - Ok(cursor + body.len()) + // write the transport header and message body via the + // medium-supplied encoder. + let map_encode_err = |e: EncodeError| match e { + EncodeError::BufferFull => MctpPacketError::SerializeError("encoding: buffer full"), + }; + encoder.write_all(&header_bytes).map_err(map_encode_err)?; + encoder.write_all(body).map_err(map_encode_err)?; + Ok(()) }, ); diff --git a/mctp-rs/src/test_util.rs b/mctp-rs/src/test_util.rs index f367c670d..e328a0c94 100644 --- a/mctp-rs/src/test_util.rs +++ b/mctp-rs/src/test_util.rs @@ -1,5 +1,6 @@ use crate::{ MctpPacketError, + buffer_encoding::{EncodingDecoder, EncodingEncoder, PassthroughEncoding}, error::MctpPacketResult, medium::{MctpMedium, MctpMediumFrame}, }; @@ -36,8 +37,12 @@ impl MctpMedium for TestMedium { type Frame = TestMediumFrame; type Error = &'static str; type ReplyContext = (); + type Encoding = PassthroughEncoding; - fn deserialize<'buf>(&self, packet: &'buf [u8]) -> MctpPacketResult<(Self::Frame, &'buf [u8]), Self> { + fn deserialize<'buf>( + &self, + packet: &'buf [u8], + ) -> MctpPacketResult<(Self::Frame, EncodingDecoder<'buf, Self::Encoding>), Self> { let packet_len = packet.len(); // check that header / trailer is present and correct @@ -51,8 +56,8 @@ impl MctpMedium for TestMedium { return Err(MctpPacketError::MediumError("trailer mismatch")); } - let packet = &packet[self.header.len()..packet_len - self.trailer.len()]; - Ok((TestMediumFrame(packet_len), packet)) + let inner = &packet[self.header.len()..packet_len - self.trailer.len()]; + Ok((TestMediumFrame(packet_len), EncodingDecoder::new(inner))) } fn max_message_body_size(&self) -> usize { self.mtu @@ -64,7 +69,7 @@ impl MctpMedium for TestMedium { message_writer: F, ) -> MctpPacketResult<&'buf [u8], Self> where - F: for<'a> FnOnce(&'a mut [u8]) -> MctpPacketResult, + F: for<'a> FnOnce(&mut EncodingEncoder<'a, Self::Encoding>) -> MctpPacketResult<(), Self>, { let header_len = self.header.len(); let trailer_len = self.trailer.len(); @@ -82,8 +87,15 @@ impl MctpMedium for TestMedium { let max_message_size = max_packet_size - header_len - trailer_len; buffer[0..header_len].copy_from_slice(self.header); - let size = message_writer(&mut buffer[header_len..header_len + max_message_size])?; - let len = header_len + size; + + let body_wire_len = { + let body_buf = &mut buffer[header_len..header_len + max_message_size]; + let mut encoder = EncodingEncoder::::new(body_buf); + message_writer(&mut encoder)?; + encoder.wire_position() + }; + + let len = header_len + body_wire_len; buffer[len..len + trailer_len].copy_from_slice(self.trailer); Ok(&buffer[..len + trailer_len]) } diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 52d64d200..659f35ae6 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -22,6 +22,10 @@ audit-as-crates-io = false [policy.keyberon] audit-as-crates-io = false +[[exemptions.crc]] +version = "3.4.0" +criteria = "safe-to-deploy" + [[exemptions.diff]] version = "0.1.13" criteria = "safe-to-run" From c08c361d63cce30992a34d206290065260f87eb1 Mon Sep 17 00:00:00 2001 From: Matteo Tullo Date: Tue, 19 May 2026 13:04:16 -0700 Subject: [PATCH 79/79] Add type-c traits for getting Discovered SVIDs, Discover Identity, executing Hard Reset (#850) Mirrors PR #820 for v0.2.0 Resolves #849 --- Cargo.lock | 1 + examples/rt685s-evk/Cargo.lock | 1 + examples/std/Cargo.lock | 9 +-- .../std/src/lib/type_c/mock_controller.rs | 25 +++++++ type-c-interface/Cargo.toml | 1 + type-c-interface/src/control/mod.rs | 1 + type-c-interface/src/control/svid.rs | 62 ++++++++++++++++ type-c-interface/src/controller/pd.rs | 19 +++++ type-c-interface/src/port/pd.rs | 15 ++++ type-c-service/src/controller/pd.rs | 26 +++++++ type-c-service/src/driver/tps6699x.rs | 72 +++++++++++++++++++ 11 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 type-c-interface/src/control/svid.rs diff --git a/Cargo.lock b/Cargo.lock index 4f5c1a898..89bd9182b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2262,6 +2262,7 @@ dependencies = [ "defmt 0.3.100", "embedded-services", "embedded-usb-pd", + "heapless 0.8.0", "log", "power-policy-interface", ] diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 342fa1dc9..2ed81527c 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -1512,6 +1512,7 @@ dependencies = [ "defmt 0.3.100", "embedded-services", "embedded-usb-pd", + "heapless 0.8.0", "power-policy-interface", ] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index 5d6227827..9d833a99b 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", @@ -1472,6 +1472,7 @@ dependencies = [ "bitfield 0.17.0", "embedded-services", "embedded-usb-pd", + "heapless 0.8.0", "log", "power-policy-interface", ] diff --git a/examples/std/src/lib/type_c/mock_controller.rs b/examples/std/src/lib/type_c/mock_controller.rs index 206857cd0..10454d907 100644 --- a/examples/std/src/lib/type_c/mock_controller.rs +++ b/examples/std/src/lib/type_c/mock_controller.rs @@ -4,6 +4,7 @@ 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::vdm::structured::command::discover_identity::{sop, sop_prime}; use embedded_usb_pd::{LocalPortId, PdError}; use embedded_usb_pd::{PowerRole, type_c::Current}; use embedded_usb_pd::{type_c::ConnectionState, ucsi::lpm}; @@ -14,6 +15,7 @@ use type_c_interface::control::dp::{DpConfig, DpPinConfig, DpStatus}; use type_c_interface::control::pd::{PdStateMachineConfig, PortStatus}; use type_c_interface::control::power::SystemPowerState; use type_c_interface::control::retimer::RetimerFwUpdateState; +use type_c_interface::control::svid::DiscoveredSvids; use type_c_interface::control::tbt::TbtConfig; use type_c_interface::control::type_c::TypeCStateMachineState; use type_c_interface::control::usb::UsbControlConfig; @@ -228,6 +230,29 @@ impl type_c_interface::controller::pd::Pd for Controller<'_> { debug!("Set Thunderbolt config for port {port:?}: {config:?}"); Ok(()) } + + async fn hard_reset(&mut self, port: LocalPortId) -> Result<(), PdError> { + debug!("Hard reset for port {port:?}"); + Ok(()) + } + + async fn get_discovered_svids(&mut self, port: LocalPortId) -> Result { + debug!("Get discovered SVIDs for port {port:?}"); + Ok(DiscoveredSvids::default()) + } + + async fn get_discover_identity_sop_response(&mut self, port: LocalPortId) -> Result { + debug!("Get Discover Identity SOP response for port {port:?}"); + Err(PdError::Failed) + } + + async fn get_discover_identity_sop_prime_response( + &mut self, + port: LocalPortId, + ) -> Result { + debug!("Get Discover Identity SOP' response for port {port:?}"); + Err(PdError::Failed) + } } impl type_c_interface::controller::max_sink_voltage::MaxSinkVoltage for Controller<'_> { diff --git a/type-c-interface/Cargo.toml b/type-c-interface/Cargo.toml index 27857480f..f8565d547 100644 --- a/type-c-interface/Cargo.toml +++ b/type-c-interface/Cargo.toml @@ -15,6 +15,7 @@ defmt = { workspace = true, optional = true } embedded-services.workspace = true embedded-usb-pd.workspace = true power-policy-interface.workspace = true +heapless.workspace = true [lints] workspace = true diff --git a/type-c-interface/src/control/mod.rs b/type-c-interface/src/control/mod.rs index 05607decc..426d42af1 100644 --- a/type-c-interface/src/control/mod.rs +++ b/type-c-interface/src/control/mod.rs @@ -3,6 +3,7 @@ pub mod dp; pub mod pd; pub mod power; pub mod retimer; +pub mod svid; pub mod tbt; pub mod type_c; pub mod usb; diff --git a/type-c-interface/src/control/svid.rs b/type-c-interface/src/control/svid.rs new file mode 100644 index 000000000..259b2f533 --- /dev/null +++ b/type-c-interface/src/control/svid.rs @@ -0,0 +1,62 @@ +use embedded_usb_pd::vdm::structured::Svid; +use heapless::Vec; + +/// Response from the `Discover SVIDs REQ` message and the PortCommandData::GetDiscoveredSvids command. +// Could be changed to hold the heapless::Vec directly if they were Copy or if PortResponseData was not Copy +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DiscoveredSvids { + num_sop: usize, + sop: [Svid; Self::NUM_SVIDS], + + num_sop_prime: usize, + sop_prime: [Svid; Self::NUM_SVIDS], +} + +impl DiscoveredSvids { + /// The number of SVIDs that can be reported in a single DiscoveredSvids response. + pub const NUM_SVIDS: usize = 8; + + /// Create a new response object from `sop` and `sop_prime`. + pub fn new(sop: Vec, sop_prime: Vec) -> Self { + let num_sop = sop.len(); + let num_sop_prime = sop_prime.len(); + + let mut sop_array = [Svid(0); _]; + for (svid, dest) in sop.into_iter().zip(sop_array.iter_mut()) { + *dest = svid; + } + + let mut sop_prime_array = [Svid(0); _]; + for (svid, dest) in sop_prime.into_iter().zip(sop_prime_array.iter_mut()) { + *dest = svid; + } + + Self { + num_sop, + sop: sop_array, + num_sop_prime, + sop_prime: sop_prime_array, + } + } + + /// Returns the number of SVIDs discovered on the SOP port partner. + pub fn number_sop_svids(&self) -> usize { + self.num_sop + } + + /// Returns an iterator over the SVIDs discovered on the SOP port partner. + pub fn svid_sop(&self) -> impl ExactSizeIterator { + self.sop.iter().copied().take(self.num_sop) + } + + /// Returns the number of SVIDs discovered on the SOP' cable plug. + pub fn number_sop_prime_svids(&self) -> usize { + self.num_sop_prime + } + + /// Returns an iterator over the SVIDs discovered on the SOP' cable plug. + pub fn svid_sop_prime(&self) -> impl ExactSizeIterator { + self.sop_prime.iter().copied().take(self.num_sop_prime) + } +} diff --git a/type-c-interface/src/controller/pd.rs b/type-c-interface/src/controller/pd.rs index 4e20afd9f..287fef713 100644 --- a/type-c-interface/src/controller/pd.rs +++ b/type-c-interface/src/controller/pd.rs @@ -1,9 +1,11 @@ use embedded_services::named::Named; +use embedded_usb_pd::vdm::structured::command::discover_identity::{sop, sop_prime}; use embedded_usb_pd::{LocalPortId, PdError, ado::Ado}; use crate::control::{ dp::{DpConfig, DpStatus}, pd::{PdStateMachineConfig, PortStatus}, + svid::DiscoveredSvids, tbt::TbtConfig, usb::UsbControlConfig, vdm::{AttnVdm, OtherVdm, SendVdm}, @@ -38,6 +40,8 @@ pub trait Pd: Named { fn send_vdm(&mut self, port: LocalPortId, tx_vdm: SendVdm) -> impl Future>; /// Execute PD Data Reset for the given port fn execute_drst(&mut self, port: LocalPortId) -> impl Future>; + /// Execute a Hard Reset on the given port. + fn hard_reset(&mut self, port: LocalPortId) -> impl Future>; /// Get DisplayPort status for the given port fn get_dp_status(&mut self, port: LocalPortId) -> impl Future>; @@ -53,6 +57,21 @@ pub trait Pd: Named { port: LocalPortId, config: UsbControlConfig, ) -> impl Future>; + + /// Get the given port's discovered SVIDs + fn get_discovered_svids(&mut self, port: LocalPortId) -> impl Future>; + + /// Get the latest response from the Discover Identity command targeting SOP. + fn get_discover_identity_sop_response( + &mut self, + port: LocalPortId, + ) -> impl Future>; + + /// Get the latest response from the Discover Identity command targeting SOP'. + fn get_discover_identity_sop_prime_response( + &mut self, + port: LocalPortId, + ) -> impl Future>; } /// PD state machine related controller functionality diff --git a/type-c-interface/src/port/pd.rs b/type-c-interface/src/port/pd.rs index 547f948b5..3cf613a00 100644 --- a/type-c-interface/src/port/pd.rs +++ b/type-c-interface/src/port/pd.rs @@ -1,9 +1,11 @@ use embedded_services::named::Named; +use embedded_usb_pd::vdm::structured::command::discover_identity::{sop, sop_prime}; use embedded_usb_pd::{PdError, ado::Ado}; use crate::control::{ dp::{DpConfig, DpStatus}, pd::{PdStateMachineConfig, PortStatus}, + svid::DiscoveredSvids, tbt::TbtConfig, usb::UsbControlConfig, vdm::{AttnVdm, OtherVdm, SendVdm}, @@ -34,6 +36,8 @@ pub trait Pd: Named { fn send_vdm(&mut self, tx_vdm: SendVdm) -> impl Future>; /// Execute PD Data Reset for this port fn execute_drst(&mut self) -> impl Future>; + /// Execute a Hard Reset on this port. + fn hard_reset(&mut self) -> impl Future>; /// Get DisplayPort status for this port fn get_dp_status(&mut self) -> impl Future>; @@ -45,6 +49,17 @@ pub trait Pd: Named { /// Set USB control configuration for this port fn set_usb_control(&mut self, config: UsbControlConfig) -> impl Future>; + + /// Get this port's discovered SVIDs + fn get_discovered_svids(&mut self) -> impl Future>; + + /// Get the latest response from the Discover Identity command targeting SOP. + fn get_discover_identity_sop_response(&mut self) -> impl Future>; + + /// Get the latest response from the Discover Identity command targeting SOP'. + fn get_discover_identity_sop_prime_response( + &mut self, + ) -> impl Future>; } /// PD state machine related controller functionality diff --git a/type-c-service/src/controller/pd.rs b/type-c-service/src/controller/pd.rs index 2091c9241..f3b862f57 100644 --- a/type-c-service/src/controller/pd.rs +++ b/type-c-service/src/controller/pd.rs @@ -2,9 +2,11 @@ use embedded_services::{event::Sender, sync::Lockable}; use embedded_usb_pd::PdError; use embedded_usb_pd::ado::Ado; +use embedded_usb_pd::vdm::structured::command::discover_identity::{sop, sop_prime}; use type_c_interface::control::{ dp::{DpConfig, DpStatus}, pd::{PdStateMachineConfig, PortStatus}, + svid::DiscoveredSvids, tbt::TbtConfig, usb::UsbControlConfig, vdm::{AttnVdm, OtherVdm, SendVdm}, @@ -130,6 +132,30 @@ impl< async fn set_usb_control(&mut self, config: UsbControlConfig) -> Result<(), PdError> { self.controller.lock().await.set_usb_control(self.port, config).await } + + async fn hard_reset(&mut self) -> Result<(), PdError> { + self.controller.lock().await.hard_reset(self.port).await + } + + async fn get_discovered_svids(&mut self) -> Result { + self.controller.lock().await.get_discovered_svids(self.port).await + } + + async fn get_discover_identity_sop_response(&mut self) -> Result { + self.controller + .lock() + .await + .get_discover_identity_sop_response(self.port) + .await + } + + async fn get_discover_identity_sop_prime_response(&mut self) -> Result { + self.controller + .lock() + .await + .get_discover_identity_sop_prime_response(self.port) + .await + } } impl< diff --git a/type-c-service/src/driver/tps6699x.rs b/type-c-service/src/driver/tps6699x.rs index 1560cd706..caa019094 100644 --- a/type-c-service/src/driver/tps6699x.rs +++ b/type-c-service/src/driver/tps6699x.rs @@ -16,6 +16,7 @@ use embedded_usb_pd::type_c::Current as TypecCurrent; use embedded_usb_pd::ucsi::lpm; use embedded_usb_pd::{DataRole, Error, LocalPortId, PdError, PlugOrientation, PowerRole}; use fw_update_interface::basic::{Error as BasicFwUpdateError, FwUpdate as BasicFwUpdate}; +use heapless::Vec; use tps6699x::MAX_SUPPORTED_PORTS; use tps6699x::asynchronous::embassy::{self as tps6699x_drv, interrupt}; use tps6699x::asynchronous::fw_update::UpdateTarget; @@ -33,6 +34,7 @@ use type_c_interface::control::dp::{DpConfig, DpPinConfig, DpStatus}; use type_c_interface::control::pd::{PdStateMachineConfig, PortStatus}; use type_c_interface::control::power::SystemPowerState; use type_c_interface::control::retimer::RetimerFwUpdateState; +use type_c_interface::control::svid::DiscoveredSvids; use type_c_interface::control::tbt::TbtConfig; use type_c_interface::control::type_c::TypeCStateMachineState; use type_c_interface::control::usb::UsbControlConfig; @@ -719,6 +721,76 @@ impl Pd for Tps6699x<'_, M, B> { { self.tps6699x.lock_inner().await.set_tbt_config(port, config_reg).await }.map_err(|e| self.log_error(e)) } + + async fn hard_reset(&mut self, port: LocalPortId) -> Result<(), PdError> { + self.guard_no_fw_update_active()?; + match self.tps6699x.execute_hrst(port).await.map_err(|e| self.log_error(e))? { + ReturnValue::Success => Ok(()), + r => { + error!("Error executing hard reset on port {}: {:#?}", port.0, r); + Err(PdError::InvalidResponse) + } + } + } + + async fn get_discovered_svids(&mut self, port: LocalPortId) -> Result { + self.guard_no_fw_update_active()?; + let svids = self + .tps6699x + .get_discovered_svids(port) + .await + .map_err(|e| self.log_error(e))?; + debug!("{:?} discovered SVIDs: {:?}", port, svids); + let mut sop = Vec::new(); + for svid in svids.svid_sop().take(sop.capacity()) { + let _ = sop.push(svid); + } + + let mut sop_prime = Vec::new(); + for svid in svids.svid_sop_prime().take(sop_prime.capacity()) { + let _ = sop_prime.push(svid); + } + + Ok(DiscoveredSvids::new(sop, sop_prime)) + } + + async fn get_discover_identity_sop_response( + &mut self, + port: LocalPortId, + ) -> Result { + self.guard_no_fw_update_active()?; + let data = self + .tps6699x + .get_received_sop_identity_data(port) + .await + .map_err(|e| self.log_error(e))?; + match data.try_into() { + Ok(vdos) => Ok(vdos), + Err(e) => { + error!("Error deserializing Received SOP Identity Data: {:?}", e); + Err(PdError::Serialize) + } + } + } + + async fn get_discover_identity_sop_prime_response( + &mut self, + port: LocalPortId, + ) -> Result { + self.guard_no_fw_update_active()?; + let data = self + .tps6699x + .get_received_sop_prime_identity_data(port) + .await + .map_err(|e| self.log_error(e))?; + match data.try_into() { + Ok(vdos) => Ok(vdos), + Err(e) => { + error!("Error deserializing Received SOP Prime Identity Data: {:?}", e); + Err(PdError::Serialize) + } + } + } } impl Retimer for Tps6699x<'_, M, B> {