Skip to content
Draft
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
29 changes: 28 additions & 1 deletion ec/test-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ec-test-cli

## Overview
Command-line tool for testing EC features (thermal, battery, RTC). Each command maps directly to an EC data source trait method — it executes the request, prints the result, and exits.
Command-line tool for testing EC features (thermal, battery, RTC) and native PCC connectivity. Commands execute the request, print the result, and exit.

See [ODP Documentation](https://opendevicepartnership.github.io/documentation/guide/overview.html) for details on EC specification.

Expand Down Expand Up @@ -37,6 +37,33 @@ Use `ec-test-cli --help` and `ec-test-cli <COMMAND> --help` to see available com

Setter commands print nothing on success — exit code 0 indicates success.

## Native PCC (Windows ARM64)

Use `--source acpi` with the native-PCC-enabled `ectest` driver and the QEMU
platform's Type 3 subspace 0. The driver's native interface is experimental
and currently gated to Windows ARM64 10.0.28000.2605 and the matching PCCT.

```text
ec-test-cli --source acpi pcc probe
ec-test-cli --source acpi pcc ping --sequence 42
```

`probe` prints the native interface metadata and query status without sending
an EC command. A successful probe does not prove that the EC can respond.

`ping` requires dev-qemu's PCC responder running over the host/EC eSPI link.
It sends command `1` with an eight-byte `PING` marker and little-endian `u32`
sequence, then checks the returned status, `PONG` marker, and matching sequence.
Omit `--sequence` to generate a value. Successful output includes:

```text
PCC execute: status=0x00000000
PCC response: PONG sequence=42
```

Failed queries, executions, and malformed or mismatched replies exit nonzero.
The `pcc` commands are Windows-only and do not use the serial or mock sources.

## Raw ACPI evaluation (Windows only)

The `eval` command evaluates an arbitrary ACPI method by name and prints its return
Expand Down
15 changes: 15 additions & 0 deletions ec/test-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ pub enum Command {
/// Evaluate a raw ACPI method (Windows `--source acpi` only).
#[cfg(target_os = "windows")]
Eval(EvalCommand),
/// Query or exercise native PCC through ectest (Windows `--source acpi` only).
#[cfg(target_os = "windows")]
#[command(subcommand)]
Pcc(PccCommand),
}

#[cfg(target_os = "windows")]
#[derive(Subcommand)]
pub enum PccCommand {
Probe,
Ping {
/// Sequence to echo; generated when omitted.
#[arg(long)]
sequence: Option<u32>,
},
}

/// Raw ACPI method evaluation arguments.
Expand Down
2 changes: 2 additions & 0 deletions ec/test-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
pub mod battery;
#[cfg(target_os = "windows")]
pub mod eval;
#[cfg(any(test, target_os = "windows"))]
pub mod pcc;
pub mod rtc;
pub mod script;
pub mod thermal;
81 changes: 81 additions & 0 deletions ec/test-cli/src/commands/pcc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#[cfg(target_os = "windows")]
use crate::cli::PccCommand;
#[cfg(target_os = "windows")]
use ec_test_lib::pcc::Pcc;

fn ping_request(sequence: u32) -> [u8; 8] {
let mut request = *b"PING\0\0\0\0";
request[4..].copy_from_slice(&sequence.to_le_bytes());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we give the ping request and pong response proper types? I'd rather have Ping { sequence } with an infallible encoder, and parse the reply into a Pong before checking that it belongs to this request. The copy here is fixed-size, so I'm not claiming it is expensive, but the protocol is currently just byte arrays assembled and interpreted by convention.

request
}

fn pong_sequence(message: &[u8], expected: u32) -> Result<u32, &'static str> {
if message.len() < 8 || &message[..4] != b"PONG" {
return Err("PCC reply is not PONG");
}
let sequence = u32::from_le_bytes([message[4], message[5], message[6], message[7]]);
if sequence != expected {
return Err("PCC PONG sequence mismatch");
}
Ok(sequence)
}

#[cfg(target_os = "windows")]
pub fn run(command: &PccCommand) -> Result<(), Box<dyn std::error::Error>> {
let pcc = Pcc::new()?;
match command {
PccCommand::Probe => {
let response = pcc.probe()?;
println!(
"PCC probe: status=0x{:08x} interface={} subspace={} type={} size={} flags=0x{:x} latency_us={} max_rate={} acpi_timestamp=0x{:08x} acpi_image_size=0x{:x}",
response.query_status as u32,
response.interface_version,
response.subspace_id,
response.subspace_type,
response.subspace_size,
response.flags,
response.nominal_latency,
response.maximum_periodic_rate,
response.acpi_timestamp,
response.acpi_image_size
);
if response.query_status < 0 {
return Err(format!("PCC probe failed: NTSTATUS 0x{:08x}", response.query_status as u32).into());
}
}
PccCommand::Ping { sequence } => {
let sequence = match *sequence {
Some(sequence) => sequence,
None => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.subsec_nanos(),
};
let response = pcc.execute(1, &ping_request(sequence))?;
println!("PCC execute: status=0x{:08x}", response.status as u32);
if response.status < 0 {
return Err(format!("PCC execute failed: NTSTATUS 0x{:08x}", response.status as u32).into());
}
let reply_sequence = pong_sequence(&response.message, sequence)?;
println!("PCC response: PONG sequence={reply_sequence}");
}
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn ping_contains_marker_and_little_endian_sequence() {
assert_eq!(ping_request(0x1234_5678), *b"PING\x78\x56\x34\x12");
}

#[test]
fn pong_requires_matching_marker_and_sequence() {
assert_eq!(pong_sequence(b"PONG\x78\x56\x34\x12", 0x1234_5678), Ok(0x1234_5678));
assert!(pong_sequence(b"PING\x78\x56\x34\x12", 0x1234_5678).is_err());
assert!(pong_sequence(b"PONG\x78\x56\x34\x12", 0x1234_5679).is_err());
assert!(pong_sequence(b"PONG", 0x1234_5678).is_err());
}
}
10 changes: 10 additions & 0 deletions ec/test-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ fn dispatch<S: Source>(source: S, command: Command) -> Result<(), Box<dyn std::e
// `eval` requires the concrete ACPI source and is handled in `main`.
#[cfg(target_os = "windows")]
Command::Eval(_) => unreachable!("eval is dispatched before reaching a generic source"),
#[cfg(target_os = "windows")]
Command::Pcc(_) => unreachable!("pcc is dispatched before reaching a generic source"),
}
}

Expand All @@ -35,6 +37,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
};
}

#[cfg(target_os = "windows")]
if let Command::Pcc(cmd) = &cli.command {
return match cli.source {
SourceKind::Acpi => commands::pcc::run(cmd),
_ => Err("`pcc` is only supported with `--source acpi`".into()),
};
}

match cli.source {
SourceKind::Mock => dispatch(ec_test_lib::mock::Mock::default(), cli.command),

Expand Down
3 changes: 3 additions & 0 deletions ec/test-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub mod acpi;
#[cfg(target_os = "windows")]
pub mod windows;

#[cfg(target_os = "windows")]
pub mod pcc;

pub mod mock;
pub mod serial;
pub mod ucsi;
Expand Down
Loading
Loading