From fa01ff263943c5fac50cbf523e4b25a67d640df8 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 11:16:30 -0800 Subject: [PATCH 01/16] Implement ACPI time-alarm device --- Cargo.lock | 127 +++++- Cargo.toml | 7 +- .../src/ec_type/generator/ec_memory_map.yaml | 38 -- embedded-service/src/ec_type/mod.rs | 116 ------ 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/rt685s-evk/Cargo.lock | 30 ++ examples/rt685s-evk/Cargo.toml | 1 + examples/rt685s-evk/src/bin/time_alarm.rs | 128 ++++++ time-alarm-service-messages/Cargo.toml | 21 + .../src/acpi_timestamp.rs | 173 ++++++++ time-alarm-service-messages/src/lib.rs | 382 ++++++++++++++++++ time-alarm-service/Cargo.toml | 32 ++ time-alarm-service/src/lib.rs | 366 +++++++++++++++++ time-alarm-service/src/task.rs | 27 ++ time-alarm-service/src/timer.rs | 350 ++++++++++++++++ 18 files changed, 1642 insertions(+), 194 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..a8f0c2e87 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,35 @@ 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", + "num_enum", + "zerocopy", +] + [[package]] name = "tokio" version = "1.47.1" diff --git a/Cargo.toml b/Cargo.toml index c71070098..a723ed183 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", @@ -54,6 +56,7 @@ bitfield = "0.17.0" bitflags = "2.8.0" bitvec = { version = "1.0.1", default-features = false } block-device-driver = "0.2" +bytemuck = { version = "1.23.2", features = ["derive"] } cfg-if = "1.0.0" chrono = { version = "0.4", default-features = false } cortex-m = "0.7.6" @@ -62,6 +65,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 +84,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 +98,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..a1b799e22 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 { 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/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 52cc130ee..c7aa90298 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -203,6 +203,20 @@ name = "bytemuck" version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -1356,6 +1370,7 @@ dependencies = [ "power-button-service", "power-policy-service", "static_cell", + "time-alarm-service", "tps6699x", "type-c-service", ] @@ -1555,6 +1570,21 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "time-alarm-service" +version = "0.1.0" +dependencies = [ + "bitfield 0.17.0", + "bytemuck", + "defmt 0.3.100", + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-mcu-hal", + "embedded-services", +] + [[package]] name = "tps6699x" version = "0.1.0" diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index c27b34f6c..d3dcee657 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -65,6 +65,7 @@ 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"] } 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..5ff5a699f --- /dev/null +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -0,0 +1,128 @@ +#![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 core::borrow::{Borrow, BorrowMut}; + use embassy_time::{Duration, Ticker}; + use embedded_services::buffer::OwnedRef; + use embedded_services::comms::{self, EndpointID, External, Internal}; + use embedded_services::ec_type::message::AcpiMsgComms; // TODO this is gone, rewrite + use embedded_services::ec_type::message::HostMsg; + + embedded_services::define_static_buffer!(acpi_buf, u8, [0u8; 69]); + + pub struct Service { + endpoint: comms::Endpoint, + acpi_buf_owned_ref: OwnedRef<'static, u8>, + } + + 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)), + acpi_buf_owned_ref: acpi_buf::get_mut().unwrap(), + }); + + 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> { + info!("mock eSPI service received message from time-alarm service"); + let msg = message.data.get::().ok_or_else(|| { + error!("Mock eSPI service received unknown message type"); + comms::MailboxDelegateError::MessageNotFound + })?; + + match msg { + HostMsg::Notification(n) => { + info!("Notification: offset={}", n.offset); + } + HostMsg::Response(acpi) => { + let payload = acpi.payload.borrow(); + let payload_slice: &[u8] = payload.borrow(); + info!( + "Response: payload_len={}, payload={:?}", + acpi.payload_len, + &payload_slice[..acpi.payload_len] + ); + } + } + + Ok(()) + } + } + + // espi service that will update the memory map + #[embassy_executor::task] + async fn run_mock_service(espi_service: &'static Service) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + + loop { + // let event = select(espi_service.signal.wait(), ticker.next()).await; + ticker.next().await; + + let payload_len = { + // TODO alternate between different messages. + let mut buffer_access = espi_service.acpi_buf_owned_ref.borrow_mut(); + let buffer: &mut [u8] = buffer_access.borrow_mut(); + buffer[0] = 2; + + 4 // u32 + }; + + let message = AcpiMsgComms { + payload: acpi_buf::get(), + payload_len, + }; + + espi_service + .endpoint + .send(EndpointID::Internal(Internal::TimeAlarm), &message) + .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_ALARM_SERVICE: OnceLock = OnceLock::new(); + time_alarm_service::Service::init( + &TIME_ALARM_SERVICE, + &spawner, + dt_clock, + tz, + ac_expiration, + ac_policy, + dc_expiration, + dc_policy, + ) + .await + .unwrap(); +} diff --git a/time-alarm-service-messages/Cargo.toml b/time-alarm-service-messages/Cargo.toml new file mode 100644 index 000000000..65a495363 --- /dev/null +++ b/time-alarm-service-messages/Cargo.toml @@ -0,0 +1,21 @@ +[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 } +embedded-mcu-hal.workspace = true +embedded-services.workspace = true +num_enum.workspace = true +zerocopy.workspace = true + +[features] +defmt = ["dep:defmt"] + +[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..476c8ae0e --- /dev/null +++ b/time-alarm-service-messages/src/acpi_timestamp.rs @@ -0,0 +1,173 @@ +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, + + // Millseconds: 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).try_into().expect("Datetime::nanoseconds() is capped at 10^9 and therefore should always divide by 10^6 into something that fits in u16"), + time_zone: i16::from(ts.time_zone).into(), + daylight: ts.dst_status.into(), + _padding: [0; 3], + } + } +} + +// ------------------------------------------------- + +#[derive(Copy, Clone, 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(Copy, Clone, 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(PartialEq, Clone, Copy)] +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 */ { + 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..2b0b43c01 --- /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 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(PartialEq, Clone, Copy)] +pub enum AcpiTimeAlarmRequest { + GetCapabilities, // 1: _GCP --> u32 (bitmask), failure: infallible + GetRealTime, // 2: _GRT --> AcpiTimestamp, failure: valid bit = 0 in returned timestamp + SetRealTime(AcpiTimestamp), // 3: _SRT --> u32 (bool), failure: u32::MAX + GetWakeStatus(AcpiTimerId), // 4: _GWS --> u32 (bitmask), failure: infallible + ClearWakeStatus(AcpiTimerId), // 5: _CWS --> u32 (bool), failure: 1 + SetTimerValue(AcpiTimerId, AlarmTimerSeconds), // 6: _STV --> u32 (bool), failure: 1, + GetTimerValue(AcpiTimerId), // 7: _TIV --> u32 (AlarmTimerSeconds), failure: infallible, u32::MAX if disabled + SetExpiredTimerPolicy(AcpiTimerId, AlarmExpiredWakePolicy), // 8: _STP --> u32 (bool), failure: 1 + GetExpiredTimerPolicy(AcpiTimerId), // 9: _TIP --> u32 (AlarmExpiredWakePolicy) failure: infallible +} + +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[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(Debug, Clone, Copy, 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, 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, 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(Debug, Clone, Copy, 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..549c3f79f --- /dev/null +++ b/time-alarm-service/Cargo.toml @@ -0,0 +1,32 @@ +[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", +] + +[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..48ae6a131 --- /dev/null +++ b/time-alarm-service/src/lib.rs @@ -0,0 +1,366 @@ +#![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 => { + panic!("Should never attempt intitialization as a response to receiving a mailbox message") + } + 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)); + } + + /// Retreives 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_else(|_| (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 { + // TODO [DYN] if we want to allow taking the HAL traits as concrete types rather than as dyn references, we'll likely need to make this a macro + // in order to accommodate the restriction that embassy tasks can't have generic parameters. When we do that, it may be worthwhile to + // also investigate ways to take the backing storage as a slice rather than as a bunch of individual references - currently, we can't + // take a slice of the array because that would be a slice of trait impls and we need dyn references here to accommodate the constraints + // on embassy task implementation. + // + 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); + self.endpoint + .send(respond_to_endpoint, &result) + .await + .expect("send returns Infallible"); + } + 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; + // TODO [SPEC] section 9.18.7 indicates that when a timer expires, both timers have their wake policies reset, + // but I can't find any similar rule for the actual timer value - that seems odd to me, verify that's actually how + // it's supposed to work + 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); + + // TODO [SPEC] the spec is ambiguous on whether or not we should adjust any outstanding timers based on the new time - see if we can find an answer elsewhere + 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()).try_into().expect("Per the ACPI spec, timers are communicated in u32 seconds, so this shouldn't be able to overflow")) + } + 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 { + // TODO [COMMS] right now, if pushing the message to the channel fails, the error that we return this gets + // discarded by our caller and we have no opportunity to raise a failure. Fixing that probably + // requires changes in the mailbox system, so we're ignoring it for now. + 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..8c6e5c0a6 --- /dev/null +++ b/time-alarm-service/src/task.rs @@ -0,0 +1,27 @@ +use crate::{AcpiTimerId, Service}; +use embedded_services::info; + +// TODO This pattern of pushing task declaration to the 'application' level allows us to +// avoid bringing in the async executor dependency into the service crate itself, but +// it also means that we can't really construct and start a task in a single motion, +// which means it's possible to forget to start a task after constructing the service +// and have the service misbehave. Investigate ways to improve that to make the API +// less error-prone. + +/// 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 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 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..130691ee8 --- /dev/null +++ b/time-alarm-service/src/timer.rs @@ -0,0 +1,350 @@ +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; +use embedded_services::GlobalRawMutex; + +/// 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) => { + self.expiration_time_storage + .write(dt.to_unix_time_seconds().try_into().expect( + "Datetime::to_unix_timestamp() returns i64, which should always fit in u32 until the year 2106", + )); + } + 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) { + 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); + } + + 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(); + }); + } + + // TODO [SPEC] the spec is ambiguous on whether or not this policy should include the number of seconds that have elapsed against it + // (i.e. if the user set it to 60s and 45s have elapsed since we switched to the expired power source, should we report + // 60s or 15s?)- see if we can get a concrete answer on this. + // + 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, + ) { + self.timer_state.lock(|timer_state| { + let mut timer_state = timer_state.borrow_mut(); + timer_state.persistent_storage.set_timer_wake_policy(wake_policy); + + // TODO [SPEC] verify this is correct - the spec isn't particularly clear on what should happen if reprogramming the policy while it's actively ticking down, + // may need to look at the windows acpi implementation or something + // + 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)); + } + }) + } + + pub fn set_expiration_time( + &self, + clock_state: &'static Mutex>, + expiration_time: Option, + ) { + 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) => { + timer_state.persistent_storage.set_expiration_time(expiration_time); + timer_state.wake_state = WakeState::Armed; + + // 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()).try_into() + .expect("Users should not have been able to program a time greater than u32::MAX seconds in the future - the ACPI spec prevents it") + )); + } + None => self.clear_expiration_time(&mut timer_state) + } + }); + } + + 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 { + timer_state.wake_state = WakeState::ExpiredWaitingForPolicyDelay( + Self::get_current_datetime(clock_state), + seconds_already_elapsed, + ); + self.timer_signal.signal(Some( + timer_state.persistent_storage + .get_timer_wake_policy() + .0 + .saturating_sub(seconds_already_elapsed), + )); + } + } else if let WakeState::ExpiredWaitingForPolicyDelay(wait_start_time, seconds_elapsed_before_wait) = + timer_state.wake_state + { + let total_seconds_elapsed_on_policy_delay: u32 = seconds_elapsed_before_wait + + u32::try_from(Self::get_current_datetime(clock_state) + .to_unix_time_seconds() + .saturating_sub(wait_start_time.to_unix_time_seconds())) + .expect("The ACPI spec expresses timeouts in terms of u32s - it's impossible to schedule a timer u32::MAX seconds in the future"); + + 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 now = Self::get_current_datetime(clock_state); + let expiration_time = timer_state.persistent_storage.get_expiration_time().expect("We should never be in the Armed or ExpiredWaitingForPolicyDelay states if there's no expiration time set"); + 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()) + .try_into() + .expect("Users should not have been able to program a time greater than u32::MAX seconds in the future - the ACPI spec prevents it"))); + return false; + } + + 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>) -> Datetime { + clock_state.lock(|clock_state| { + clock_state + .borrow() + .datetime_clock + .get_current_datetime() + .expect("Datetime clock should have already been initialized before we were constructed") + }) + } +} From cece16aaa7fc347660e8c890562efbba2dcff0fc Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 11:49:20 -0800 Subject: [PATCH 02/16] fix deps, example --- Cargo.toml | 1 - examples/rt685s-evk/Cargo.lock | 73 ++++++++++++--------- examples/rt685s-evk/Cargo.toml | 3 + examples/rt685s-evk/src/bin/time_alarm.rs | 78 +++++++++-------------- time-alarm-service-messages/Cargo.toml | 2 +- 5 files changed, 78 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a723ed183..27aa09cd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ bitfield = "0.17.0" bitflags = "2.8.0" bitvec = { version = "1.0.1", default-features = false } block-device-driver = "0.2" -bytemuck = { version = "1.23.2", features = ["derive"] } cfg-if = "1.0.0" chrono = { version = "0.4", default-features = false } cortex-m = "0.7.6" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index c7aa90298..8a3945ed4 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -203,20 +203,6 @@ name = "bytemuck" version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "byteorder" @@ -522,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", @@ -549,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", @@ -702,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]] @@ -1073,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", ] @@ -1093,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", ] @@ -1187,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", @@ -1197,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", @@ -1337,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" @@ -1364,13 +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", ] @@ -1482,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" @@ -1575,7 +1575,6 @@ name = "time-alarm-service" version = "0.1.0" dependencies = [ "bitfield 0.17.0", - "bytemuck", "defmt 0.3.100", "embassy-executor", "embassy-futures", @@ -1583,6 +1582,20 @@ dependencies = [ "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]] diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index d3dcee657..ca667b922 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -66,6 +66,9 @@ embedded-usb-pd = { git = "https://github.com/OpenDevicePartnership/embedded-usb ] } 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 index 5ff5a699f..63b7a76c7 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -10,25 +10,18 @@ use {defmt_rtt as _, panic_probe as _}; mod mock_espi_service { use crate::OnceLock; use crate::{error, info}; - use core::borrow::{Borrow, BorrowMut}; use embassy_time::{Duration, Ticker}; - use embedded_services::buffer::OwnedRef; use embedded_services::comms::{self, EndpointID, External, Internal}; - use embedded_services::ec_type::message::AcpiMsgComms; // TODO this is gone, rewrite - use embedded_services::ec_type::message::HostMsg; - - embedded_services::define_static_buffer!(acpi_buf, u8, [0u8; 69]); + use time_alarm_service_messages::{AcpiTimeAlarmRequest, AcpiTimeAlarmResponse}; pub struct Service { endpoint: comms::Endpoint, - acpi_buf_owned_ref: OwnedRef<'static, u8>, } 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)), - acpi_buf_owned_ref: acpi_buf::get_mut().unwrap(), }); comms::register_endpoint(instance, &instance.endpoint).await.unwrap(); @@ -39,57 +32,29 @@ mod mock_espi_service { impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - info!("mock eSPI service received message from time-alarm service"); - let msg = message.data.get::().ok_or_else(|| { + let msg = message.data.get::().ok_or_else(|| { error!("Mock eSPI service received unknown message type"); comms::MailboxDelegateError::MessageNotFound })?; - match msg { - HostMsg::Notification(n) => { - info!("Notification: offset={}", n.offset); - } - HostMsg::Response(acpi) => { - let payload = acpi.payload.borrow(); - let payload_slice: &[u8] = payload.borrow(); - info!( - "Response: payload_len={}, payload={:?}", - acpi.payload_len, - &payload_slice[..acpi.payload_len] - ); - } - } + info!("Mock eSPI service received ACPI Time Alarm Response: {:?}", msg); Ok(()) } } - // espi service that will update the memory map #[embassy_executor::task] async fn run_mock_service(espi_service: &'static Service) { let mut ticker = Ticker::every(Duration::from_secs(1)); loop { - // let event = select(espi_service.signal.wait(), ticker.next()).await; ticker.next().await; - - let payload_len = { - // TODO alternate between different messages. - let mut buffer_access = espi_service.acpi_buf_owned_ref.borrow_mut(); - let buffer: &mut [u8] = buffer_access.borrow_mut(); - buffer[0] = 2; - - 4 // u32 - }; - - let message = AcpiMsgComms { - payload: acpi_buf::get(), - payload_len, - }; - espi_service .endpoint - .send(EndpointID::Internal(Internal::TimeAlarm), &message) + .send( + EndpointID::Internal(Internal::TimeAlarm), + &AcpiTimeAlarmRequest::GetRealTime, + ) .await .unwrap(); } @@ -112,10 +77,10 @@ async fn main(spawner: embassy_executor::Spawner) { static MOCK_ESPI_SERVICE: OnceLock = OnceLock::new(); mock_espi_service::Service::init(spawner, &MOCK_ESPI_SERVICE).await; - static TIME_ALARM_SERVICE: OnceLock = OnceLock::new(); - time_alarm_service::Service::init( - &TIME_ALARM_SERVICE, - &spawner, + 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, @@ -124,5 +89,24 @@ async fn main(spawner: embassy_executor::Spawner) { dc_policy, ) .await - .unwrap(); + .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/time-alarm-service-messages/Cargo.toml b/time-alarm-service-messages/Cargo.toml index 65a495363..ac2377989 100644 --- a/time-alarm-service-messages/Cargo.toml +++ b/time-alarm-service-messages/Cargo.toml @@ -15,7 +15,7 @@ num_enum.workspace = true zerocopy.workspace = true [features] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "embedded-mcu-hal/defmt"] [lints] workspace = true From 7e78b6ca02addaaa32b55892fd0c36518232c4d7 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 12:38:59 -0800 Subject: [PATCH 03/16] fix example --- examples/rt685s-evk/src/bin/time_alarm.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 63b7a76c7..5f4d3d305 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -8,11 +8,12 @@ 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, AcpiTimeAlarmResponse}; + use time_alarm_service_messages::{AcpiTimeAlarmRequest, AcpiTimeAlarmResult}; pub struct Service { endpoint: comms::Endpoint, @@ -32,7 +33,7 @@ mod mock_espi_service { impl comms::MailboxDelegate for Service { fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> { - let msg = message.data.get::().ok_or_else(|| { + let msg = message.data.get::().ok_or_else(|| { error!("Mock eSPI service received unknown message type"); comms::MailboxDelegateError::MessageNotFound })?; From 87f8b0b6e6396aaedf6c506ba72a56243ddb3089 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 13:18:55 -0800 Subject: [PATCH 04/16] fix ci --- examples/rt633/Cargo.lock | 50 ++++++++++++++++++-------- examples/rt633/src/bin/espi_battery.rs | 1 + examples/std/Cargo.lock | 8 ++--- time-alarm-service/Cargo.toml | 1 + 4 files changed, 41 insertions(+), 19 deletions(-) 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/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/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index 549c3f79f..9cbd222b0 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -26,6 +26,7 @@ defmt = [ "embassy-time/defmt", "embassy-sync/defmt", "embassy-executor/defmt", + "time-alarm-service-messages/defmt", ] [lints] From 2df7307162a56d5c2abcac920a762f4157dcbf51 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 13:45:22 -0800 Subject: [PATCH 05/16] vet --- supply-chain/audits.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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" From c5f08cedc94b8093367d2812a09a1f13db6b57c8 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 13:59:58 -0800 Subject: [PATCH 06/16] ci fixes --- time-alarm-service-messages/src/acpi_timestamp.rs | 6 +++++- time-alarm-service/src/lib.rs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/time-alarm-service-messages/src/acpi_timestamp.rs b/time-alarm-service-messages/src/acpi_timestamp.rs index 476c8ae0e..013f9984f 100644 --- a/time-alarm-service-messages/src/acpi_timestamp.rs +++ b/time-alarm-service-messages/src/acpi_timestamp.rs @@ -51,7 +51,7 @@ impl From<&AcpiTimestamp> for RawAcpiTimestamp { minute: ts.datetime.minute(), second: ts.datetime.second(), valid_or_padding: 1, // valid - milliseconds: (ts.datetime.nanoseconds() / 1_000_000).try_into().expect("Datetime::nanoseconds() is capped at 10^9 and therefore should always divide by 10^6 into something that fits in u16"), + 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], @@ -142,6 +142,10 @@ pub struct AcpiTimestamp { 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() diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 48ae6a131..77b6f7682 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -222,19 +222,24 @@ impl Service { match select(acpi_command, power_source_change).await { Either::First((respond_to_endpoint, acpi_command)) => { + #[cfg(feature = "defmt")] 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); + #[cfg(feature = "defmt")] info!("[Time/Alarm] Responding with: {:?}", result); + self.endpoint .send(respond_to_endpoint, &result) .await .expect("send returns Infallible"); } Either::Second(new_power_source) => { + #[cfg(feature = "defmt")] info!("[Time/Alarm] Power source changed to {:?}", new_power_source); self.timers From 69d577f76d88908adabb021807049e4dd34dc141 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 14:14:34 -0800 Subject: [PATCH 07/16] pr feedback --- .../src/acpi_timestamp.rs | 2 +- time-alarm-service-messages/src/lib.rs | 20 +++++++++---------- time-alarm-service/src/lib.rs | 6 ++---- time-alarm-service/src/task.rs | 4 ++-- time-alarm-service/src/timer.rs | 15 ++++++-------- 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/time-alarm-service-messages/src/acpi_timestamp.rs b/time-alarm-service-messages/src/acpi_timestamp.rs index 013f9984f..028884099 100644 --- a/time-alarm-service-messages/src/acpi_timestamp.rs +++ b/time-alarm-service-messages/src/acpi_timestamp.rs @@ -28,7 +28,7 @@ struct RawAcpiTimestamp { // 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, - // Millseconds: 0-999. Leap seconds are not supported. + // Milliseconds: 0-999. Leap seconds are not supported. milliseconds: U16, // Time zone: -1440 to 1440 in minutes from UTC, or 2047 if unspecified diff --git a/time-alarm-service-messages/src/lib.rs b/time-alarm-service-messages/src/lib.rs index 2b0b43c01..0fac4cf08 100644 --- a/time-alarm-service-messages/src/lib.rs +++ b/time-alarm-service-messages/src/lib.rs @@ -8,21 +8,21 @@ use core::array::TryFromSliceError; use embedded_services::relay::{MessageSerializationError, SerializableMessage}; /// Message types for the ACPI Time and Alarm device service. -/// These directly analogous to the ACPI Time and Alarm device methods. +/// 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(PartialEq, Clone, Copy)] pub enum AcpiTimeAlarmRequest { - GetCapabilities, // 1: _GCP --> u32 (bitmask), failure: infallible - GetRealTime, // 2: _GRT --> AcpiTimestamp, failure: valid bit = 0 in returned timestamp - SetRealTime(AcpiTimestamp), // 3: _SRT --> u32 (bool), failure: u32::MAX - GetWakeStatus(AcpiTimerId), // 4: _GWS --> u32 (bitmask), failure: infallible - ClearWakeStatus(AcpiTimerId), // 5: _CWS --> u32 (bool), failure: 1 - SetTimerValue(AcpiTimerId, AlarmTimerSeconds), // 6: _STV --> u32 (bool), failure: 1, - GetTimerValue(AcpiTimerId), // 7: _TIV --> u32 (AlarmTimerSeconds), failure: infallible, u32::MAX if disabled - SetExpiredTimerPolicy(AcpiTimerId, AlarmExpiredWakePolicy), // 8: _STP --> u32 (bool), failure: 1 - GetExpiredTimerPolicy(AcpiTimerId), // 9: _TIP --> u32 (AlarmExpiredWakePolicy) failure: infallible + 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(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 77b6f7682..23283d9e9 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -31,9 +31,7 @@ impl From for MailboxDelegateError { fn from(error: TimeAlarmError) -> Self { match error { TimeAlarmError::UnknownCommand => MailboxDelegateError::InvalidData, - TimeAlarmError::DoubleInitError => { - panic!("Should never attempt intitialization as a response to receiving a mailbox message") - } + TimeAlarmError::DoubleInitError => MailboxDelegateError::Other, TimeAlarmError::MailboxFullError => MailboxDelegateError::BufferFull, TimeAlarmError::ClockError(_) => MailboxDelegateError::Other, } @@ -89,7 +87,7 @@ mod time_zone_data { self.storage.write(zerocopy::transmute!(representation)); } - /// Retreives the current time zone / daylight savings time. + /// 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). /// diff --git a/time-alarm-service/src/task.rs b/time-alarm-service/src/task.rs index 8c6e5c0a6..3d694cd73 100644 --- a/time-alarm-service/src/task.rs +++ b/time-alarm-service/src/task.rs @@ -16,12 +16,12 @@ pub async fn command_handler_task(service: &'static Service) { /// 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 timer task"); + 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 timer task"); + 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 index 130691ee8..1d01f1cbf 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -68,10 +68,8 @@ mod persistent_storage { pub fn set_expiration_time(&mut self, expiration_time: Option) { match expiration_time { Some(dt) => { - self.expiration_time_storage - .write(dt.to_unix_time_seconds().try_into().expect( - "Datetime::to_unix_timestamp() returns i64, which should always fit in u32 until the year 2106", - )); + // 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); @@ -193,13 +191,12 @@ impl Timer { // 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()).try_into() - .expect("Users should not have been able to program a time greater than u32::MAX seconds in the future - the ACPI spec prevents it") + 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 )); } - None => self.clear_expiration_time(&mut timer_state) + None => self.clear_expiration_time(&mut timer_state), } }); } From 5f996fa796de888f57b40ff79982d9ee897cf4bc Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 14:37:16 -0800 Subject: [PATCH 08/16] depanic --- time-alarm-service/src/lib.rs | 12 ++++---- time-alarm-service/src/timer.rs | 53 ++++++++++++++++----------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 23283d9e9..3f891ee84 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -231,10 +231,8 @@ impl Service { #[cfg(feature = "defmt")] info!("[Time/Alarm] Responding with: {:?}", result); - self.endpoint - .send(respond_to_endpoint, &result) - .await - .expect("send returns Infallible"); + let _: Result<(), core::convert::Infallible> = + self.endpoint.send(respond_to_endpoint, &result).await; } Either::Second(new_power_source) => { #[cfg(feature = "defmt")] @@ -341,7 +339,11 @@ impl Service { .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()).try_into().expect("Per the ACPI spec, timers are communicated in u32 seconds, so this shouldn't be able to overflow")) + AlarmTimerSeconds( + expiration_time + .to_unix_time_seconds() + .saturating_sub(current_time.to_unix_time_seconds()) as u32, + ) } None => AlarmTimerSeconds::DISABLED, }; diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs index 1d01f1cbf..f2284333d 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -4,7 +4,7 @@ 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; -use embedded_services::GlobalRawMutex; +use embedded_services::{GlobalRawMutex, error}; /// Represents where in the timer lifecycle the current timer is #[derive(Copy, Clone, Debug, PartialEq)] @@ -144,10 +144,6 @@ impl Timer { }); } - // TODO [SPEC] the spec is ambiguous on whether or not this policy should include the number of seconds that have elapsed against it - // (i.e. if the user set it to 60s and 45s have elapsed since we switched to the expired power source, should we report - // 60s or 15s?)- see if we can get a concrete answer on this. - // pub fn get_timer_wake_policy(&self) -> AlarmExpiredWakePolicy { self.timer_state .lock(|timer_state| timer_state.borrow().persistent_storage.get_timer_wake_policy()) @@ -162,9 +158,6 @@ impl Timer { let mut timer_state = timer_state.borrow_mut(); timer_state.persistent_storage.set_timer_wake_policy(wake_policy); - // TODO [SPEC] verify this is correct - the spec isn't particularly clear on what should happen if reprogramming the policy while it's actively ticking down, - // may need to look at the windows acpi implementation or something - // if let WakeState::ExpiredWaitingForPolicyDelay(_, _) = timer_state.wake_state { timer_state.wake_state = WakeState::ExpiredWaitingForPolicyDelay(Self::get_current_datetime(clock_state), 0); @@ -224,23 +217,21 @@ impl Timer { seconds_already_elapsed, ); self.timer_signal.signal(Some( - timer_state.persistent_storage + timer_state + .persistent_storage .get_timer_wake_policy() .0 .saturating_sub(seconds_already_elapsed), )); } } else if let WakeState::ExpiredWaitingForPolicyDelay(wait_start_time, seconds_elapsed_before_wait) = - timer_state.wake_state + timer_state.wake_state { let total_seconds_elapsed_on_policy_delay: u32 = seconds_elapsed_before_wait - + u32::try_from(Self::get_current_datetime(clock_state) + + (Self::get_current_datetime(clock_state) .to_unix_time_seconds() - .saturating_sub(wait_start_time.to_unix_time_seconds())) - .expect("The ACPI spec expresses timeouts in terms of u32s - it's impossible to schedule a timer u32::MAX seconds in the future"); - - timer_state.wake_state = - WakeState::ExpiredWaitingForPowerSource(total_seconds_elapsed_on_policy_delay); + .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 + timer_state.wake_state = WakeState::ExpiredWaitingForPowerSource(total_seconds_elapsed_on_policy_delay); self.timer_signal.signal(None); } }); @@ -287,37 +278,45 @@ impl Timer { // 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::Clear | WakeState::ExpiredOrphaned | WakeState::ExpiredWaitingForPowerSource(_) => { + return false; + } WakeState::Armed | WakeState::ExpiredWaitingForPolicyDelay(_, _) => { let now = Self::get_current_datetime(clock_state); - let expiration_time = timer_state.persistent_storage.get_expiration_time().expect("We should never be in the Armed or ExpiredWaitingForPolicyDelay states if there's no expiration time set"); + let expiration_time = timer_state.persistent_storage.get_expiration_time().unwrap_or_else(|| { + error!("[Time/Alarm] Timer expired when no expiration time was set - this should never happen"); + Datetime::from_unix_time_seconds(0) + }); 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()) - .try_into() - .expect("Users should not have been able to program a time greater than u32::MAX seconds in the future - the ACPI spec prevents it"))); + self.timer_signal.signal(Some( + expiration_time + .to_unix_time_seconds() + .saturating_sub(now.to_unix_time_seconds()) as u32, + )); return false; } 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); + timer_state + .persistent_storage + .set_timer_wake_policy(AlarmExpiredWakePolicy::NEVER); self.clear_expiration_time(&mut timer_state); return true; - } - else { + } 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); + timer_state.wake_state = WakeState::ExpiredWaitingForPowerSource( + timer_state.persistent_storage.get_timer_wake_policy().0, + ); } else { timer_state.wake_state = WakeState::ExpiredWaitingForPowerSource(0); } From 74e5a4d83cd1d09c8a1be72ab2f99de0a4fd3663 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 14:51:34 -0800 Subject: [PATCH 09/16] fix test --- embedded-service/src/ec_type/mod.rs | 205 ---------------------------- 1 file changed, 205 deletions(-) diff --git a/embedded-service/src/ec_type/mod.rs b/embedded-service/src/ec_type/mod.rs index a1b799e22..0a73a17c1 100644 --- a/embedded-service/src/ec_type/mod.rs +++ b/embedded-service/src/ec_type/mod.rs @@ -882,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); - } } From 0709ae9762029197ec6253b3bed95bf442bce396 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 16:33:08 -0800 Subject: [PATCH 10/16] depanic --- time-alarm-service/src/lib.rs | 11 ++- time-alarm-service/src/timer.rs | 156 +++++++++++++++++++++----------- 2 files changed, 111 insertions(+), 56 deletions(-) diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 3f891ee84..a57619f4f 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -205,8 +205,8 @@ impl Service { // 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); + 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?; @@ -256,7 +256,8 @@ impl Service { // TODO [SPEC] section 9.18.7 indicates that when a timer expires, both timers have their wake policies reset, // but I can't find any similar rule for the actual timer value - that seems odd to me, verify that's actually how // it's supposed to work - self.timers + let _ = self + .timers .get_timer(timer_id.get_other_timer_id()) .set_timer_wake_policy(&self.clock_state, AlarmExpiredWakePolicy::NEVER); @@ -305,7 +306,7 @@ impl Service { AcpiTimeAlarmRequest::SetExpiredTimerPolicy(timer_id, timer_policy) => { self.timers .get_timer(timer_id) - .set_timer_wake_policy(&self.clock_state, timer_policy); + .set_timer_wake_policy(&self.clock_state, timer_policy)?; Ok(AcpiTimeAlarmResponse::OkNoData) } AcpiTimeAlarmRequest::SetTimerValue(timer_id, timer_value) => { @@ -324,7 +325,7 @@ impl Service { self.timers .get_timer(timer_id) - .set_expiration_time(&self.clock_state, new_expiration_time); + .set_expiration_time(&self.clock_state, new_expiration_time)?; Ok(AcpiTimeAlarmResponse::OkNoData) } AcpiTimeAlarmRequest::GetExpiredTimerPolicy(timer_id) => Ok(AcpiTimeAlarmResponse::WakePolicy( diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs index f2284333d..e5a6448cf 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -3,7 +3,7 @@ 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; +use embedded_mcu_hal::time::{Datetime, DatetimeClockError}; use embedded_services::{GlobalRawMutex, error}; /// Represents where in the timer lifecycle the current timer is @@ -114,20 +114,26 @@ impl Timer { } } - pub fn start(&self, clock_state: &'static Mutex>, active: bool) { + 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 { @@ -153,16 +159,18 @@ impl Timer { &self, clock_state: &'static Mutex>, wake_policy: AlarmExpiredWakePolicy, - ) { + ) -> Result<(), DatetimeClockError> { self.timer_state.lock(|timer_state| { let mut timer_state = timer_state.borrow_mut(); - timer_state.persistent_storage.set_timer_wake_policy(wake_policy); - if let WakeState::ExpiredWaitingForPolicyDelay(_, _) = timer_state.wake_state { timer_state.wake_state = - WakeState::ExpiredWaitingForPolicyDelay(Self::get_current_datetime(clock_state), 0); + 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(()) }) } @@ -170,7 +178,7 @@ impl Timer { &self, clock_state: &'static Mutex>, expiration_time: Option, - ) { + ) -> Result<(), DatetimeClockError> { self.timer_state.lock(|timer_state| { let mut timer_state = timer_state.borrow_mut(); @@ -179,19 +187,21 @@ impl Timer { match expiration_time { Some(dt) => { - timer_state.persistent_storage.set_expiration_time(expiration_time); - timer_state.wake_state = WakeState::Armed; - // 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()) + .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 { @@ -212,25 +222,54 @@ impl Timer { if !was_active { if let WakeState::ExpiredWaitingForPowerSource(seconds_already_elapsed) = timer_state.wake_state { - timer_state.wake_state = WakeState::ExpiredWaitingForPolicyDelay( - Self::get_current_datetime(clock_state), - seconds_already_elapsed, - ); - self.timer_signal.signal(Some( - timer_state - .persistent_storage - .get_timer_wake_policy() - .0 - .saturating_sub(seconds_already_elapsed), - )); + 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: u32 = seconds_elapsed_before_wait - + (Self::get_current_datetime(clock_state) - .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 + 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); } @@ -283,20 +322,39 @@ impl Timer { } WakeState::Armed | WakeState::ExpiredWaitingForPolicyDelay(_, _) => { - let now = Self::get_current_datetime(clock_state); - let expiration_time = timer_state.persistent_storage.get_expiration_time().unwrap_or_else(|| { - error!("[Time/Alarm] Timer expired when no expiration time was set - this should never happen"); - Datetime::from_unix_time_seconds(0) - }); - 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; + let expiration_time = match timer_state.persistent_storage.get_expiration_time() { + Some(now) => now, + 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); @@ -334,13 +392,9 @@ impl Timer { self.timer_signal.signal(None); } - fn get_current_datetime(clock_state: &'static Mutex>) -> Datetime { - clock_state.lock(|clock_state| { - clock_state - .borrow() - .datetime_clock - .get_current_datetime() - .expect("Datetime clock should have already been initialized before we were constructed") - }) + fn get_current_datetime( + clock_state: &'static Mutex>, + ) -> Result { + clock_state.lock(|clock_state| clock_state.borrow().datetime_clock.get_current_datetime()) } } From 2ca9bbb74365422e4acb881ee3a5a90875bd839b Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 16:39:53 -0800 Subject: [PATCH 11/16] clippy --- time-alarm-service/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index a57619f4f..54d6c1c75 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -96,7 +96,7 @@ mod time_zone_data { (|| -> Result<(AcpiTimeZone, AcpiDaylightSavingsTimeStatus), time_alarm_service_messages::AcpiTimeAlarmError> { Ok((representation.tz.try_into()?, representation.dst.try_into()?)) })() - .unwrap_or_else(|_| (AcpiTimeZone::Unknown, AcpiDaylightSavingsTimeStatus::NotObserved)) + .unwrap_or((AcpiTimeZone::Unknown, AcpiDaylightSavingsTimeStatus::NotObserved)) } } } From aaae2797281ea14fce44da98dfd935e221c3d32e Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 23 Jan 2026 16:51:01 -0800 Subject: [PATCH 12/16] fix comment --- time-alarm-service/src/timer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs index e5a6448cf..4ba7fd024 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -19,7 +19,7 @@ enum WakeState { /// 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 + /// Expired while the policy was set to NEVER, so the timer is effectively dead until reprogrammed ExpiredOrphaned, } From 4648f4c52289ab8067f629c009f68eabcab3b0be Mon Sep 17 00:00:00 2001 From: Billy Price Date: Mon, 26 Jan 2026 10:17:56 -0800 Subject: [PATCH 13/16] pr feedback --- Cargo.lock | 1 + time-alarm-service/src/timer.rs | 2 +- uart-service/Cargo.toml | 1 + uart-service/src/mctp.rs | 7 ++++--- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8f0c2e87..723c4cea1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2366,6 +2366,7 @@ dependencies = [ "mctp-rs", "num_enum", "thermal-service-messages", + "time-alarm-service-messages", ] [[package]] diff --git a/time-alarm-service/src/timer.rs b/time-alarm-service/src/timer.rs index 4ba7fd024..78d46cb85 100644 --- a/time-alarm-service/src/timer.rs +++ b/time-alarm-service/src/timer.rs @@ -323,7 +323,7 @@ impl Timer { WakeState::Armed | WakeState::ExpiredWaitingForPolicyDelay(_, _) => { let expiration_time = match timer_state.persistent_storage.get_expiration_time() { - Some(now) => now, + Some(expiration_time) => expiration_time, None => { error!( "[Time/Alarm] Timer expired when no expiration time was set - this should never happen" diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml index f90ff1e86..3d4ed0122 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 = [] 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 b8e8d9ee24d2df66ad2e94293e490d98fe10de87 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Mon, 26 Jan 2026 10:49:05 -0800 Subject: [PATCH 14/16] fix debug --- Cargo.lock | 1 + time-alarm-service-messages/Cargo.toml | 2 ++ time-alarm-service-messages/src/acpi_timestamp.rs | 6 +++--- time-alarm-service-messages/src/lib.rs | 12 ++++++------ time-alarm-service/Cargo.toml | 2 ++ time-alarm-service/src/lib.rs | 3 --- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 723c4cea1..6efaee808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2172,6 +2172,7 @@ dependencies = [ "defmt 0.3.100", "embedded-mcu-hal", "embedded-services", + "log", "num_enum", "zerocopy", ] diff --git a/time-alarm-service-messages/Cargo.toml b/time-alarm-service-messages/Cargo.toml index ac2377989..04e172f7d 100644 --- a/time-alarm-service-messages/Cargo.toml +++ b/time-alarm-service-messages/Cargo.toml @@ -9,6 +9,7 @@ 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 @@ -16,6 +17,7 @@ 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 index 028884099..c89f2ff32 100644 --- a/time-alarm-service-messages/src/acpi_timestamp.rs +++ b/time-alarm-service-messages/src/acpi_timestamp.rs @@ -61,7 +61,7 @@ impl From<&AcpiTimestamp> for RawAcpiTimestamp { // ------------------------------------------------- -#[derive(Copy, Clone, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum AcpiDaylightSavingsTimeStatus { @@ -79,7 +79,7 @@ pub enum AcpiDaylightSavingsTimeStatus { // ------------------------------------------------- -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct AcpiTimeZoneOffset { minutes_from_utc: i16, // minutes from UTC @@ -133,7 +133,7 @@ impl From for i16 { // ------------------------------------------------- #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(PartialEq, Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct AcpiTimestamp { pub datetime: Datetime, pub time_zone: AcpiTimeZone, diff --git a/time-alarm-service-messages/src/lib.rs b/time-alarm-service-messages/src/lib.rs index 0fac4cf08..0b6329eb0 100644 --- a/time-alarm-service-messages/src/lib.rs +++ b/time-alarm-service-messages/src/lib.rs @@ -12,7 +12,7 @@ use embedded_services::relay::{MessageSerializationError, SerializableMessage}; /// 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(PartialEq, Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum AcpiTimeAlarmRequest { GetCapabilities, // _GCP GetRealTime, // _GRT @@ -25,7 +25,7 @@ pub enum AcpiTimeAlarmRequest { GetExpiredTimerPolicy(AcpiTimerId), // _TIP } -#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] #[repr(u16)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum AcpiTimeAlarmRequestDiscriminant { @@ -186,7 +186,7 @@ impl Default for AlarmExpiredWakePolicy { // ------------------------------------------------- // Timer ID as defined in the ACPI spec. -#[derive(Debug, Clone, Copy, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[derive(Clone, Copy, Debug, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u32)] pub enum AcpiTimerId { @@ -234,7 +234,7 @@ bitfield!( // ------------------------------------------------- -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcpiTimeAlarmResponse { Capabilities(TimeAlarmDeviceCapabilities), @@ -247,7 +247,7 @@ pub enum AcpiTimeAlarmResponse { OkNoData, } -#[derive(Copy, Clone, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[derive(Copy, Clone, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] #[repr(u16)] enum AcpiTimeAlarmResponseDiscriminant { Capabilities = 1, @@ -315,7 +315,7 @@ impl SerializableMessage for AcpiTimeAlarmResponse { } } -#[derive(Debug, Clone, Copy, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[derive(Clone, Copy, Debug, PartialEq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u16)] pub enum AcpiTimeAlarmError { diff --git a/time-alarm-service/Cargo.toml b/time-alarm-service/Cargo.toml index 9cbd222b0..3b3f3fcff 100644 --- a/time-alarm-service/Cargo.toml +++ b/time-alarm-service/Cargo.toml @@ -29,5 +29,7 @@ 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 index 54d6c1c75..58f2582ad 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -220,7 +220,6 @@ impl Service { match select(acpi_command, power_source_change).await { Either::First((respond_to_endpoint, acpi_command)) => { - #[cfg(feature = "defmt")] info!("[Time/Alarm] Received command: {:?}", acpi_command); let result: AcpiTimeAlarmResult = self @@ -228,14 +227,12 @@ impl Service { .await .map_err(|_| time_alarm_service_messages::AcpiTimeAlarmError::UnspecifiedFailure); - #[cfg(feature = "defmt")] info!("[Time/Alarm] Responding with: {:?}", result); let _: Result<(), core::convert::Infallible> = self.endpoint.send(respond_to_endpoint, &result).await; } Either::Second(new_power_source) => { - #[cfg(feature = "defmt")] info!("[Time/Alarm] Power source changed to {:?}", new_power_source); self.timers From 8420cbd9519e7280722acaeb1853703fd4589112 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Mon, 26 Jan 2026 10:54:56 -0800 Subject: [PATCH 15/16] fix missing feature dependency --- uart-service/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/uart-service/Cargo.toml b/uart-service/Cargo.toml index 3d4ed0122..8049d9d28 100644 --- a/uart-service/Cargo.toml +++ b/uart-service/Cargo.toml @@ -41,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"] From 2b56070cf075138e82de85b867d674210afc7a7c Mon Sep 17 00:00:00 2001 From: Billy Price Date: Tue, 27 Jan 2026 09:10:07 -0800 Subject: [PATCH 16/16] migrate todos to github tasks --- time-alarm-service/src/lib.rs | 29 +++++++---------------------- time-alarm-service/src/task.rs | 7 ------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 58f2582ad..10d5732c9 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -156,12 +156,6 @@ pub struct Service { } impl Service { - // TODO [DYN] if we want to allow taking the HAL traits as concrete types rather than as dyn references, we'll likely need to make this a macro - // in order to accommodate the restriction that embassy tasks can't have generic parameters. When we do that, it may be worthwhile to - // also investigate ways to take the backing storage as a slice rather than as a bunch of individual references - currently, we can't - // take a slice of the array because that would be a slice of trait impls and we need dyn references here to accommodate the constraints - // on embassy task implementation. - // pub async fn init( service_storage: &'static OnceLock, backing_clock: &'static mut impl DatetimeClock, @@ -250,9 +244,6 @@ impl Service { let timer = self.timers.get_timer(timer_id); loop { timer.wait_until_wake(&self.clock_state).await; - // TODO [SPEC] section 9.18.7 indicates that when a timer expires, both timers have their wake policies reset, - // but I can't find any similar rule for the actual timer value - that seems odd to me, verify that's actually how - // it's supposed to work let _ = self .timers .get_timer(timer_id.get_other_timer_id()) @@ -282,16 +273,13 @@ impl Service { 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); - - // TODO [SPEC] the spec is ambiguous on whether or not we should adjust any outstanding timers based on the new time - see if we can find an answer elsewhere - Ok(AcpiTimeAlarmResponse::OkNoData) - }) - } + 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)) @@ -360,9 +348,6 @@ impl comms::MailboxDelegate for Service { .map_err(|_| MailboxDelegateError::BufferFull)?; Ok(()) } else { - // TODO [COMMS] right now, if pushing the message to the channel fails, the error that we return this gets - // discarded by our caller and we have no opportunity to raise a failure. Fixing that probably - // requires changes in the mailbox system, so we're ignoring it for now. Err(comms::MailboxDelegateError::InvalidData) } } diff --git a/time-alarm-service/src/task.rs b/time-alarm-service/src/task.rs index 3d694cd73..d1f1e6d44 100644 --- a/time-alarm-service/src/task.rs +++ b/time-alarm-service/src/task.rs @@ -1,13 +1,6 @@ use crate::{AcpiTimerId, Service}; use embedded_services::info; -// TODO This pattern of pushing task declaration to the 'application' level allows us to -// avoid bringing in the async executor dependency into the service crate itself, but -// it also means that we can't really construct and start a task in a single motion, -// which means it's possible to forget to start a task after constructing the service -// and have the service misbehave. Investigate ways to improve that to make the API -// less error-prone. - /// 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");