From 514fdbfeb43462000e28e490b2204429273fe8aa Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 1 Oct 2026 18:09:54 +0000 Subject: [PATCH 1/2] 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 From f27725df971bb7ed0309e0da879ce7a87002d146 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 1 Oct 2026 20:47:27 +0000 Subject: [PATCH 2/2] Select TimeAlarm source from emulated GPIO2 Require an initialized input before starting the runner and track source changes with GPIO level interrupts. Preserve default builds and the construction-time wake fixtures. Assisted-by: GitHub Copilot:gpt-6-astra Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- platform/dev-qemu/Cargo.lock | 4 +-- platform/dev-qemu/Cargo.toml | 5 ++-- platform/dev-qemu/README.md | 22 +++++++++++++++++ platform/dev-qemu/src/board.rs | 9 +++++++ platform/dev-qemu/src/main.rs | 45 +++++++++++++++++++++++++++++++++- 5 files changed, 80 insertions(+), 5 deletions(-) diff --git a/platform/dev-qemu/Cargo.lock b/platform/dev-qemu/Cargo.lock index 848c0f0..1827c29 100644 --- a/platform/dev-qemu/Cargo.lock +++ b/platform/dev-qemu/Cargo.lock @@ -390,7 +390,7 @@ dependencies = [ [[package]] name = "embassy-qemu-riscv" version = "0.2.1" -source = "git+https://github.com/dymk/qemu-riscv-rs?rev=7c149e827f13335962089e5d11d2a5432c487b1f#7c149e827f13335962089e5d11d2a5432c487b1f" +source = "git+https://github.com/dymk/qemu-riscv-rs?rev=a1ff86d62c683928464bf310de7d8ad965d5bd9b#a1ff86d62c683928464bf310de7d8ad965d5bd9b" dependencies = [ "critical-section", "defmt 1.1.0", @@ -1002,7 +1002,7 @@ dependencies = [ [[package]] name = "qemu-riscv-pac" version = "0.1.0" -source = "git+https://github.com/dymk/qemu-riscv-rs?rev=7c149e827f13335962089e5d11d2a5432c487b1f#7c149e827f13335962089e5d11d2a5432c487b1f" +source = "git+https://github.com/dymk/qemu-riscv-rs?rev=a1ff86d62c683928464bf310de7d8ad965d5bd9b#a1ff86d62c683928464bf310de7d8ad965d5bd9b" dependencies = [ "critical-section", "riscv", diff --git a/platform/dev-qemu/Cargo.toml b/platform/dev-qemu/Cargo.toml index 50803d6..4bfea21 100644 --- a/platform/dev-qemu/Cargo.toml +++ b/platform/dev-qemu/Cargo.toml @@ -41,12 +41,13 @@ ignored = ["riscv-rt"] [features] time-alarm-wake = ["dep:time-alarm-service-interface"] +time-alarm-power-input = ["time-alarm-wake"] [dependencies] riscv-rt = "0.17.0" -# 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 = [ +# Restore upstream after the zero-gap I2C transaction-boundary fix and input-validity support are merged. +embassy-qemu-riscv = { git = "https://github.com/dymk/qemu-riscv-rs", rev = "a1ff86d62c683928464bf310de7d8ad965d5bd9b", features = [ "defmt", ] } embassy-executor = { version = "0.10.0", features = [ diff --git a/platform/dev-qemu/README.md b/platform/dev-qemu/README.md index 792caae..247874b 100644 --- a/platform/dev-qemu/README.md +++ b/platform/dev-qemu/README.md @@ -41,6 +41,28 @@ 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. +## Emulated runtime AC/DC input + +`cargo build --release --features time-alarm-power-input` includes the wake +output and uses GPIO2 (high = AC, low = DC) instead of `ODP_WAKE_SOURCE`. +The input must be explicitly initialized by the QEMU model before startup; +the firmware rejects an unset validity bit rather than assuming DC. Source +selection happens before the TimeAlarm runner starts. Socket bytes alone do +not establish validity. + +Use the GPIO2-enabled QEMU model with `input-reset-mask=4` and `input-reset=4` +for cold AC, or `input-reset=0` for cold DC, and its `ec-gpio2` control socket. +Raw bytes `01` and `00` select AC and DC at runtime. The dedicated input task +resamples after opposite-level waits on the shared GPIO interrupt. This is a +level interface, not an event counter: a transient that returns before the +task samples it is not a recorded power-source change. + +The model retains the last valid source across control-socket disconnects +and EC warm resets, and diagnoses malformed bytes. GPIO1 remains the wake +output and GPIO0 remains the HID interrupt. The default build and the explicit +construction-time `time-alarm-wake` fixture are unchanged. This emulated +external input neither detects physical power nor reports Windows power state. + ## 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 73e5b04..fdc53b1 100644 --- a/platform/dev-qemu/src/board.rs +++ b/platform/dev-qemu/src/board.rs @@ -13,6 +13,11 @@ bind_interrupts!(struct Irqs { I2C_TARGET => target::InterruptHandler; }); +#[cfg(feature = "time-alarm-power-input")] +bind_interrupts!(struct GpioIrqs { + GPIO => embassy_qemu_riscv::gpio::InterruptHandler; +}); + /// Board IO for the dev-qemu platform. /// /// This minimal development board provides a UART interface for ODP service @@ -26,6 +31,8 @@ pub struct Board { pub gpio: Output<'static>, #[cfg(feature = "time-alarm-wake")] pub wake_gpio: Output<'static>, + #[cfg(feature = "time-alarm-power-input")] + pub power_input: embassy_qemu_riscv::gpio::Input<'static, embassy_qemu_riscv::gpio::Async>, } impl BoardIo for Board { @@ -49,6 +56,8 @@ impl BoardIo for Board { gpio, #[cfg(feature = "time-alarm-wake")] wake_gpio: Output::new(p.GPIO1, Level::Low), + #[cfg(feature = "time-alarm-power-input")] + power_input: embassy_qemu_riscv::gpio::Input::new_async(p.GPIO2, GpioIrqs), } } } diff --git a/platform/dev-qemu/src/main.rs b/platform/dev-qemu/src/main.rs index 016b1a0..a7beec9 100644 --- a/platform/dev-qemu/src/main.rs +++ b/platform/dev-qemu/src/main.rs @@ -22,13 +22,22 @@ async fn main(spawner: Spawner) { #[cfg(feature = "time-alarm-wake")] let (relay, time_alarm) = { use time_alarm_service_interface::{AcpiTimerId, AlarmTimerSeconds, TimeAlarmService}; + #[cfg(not(feature = "time-alarm-power-input"))] 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| { + #[cfg(feature = "time-alarm-power-input")] + let source = { + assert!( + board.power_input.is_initialized(), + "TimeAlarm GPIO2 power input must be explicitly initialized" + ); + power_source(&board.power_input) + }; + info!("TimeAlarm wake source: {:?}", source); for timer in [AcpiTimerId::AcPower, AcpiTimerId::DcPower] { service .set_timer_value(timer, AlarmTimerSeconds::DISABLED) @@ -38,6 +47,10 @@ async fn main(spawner: Spawner) { }) .await; spawner.spawn(time_alarm_wake(service, board.wake_gpio).expect("Failed to spawn TimeAlarm wake task")); + #[cfg(feature = "time-alarm-power-input")] + spawner.spawn( + time_alarm_power_input(service, board.power_input).expect("Failed to spawn TimeAlarm power input task"), + ); (relay, service) }; @@ -63,3 +76,33 @@ async fn time_alarm_wake( info!("TimeAlarm wake requested: {}", requested); } } + +#[cfg(feature = "time-alarm-power-input")] +fn power_source( + input: &embassy_qemu_riscv::gpio::Input<'_, embassy_qemu_riscv::gpio::Async>, +) -> time_alarm_service_interface::AcpiTimerId { + use time_alarm_service_interface::AcpiTimerId; + if input.is_high() { + AcpiTimerId::AcPower + } else { + AcpiTimerId::DcPower + } +} + +#[cfg(feature = "time-alarm-power-input")] +#[embassy_executor::task] +async fn time_alarm_power_input( + service: platform_common::mock::time_alarm::TimeAlarmService, + mut input: embassy_qemu_riscv::gpio::Input<'static, embassy_qemu_riscv::gpio::Async>, +) { + use time_alarm_service_interface::AcpiTimerId; + loop { + let source = power_source(&input); + service.set_power_source(source); + info!("TimeAlarm power input: {:?}", source); + match source { + AcpiTimerId::AcPower => input.wait_for_low().await, + AcpiTimerId::DcPower => input.wait_for_high().await, + } + } +}