Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions platform/dev-qemu/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion platform/dev-qemu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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"
Expand Down
16 changes: 16 additions & 0 deletions platform/dev-qemu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 9 additions & 1 deletion platform/dev-qemu/src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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),
}
}
}
41 changes: 41 additions & 0 deletions platform/dev-qemu/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,58 @@ 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

#[embassy_executor::main]
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);
}
}
10 changes: 9 additions & 1 deletion platform/platform-common/src/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
34 changes: 24 additions & 10 deletions platform/platform-common/src/mock/time_alarm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<tas::mock::MockNvramStorage<'static>> = StaticCell::new();
Expand All @@ -25,15 +32,22 @@ pub async fn init(spawner: embassy_executor::Spawner) -> TimeAlarmService {
static CLOCK: StaticCell<tas::mock::MockDatetimeClock> = 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
Expand Down
Loading