From 514fdbfeb43462000e28e490b2204429273fe8aa Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 1 Oct 2026 18:09:54 +0000 Subject: [PATCH] Wire the QEMU TimeAlarm wake fixture Configure an explicit AC or DC source before starting the timer runner and drive its latched wake level on GPIO1. Disable initial fixture alarms to keep startup idle-low and leave ordinary initialization unchanged. Preserve upstream's shared HID TimeAlarm and MCTP services and the one-argument initializer. Pin the rebased library wake changes and retain the HAL's zero-gap I2C transaction-boundary fix. Assisted-by: GitHub Copilot:gpt-6-astra Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- platform/dev-qemu/Cargo.lock | 35 ++++++++-------- platform/dev-qemu/Cargo.toml | 7 +++- platform/dev-qemu/README.md | 16 ++++++++ platform/dev-qemu/src/board.rs | 10 ++++- platform/dev-qemu/src/main.rs | 41 +++++++++++++++++++ platform/platform-common/src/mock/mod.rs | 10 ++++- .../platform-common/src/mock/time_alarm.rs | 34 ++++++++++----- 7 files changed, 123 insertions(+), 30 deletions(-) diff --git a/platform/dev-qemu/Cargo.lock b/platform/dev-qemu/Cargo.lock index 03e2b6c..848c0f0 100644 --- a/platform/dev-qemu/Cargo.lock +++ b/platform/dev-qemu/Cargo.lock @@ -29,7 +29,7 @@ dependencies = [ [[package]] name = "battery-service" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "battery-service-interface", "defmt 0.3.100", @@ -42,7 +42,7 @@ dependencies = [ [[package]] name = "battery-service-interface" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embedded-batteries-async", @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "battery-service-relay" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "battery-service-interface", "defmt 0.3.100", @@ -292,6 +292,7 @@ dependencies = [ "semihosting", "static_cell", "time-alarm-service", + "time-alarm-service-interface", "time-alarm-service-relay", "uart-service", ] @@ -389,7 +390,7 @@ dependencies = [ [[package]] name = "embassy-qemu-riscv" version = "0.2.1" -source = "git+https://github.com/kurtjd/qemu-riscv-rs#60430f9284ecc00ca79fb3b3d5e23b851b7e0336" +source = "git+https://github.com/dymk/qemu-riscv-rs?rev=7c149e827f13335962089e5d11d2a5432c487b1f#7c149e827f13335962089e5d11d2a5432c487b1f" dependencies = [ "critical-section", "defmt 1.1.0", @@ -630,7 +631,7 @@ dependencies = [ [[package]] name = "embedded-services" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "bitfield 0.17.0", "cortex-m", @@ -748,7 +749,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hidi2c-target-service" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embassy-futures", @@ -819,7 +820,7 @@ dependencies = [ [[package]] name = "mctp-rs" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "bit-register 0.1.0 (git+https://github.com/OpenDevicePartnership/odp-utilities)", "crc", @@ -894,7 +895,7 @@ dependencies = [ [[package]] name = "odp-service-common" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "embedded-services", "static_cell", @@ -959,7 +960,7 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "power-policy-interface" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embassy-sync 0.8.0", @@ -1001,7 +1002,7 @@ dependencies = [ [[package]] name = "qemu-riscv-pac" version = "0.1.0" -source = "git+https://github.com/kurtjd/qemu-riscv-rs#60430f9284ecc00ca79fb3b3d5e23b851b7e0336" +source = "git+https://github.com/dymk/qemu-riscv-rs?rev=7c149e827f13335962089e5d11d2a5432c487b1f#7c149e827f13335962089e5d11d2a5432c487b1f" dependencies = [ "critical-section", "riscv", @@ -1261,7 +1262,7 @@ dependencies = [ [[package]] name = "thermal-service" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embassy-futures", @@ -1278,7 +1279,7 @@ dependencies = [ [[package]] name = "thermal-service-interface" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embassy-time", @@ -1289,7 +1290,7 @@ dependencies = [ [[package]] name = "thermal-service-relay" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embedded-services", @@ -1330,7 +1331,7 @@ dependencies = [ [[package]] name = "time-alarm-service" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embassy-futures", @@ -1346,7 +1347,7 @@ dependencies = [ [[package]] name = "time-alarm-service-interface" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", @@ -1358,7 +1359,7 @@ dependencies = [ [[package]] name = "time-alarm-service-relay" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "bitfield 0.17.0", "defmt 0.3.100", @@ -1439,7 +1440,7 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "uart-service" version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#b2914600404371ba150093bec40d4e7e116b3369" +source = "git+https://github.com/OpenDevicePartnership/embedded-services?branch=main#baf1a1577c71375ec0ee33830b874f40e17b9423" dependencies = [ "defmt 0.3.100", "embassy-sync 0.8.0", diff --git a/platform/dev-qemu/Cargo.toml b/platform/dev-qemu/Cargo.toml index 0bbda45..50803d6 100644 --- a/platform/dev-qemu/Cargo.toml +++ b/platform/dev-qemu/Cargo.toml @@ -39,10 +39,14 @@ style = "deny" [package.metadata.cargo-machete] ignored = ["riscv-rt"] +[features] +time-alarm-wake = ["dep:time-alarm-service-interface"] + [dependencies] riscv-rt = "0.17.0" -embassy-qemu-riscv = { git = "https://github.com/kurtjd/qemu-riscv-rs", features = [ +# Restore upstream after the zero-gap I2C transaction-boundary fix is merged. +embassy-qemu-riscv = { git = "https://github.com/dymk/qemu-riscv-rs", rev = "7c149e827f13335962089e5d11d2a5432c487b1f", features = [ "defmt", ] } embassy-executor = { version = "0.10.0", features = [ @@ -68,6 +72,7 @@ time-alarm-service-relay = { git = "https://github.com/OpenDevicePartnership/emb ] } odp-service-common = { git = "https://github.com/OpenDevicePartnership/embedded-services", branch = "main" } platform-common = { path = "../platform-common", features = ["mock"] } +time-alarm-service-interface = { git = "https://github.com/OpenDevicePartnership/embedded-services", branch = "main", optional = true } static_cell = "2.1.0" defmt = "0.3.6" diff --git a/platform/dev-qemu/README.md b/platform/dev-qemu/README.md index 05ced1d..792caae 100644 --- a/platform/dev-qemu/README.md +++ b/platform/dev-qemu/README.md @@ -25,6 +25,22 @@ E.g. to connect with [ec-test-app](https://github.com/OpenDevicePartnership/odp- To run without logging (skips `defmt-print`): `cargo run-headless` +## CPU retention-wake fixture + +Build with `ODP_WAKE_SOURCE=ac cargo build --locked --release --features time-alarm-wake` +or select `dc` explicitly. The feature requires one of those build-time +values; a missing or invalid selection fails at startup rather than assuming +a power source. Both alarms are disabled and the source is selected before +the TimeAlarm runner starts. Ordinary builds retain their initialization policy. + +This test-only source selection is not hardware AC/DC detection. GPIO1 is an +active-high wake-request level driven by the real TimeAlarm service; GPIO0 +remains the HID interrupt. HID-over-I2C and UART/MCTP continue to share that +same TimeAlarm service. Clear or disable the corresponding timers through +the existing relay commands to release their wake latches. The CPU retention +test harness connects the separate `ec-gpio1` channel to the host GPIO1 and +verifies actual standby return; the EC log alone does not prove host resume. + ## Sockets While `dev-qemu` is running, the `ec` machine exposes two sockets that external programs (such as another QEMU instance) can connect to: diff --git a/platform/dev-qemu/src/board.rs b/platform/dev-qemu/src/board.rs index a9a087f..73e5b04 100644 --- a/platform/dev-qemu/src/board.rs +++ b/platform/dev-qemu/src/board.rs @@ -24,6 +24,8 @@ pub struct Board { pub i2c: I2c<'static, I2cAsync>, /// Interrupt line the HID device drives to signal the host (active low). pub gpio: Output<'static>, + #[cfg(feature = "time-alarm-wake")] + pub wake_gpio: Output<'static>, } impl BoardIo for Board { @@ -41,6 +43,12 @@ impl BoardIo for Board { // Start high (since this is an active-low signal) let gpio = Output::new(p.GPIO0, Level::High); - Board { uart, i2c, gpio } + Board { + uart, + i2c, + gpio, + #[cfg(feature = "time-alarm-wake")] + wake_gpio: Output::new(p.GPIO1, Level::Low), + } } } diff --git a/platform/dev-qemu/src/main.rs b/platform/dev-qemu/src/main.rs index 0457b92..016b1a0 100644 --- a/platform/dev-qemu/src/main.rs +++ b/platform/dev-qemu/src/main.rs @@ -8,6 +8,8 @@ mod mctp; use board::Board; use defmt_semihosting as _; use embassy_executor::Spawner; +#[cfg(feature = "time-alarm-wake")] +use embedded_services::info; use platform_common::board::BoardIo; use semihosting as _; // Panic handler @@ -15,10 +17,49 @@ use semihosting as _; // Panic handler async fn main(spawner: Spawner) { let p = embassy_qemu_riscv::init(); let board = Board::init(p); + #[cfg(not(feature = "time-alarm-wake"))] let (relay, time_alarm) = platform_common::mock::init_with_time_alarm(spawner).await; + #[cfg(feature = "time-alarm-wake")] + let (relay, time_alarm) = { + use time_alarm_service_interface::{AcpiTimerId, AlarmTimerSeconds, TimeAlarmService}; + let source = match option_env!("ODP_WAKE_SOURCE") { + Some("ac") => AcpiTimerId::AcPower, + Some("dc") => AcpiTimerId::DcPower, + _ => panic!("Build the wake fixture with ODP_WAKE_SOURCE=ac or dc"), + }; + info!("TimeAlarm wake source: {:?}", source); + let (relay, service) = platform_common::mock::init_with_time_alarm_configure(spawner, |service| { + for timer in [AcpiTimerId::AcPower, AcpiTimerId::DcPower] { + service + .set_timer_value(timer, AlarmTimerSeconds::DISABLED) + .expect("Failed to disable initial fixture alarm"); + } + service.set_power_source(source); + }) + .await; + spawner.spawn(time_alarm_wake(service, board.wake_gpio).expect("Failed to spawn TimeAlarm wake task")); + (relay, service) + }; // This allows thermal and battery to continue receiving requests over uart/mctp, but also allows // time-alarm to simultaneously receive requests over uart/mctp or hidi2c spawner.spawn(mctp::uart_service(board.uart, relay).expect("Failed to spawn UART service task")); hid::init(spawner, board.i2c, board.gpio, time_alarm).await; } + +#[cfg(feature = "time-alarm-wake")] +#[embassy_executor::task] +async fn time_alarm_wake( + service: platform_common::mock::time_alarm::TimeAlarmService, + mut gpio: embassy_qemu_riscv::gpio::Output<'static>, +) { + loop { + let requested = service.wait_for_wake_signal().await; + if requested { + gpio.set_high(); + } else { + gpio.set_low(); + } + info!("TimeAlarm wake requested: {}", requested); + } +} diff --git a/platform/platform-common/src/mock/mod.rs b/platform/platform-common/src/mock/mod.rs index f04d673..6822cb3 100644 --- a/platform/platform-common/src/mock/mod.rs +++ b/platform/platform-common/src/mock/mod.rs @@ -24,12 +24,20 @@ pub async fn init(spawner: embassy_executor::Spawner) -> MockOdpRelayHandler { pub async fn init_with_time_alarm( spawner: embassy_executor::Spawner, ) -> (MockOdpRelayHandler, time_alarm_service::Service<'static>) { + init_with_time_alarm_configure(spawner, |_| {}).await +} + +/// Configure TimeAlarm before its runner starts and retain its board control handle. +pub async fn init_with_time_alarm_configure( + spawner: embassy_executor::Spawner, + configure: impl FnOnce(time_alarm::TimeAlarmService), +) -> (MockOdpRelayHandler, time_alarm::TimeAlarmService) { embedded_services::info!("Initializing mock services..."); embedded_services::init().await; let thermal = thermal::init(spawner).await; let battery = battery::init(spawner).await; - let tas = time_alarm::init(spawner).await; + let tas = time_alarm::init_with(spawner, configure).await; let relay = MockOdpRelayHandler::new( battery_service_relay::BatteryServiceRelayHandler::new(battery), diff --git a/platform/platform-common/src/mock/time_alarm.rs b/platform/platform-common/src/mock/time_alarm.rs index 68005d5..e5d896a 100644 --- a/platform/platform-common/src/mock/time_alarm.rs +++ b/platform/platform-common/src/mock/time_alarm.rs @@ -2,9 +2,16 @@ use embedded_services::info; use static_cell::StaticCell; use time_alarm_service as tas; -type TimeAlarmService = tas::Service<'static>; +pub type TimeAlarmService = tas::Service<'static>; pub async fn init(spawner: embassy_executor::Spawner) -> TimeAlarmService { + init_with(spawner, |_| {}).await +} + +pub async fn init_with( + spawner: embassy_executor::Spawner, + configure: impl FnOnce(TimeAlarmService), +) -> TimeAlarmService { info!("Initializing time and alarm service..."); static TZ_STORAGE: StaticCell> = StaticCell::new(); @@ -25,15 +32,22 @@ pub async fn init(spawner: embassy_executor::Spawner) -> TimeAlarmService { static CLOCK: StaticCell = StaticCell::new(); let clock = CLOCK.init(tas::mock::MockDatetimeClock::new_running()); - let service = odp_service_common::spawn_service!(spawner, TimeAlarmService, |resources| tas::Service::new( - resources, - clock, - tz_storage, - ac_exp_storage, - ac_pol_storage, - dc_exp_storage, - dc_pol_storage, - )) + let service = odp_service_common::spawn_service!(spawner, TimeAlarmService, |resources| async { + tas::Service::new( + resources, + clock, + tz_storage, + ac_exp_storage, + ac_pol_storage, + dc_exp_storage, + dc_pol_storage, + ) + .await + .map(|(service, runner)| { + configure(service); + (service, runner) + }) + }) .expect("Failed to initialize time-alarm service"); service