Skip to content
Merged
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
32 changes: 32 additions & 0 deletions EXAMPLES_ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,38 @@ Capsule Header
Type: Framework Retimer23 (Right)
```

## Raw EC Host Commands

Send an arbitrary EC host command by specifying a command ID, version, and
optional payload bytes. The response is displayed in xxd-style hex+ASCII format.

```
# Send EC_CMD_GET_VERSION (0x0002) with version 0, no payload
> sudo framework_tool --host-command 0x0002 0
Response (120 bytes):
00000000: 7375 6e66 6c6f 7765 722d 332e 302e 332d sunflower-3.0.3-
00000010: 3838 6664 6135 3400 0000 0000 0000 0000 88fda54.........
00000020: 7375 6e66 6c6f 7765 722d 332e 302e 332d sunflower-3.0.3-
00000030: 3838 6664 6135 3400 0000 0000 0000 0000 88fda54.........
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0100 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 ........

# Query supported versions of EC_CMD_GET_VERSION (0x0002)
# EC_CMD_GET_CMD_VERSIONS (0x0008), version 0, payload: command ID byte
# Command 2 supports version 0 and 1 (0b11 = 3)
> framework_tool --host-command 0x0008 0 2
Response (24 bytes):
00000000: 0300 0000 0000 0000 0000 0000 0000 0000 ................
00000010: 0000 0000 0000 0000 ........
# Command 1 only supports version 0 (0b01 = 1)
> framework_tool --host-command 0x0008 0 1
Response (24 bytes):
00000000: 0100 0000 0000 0000 0000 0000 0000 0000 ................
00000010: 0000 0000 0000 0000 ........
```

## Version Check

Check if the firmware version is what you expect, returns exit code 0 on
Expand Down
6 changes: 5 additions & 1 deletion completions/bash/framework_tool
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ _framework_tool() {

case "${cmd}" in
framework_tool)
opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --generate-completions --help"
opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -197,6 +197,10 @@ _framework_tool() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--host-command)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--generate-completions)
COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
return 0
Expand Down
1 change: 1 addition & 0 deletions completions/fish/framework_tool.fish
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ complete -c framework_tool -l pd-addrs -d 'Specify I2C addresses of the PD chips
complete -c framework_tool -l pd-ports -d 'Specify I2C ports of the PD chips (Advanced)' -r
complete -c framework_tool -l flash-gpu-descriptor-file -d 'File to write to the gpu EEPROM' -r -F
complete -c framework_tool -l dump-gpu-descriptor-file -d 'File to dump the gpu EEPROM to' -r -F
complete -c framework_tool -l host-command -d 'Send an EC host command. Args: <CMD_ID> <VERSION> [DATA...]' -r
complete -c framework_tool -l generate-completions -d 'Generate shell completions and print to stdout' -r -f -a "bash\t''
elvish\t''
fish\t''
Expand Down
1 change: 1 addition & 0 deletions completions/zsh/_framework_tool
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ _framework_tool() {
'*--pd-ports=[Specify I2C ports of the PD chips (Advanced)]:PD_PORTS:_default:PD_PORTS:_default:PD_PORTS:_default' \
'--flash-gpu-descriptor-file=[File to write to the gpu EEPROM]:FLASH_GPU_DESCRIPTOR_FILE:_files' \
'--dump-gpu-descriptor-file=[File to dump the gpu EEPROM to]:DUMP_GPU_DESCRIPTOR_FILE:_files' \
'*--host-command=[Send an EC host command. Args\: <CMD_ID> <VERSION> \[DATA...\]]:HOST_COMMAND:_default:HOST_COMMAND:_default' \
'--generate-completions=[Generate shell completions and print to stdout]:SHELL:(bash elvish fish powershell zsh)' \
'*-v[Increase logging verbosity]' \
'*--verbose[Increase logging verbosity]' \
Expand Down
38 changes: 37 additions & 1 deletion framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ struct ClapCli {
#[arg(long)]
nvidia: bool,

/// Send an EC host command. Args: <CMD_ID> <VERSION> [DATA...]
#[arg(long, value_parser=maybe_hex::<u16>)]
#[clap(num_args = 2..)]
host_command: Vec<u16>,

/// Generate shell completions and print to stdout
#[arg(long, value_name = "SHELL", hide = true)]
generate_completions: Option<Shell>,
Expand Down Expand Up @@ -419,6 +424,37 @@ pub fn parse(args: &[String]) -> Cli {
)),
_ => None,
};
let host_command = if args.host_command.len() >= 2 {
let cmd_ver = if let Ok(cmd_ver) = u8::try_from(args.host_command[1]) {
cmd_ver
} else {
cli.error(
ErrorKind::InvalidValue,
"Second argument of --host-command must be a one byte command version",
)
.exit();
};
Some((
args.host_command[0],
cmd_ver,
args.host_command[2..]
.iter()
.map(|&x| {
if let Ok(x) = u8::try_from(x) {
x
} else {
cli.error(
ErrorKind::InvalidValue,
"All payload values of --host-command must be one byte each",
)
.exit();
}
})
.collect(),
))
} else {
None
};

Cli {
verbosity: LogLevel(args.verbosity.log_level_filter()),
Expand Down Expand Up @@ -517,6 +553,6 @@ pub fn parse(args: &[String]) -> Cli {
.dump_gpu_descriptor_file
.map(|x| x.into_os_string().into_string().unwrap()),
nvidia: args.nvidia,
raw_command: vec![],
host_command,
}
}
22 changes: 15 additions & 7 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use crate::chromium_ec::commands::RgbS;
use crate::chromium_ec::commands::TabletModeOverride;
use crate::chromium_ec::EcResponseStatus;
use crate::chromium_ec::{print_err, EcFlashType};
use crate::chromium_ec::{EcError, EcResult};
use crate::chromium_ec::{CrosEcDriver, EcError, EcResult};
use crate::csme;
use crate::ec_binary;
use crate::esrt::{self, ResourceType};
Expand Down Expand Up @@ -230,8 +230,7 @@ pub struct Cli {
// UEFI only
pub allupdate: bool,
pub paginate: bool,
// TODO: This is not actually implemented yet
pub raw_command: Vec<String>,
pub host_command: Option<(u16, u8, Vec<u8>)>,
}

pub fn parse(args: &[String]) -> Cli {
Expand Down Expand Up @@ -316,7 +315,7 @@ pub fn parse(args: &[String]) -> Cli {
nvidia: cli.nvidia,
// allupdate
paginate: cli.paginate,
// raw_command
// host_command
..Default::default()
}
} else {
Expand Down Expand Up @@ -1603,9 +1602,18 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
} else {
println!("Not all EC versions support this comand.")
};
// TODO:
//} else if arg == "-raw-command" {
// raw_command(&args[1..]);
} else if let Some((command_id, command_version, ref data)) = args.host_command {
match ec.send_command(command_id, command_version, data) {
Comment on lines +1605 to +1606
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

args is a shared reference (&Cli), but this pattern matches on args.host_command by value, which attempts to move the Option<(u16, u8, Vec<u8>)> out of a borrowed struct and won’t compile. Match on a reference instead (e.g. via args.host_command.as_ref() / &args.host_command) and borrow the payload bytes when calling send_command.

Suggested change
} else if let Some((command_id, command_version, ref data)) = args.host_command {
match ec.send_command(command_id, command_version, data) {
} else if let Some((command_id, command_version, data)) = args.host_command.as_ref() {
match ec.send_command(*command_id, *command_version, data) {

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

won’t compile

no it does build

Ok(response) => {
println!("Response ({} bytes):", response.len());
if response.is_empty() {
println!(" (empty)");
} else {
util::print_multiline_buffer(&response, 0);
}
}
Err(e) => println!("EC command failed: {:?}", e),
}
} else if let Some(pd_bin_path) = &args.pd_bin {
#[cfg(feature = "uefi")]
let data: Option<Vec<u8>> = crate::uefi::fs::shell_read_file(pd_bin_path);
Expand Down
53 changes: 50 additions & 3 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub fn parse(args: &[String]) -> Cli {
info: false,
meinfo: None,
nvidia: false,
raw_command: vec![],
host_command: None,
};

if args.len() == 0 {
Expand Down Expand Up @@ -708,8 +708,39 @@ pub fn parse(args: &[String]) -> Cli {
None
};
found_an_option = true;
} else if arg == "--raw-command" {
cli.raw_command = args[1..].to_vec();
} else if arg == "--host-command" {
cli.host_command = if args.len() > i + 2 {
let cmd_id = parse_hex_or_dec_u16(&args[i + 1]);
let version = parse_hex_or_dec_u8(&args[i + 2]);
if let (Some(cmd_id), Some(version)) = (cmd_id, version) {
let mut data = Vec::new();
let mut parse_error = false;
for j in (i + 3)..args.len() {
if args[j].starts_with('-') {
break;
}
if let Some(byte) = parse_hex_or_dec_u8(&args[j]) {
data.push(byte);
} else {
println!("Invalid data byte for --host-command: '{}'", args[j]);
parse_error = true;
break;
}
}
if parse_error {
None
} else {
Some((cmd_id, version, data))
}
} else {
Comment on lines +722 to +735
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

When an invalid payload byte is encountered, the code prints an error and breaks, but still returns Some((cmd_id, version, data)) and will proceed to send a truncated payload. Treat any invalid byte as a hard parse failure for --host-command (return None / clear cli.host_command) so you don’t accidentally send a different command than intended.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

println!("Invalid values for --host-command. Usage: --host-command <CMD_ID> <VERSION> [DATA...]");
None
}
} else {
println!("--host-command requires at least two arguments: <CMD_ID> <VERSION>");
None
};
found_an_option = true;
} else if arg == "--compare-version" {
cli.compare_version = if args.len() > i + 1 {
Some(args[i + 1].clone())
Expand Down Expand Up @@ -817,3 +848,19 @@ pub fn parse(args: &[String]) -> Cli {

cli
}

fn parse_hex_or_dec_u16(s: &str) -> Option<u16> {
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
u16::from_str_radix(hex, 16).ok()
} else {
s.parse::<u16>().ok()
}
}

fn parse_hex_or_dec_u8(s: &str) -> Option<u8> {
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
u8::from_str_radix(hex, 16).ok()
} else {
s.parse::<u8>().ok()
}
}