From 713ba623efbb034070709409caef48299fc1bb49 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 11:58:51 +0800 Subject: [PATCH 1/9] --hello: Add EC diag command Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 18 +++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 8 +++++++ framework_lib/src/commandline/clap_std.rs | 5 ++++ framework_lib/src/commandline/mod.rs | 23 +++++++++++++++++++ framework_lib/src/commandline/uefi.rs | 4 ++++ .../completions/bash/framework_tool | 2 +- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 9 files changed, 62 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index b3067fa..64bad14 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -15,6 +15,7 @@ use super::{CrosEc, CrosEcDriver, EcError, EcResult}; #[derive(Debug, FromPrimitive)] #[repr(u16)] pub enum EcCommands { + Hello = 0x01, GetVersion = 0x02, GetBuildInfo = 0x04, /// Command to read data from EC memory map diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index e0bb0b2..90e4ce4 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -6,6 +6,24 @@ use super::{command::*, input_deck::INPUT_DECK_SLOTS}; #[cfg(feature = "uefi")] use core::prelude::rust_2021::derive; +#[repr(C, packed)] +pub struct EcRequestHello { + /// Pass anything here + pub in_data: u32, +} + +#[repr(C, packed)] +pub struct EcResponseHello { + /// Output will be in_data + 0x01020304 + pub out_data: u32, +} + +impl EcRequest for EcRequestHello { + fn command_id() -> EcCommands { + EcCommands::Hello + } +} + #[repr(C, packed)] pub struct EcRequestGetVersion {} diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index fa470f9..3231df2 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1685,6 +1685,14 @@ impl CrosEc { Ok(res.hibernation_delay) } + /// Basic communication check with the EC + /// + /// The EC returns the sent value incremented by 0x01020304 + pub fn hello(&self, in_data: u32) -> EcResult { + let res = EcRequestHello { in_data }.send_command(self)?; + Ok(res.out_data) + } + pub fn reset_s0ix_counter(&self) -> EcResult<()> { EcRequestS0ixCounter { flags: EC_S0IX_COUNTER_RESET, diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index cdb4c34..783386f 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -293,6 +293,10 @@ struct ClapCli { #[arg(long)] s0ix_counter: bool, + /// Check basic communication with EC + #[arg(long)] + hello: bool, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -582,6 +586,7 @@ pub fn parse(args: &[String]) -> Cli { sysinfo: args.sysinfo, uptimeinfo: args.uptimeinfo, s0ix_counter: args.s0ix_counter, + hello: args.hello, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index c56ac92..5bd20a1 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -246,6 +246,7 @@ pub struct Cli { pub sysinfo: bool, pub uptimeinfo: bool, pub s0ix_counter: bool, + pub hello: bool, pub hash: Option, pub pd_addrs: Option<(u16, u16, u16)>, pub pd_ports: Option<(u8, u8, u8)>, @@ -338,6 +339,7 @@ pub fn parse(args: &[String]) -> Cli { sysinfo: cli.sysinfo, uptimeinfo: cli.uptimeinfo, s0ix_counter: cli.s0ix_counter, + hello: cli.hello, hash: cli.hash, pd_addrs: cli.pd_addrs, pd_ports: cli.pd_ports, @@ -1585,6 +1587,26 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else { println!("s0ix_counter: Unknown"); } + } else if args.hello { + const HELLO_DATA: u32 = 0xa0b0c0d0; + const HELLO_OFFSET: u32 = 0x01020304; + match ec.hello(HELLO_DATA) { + Ok(out_data) if out_data == HELLO_DATA.wrapping_add(HELLO_OFFSET) => { + println!("EC says hello!"); + } + Ok(out_data) => { + println!( + "EC returned invalid response: {:#010x}, expected {:#010x}", + out_data, + HELLO_DATA.wrapping_add(HELLO_OFFSET) + ); + return 1; + } + Err(err) => { + println!("Failed to communicate with EC: {:?}", err); + return 1; + } + } } else if args.test { println!("Self-Test"); let result = selftest(&ec); @@ -1984,6 +2006,7 @@ Options: --sysinfo Show system info (reset flags, current image, locked state) --uptimeinfo Show EC uptime information --s0ix-counter Show S0ix counter + --hello Check basic communication with EC --intrusion Show status of intrusion switch --inputdeck Show status of the input deck --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Laptop 12, 13, 16) diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 748c913..93d5e78 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -92,6 +92,7 @@ pub fn parse(args: &[String]) -> Cli { sysinfo: false, uptimeinfo: false, s0ix_counter: false, + hello: false, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), @@ -550,6 +551,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--s0ix-counter" { cli.s0ix_counter = true; found_an_option = true; + } else if arg == "--hello" { + cli.hello = true; + found_an_option = true; } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index d8b39a9..1279709 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index 0be6254..979e5fb 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -106,6 +106,7 @@ complete -c framework_tool -l stylus-battery -d 'Check stylus battery level (USI complete -c framework_tool -l sysinfo -d 'Show system info (reset flags, current image, locked state)' complete -c framework_tool -l uptimeinfo complete -c framework_tool -l s0ix-counter +complete -c framework_tool -l hello -d 'Check basic communication with EC' complete -c framework_tool -s t -l test -d 'Run self-test to check if interaction with EC is possible' complete -c framework_tool -l test-retimer -d 'Run self-test to check if interaction with retimers is possible' complete -c framework_tool -l boardid -d 'Print all board IDs' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index d005f94..1233e66 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -90,6 +90,7 @@ _framework_tool() { '--sysinfo[Show system info (reset flags, current image, locked state)]' \ '--uptimeinfo[]' \ '--s0ix-counter[]' \ +'--hello[Check basic communication with EC]' \ '-t[Run self-test to check if interaction with EC is possible]' \ '--test[Run self-test to check if interaction with EC is possible]' \ '--test-retimer[Run self-test to check if interaction with retimers is possible]' \ From 85dd3b7d69de420377eccf515ecfd0c2e596b47e Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 12:04:33 +0800 Subject: [PATCH 2/9] --test: Run hello host command Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 17 +++++++++++++++++ framework_lib/src/commandline/mod.rs | 22 +++++++--------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 3231df2..c6bbddb 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1693,6 +1693,23 @@ impl CrosEc { Ok(res.out_data) } + /// Check basic communication with the EC works by sending a magic value + /// and verifying the EC returns it incremented by the expected offset + pub fn check_hello(&self) -> EcResult<()> { + const HELLO_DATA: u32 = 0xa0b0c0d0; + const HELLO_OFFSET: u32 = 0x01020304; + let expected = HELLO_DATA.wrapping_add(HELLO_OFFSET); + let out_data = self.hello(HELLO_DATA)?; + if out_data == expected { + Ok(()) + } else { + Err(EcError::DeviceError(format!( + "Invalid response: {:#010x}, expected {:#010x}", + out_data, expected + ))) + } + } + pub fn reset_s0ix_counter(&self) -> EcResult<()> { EcRequestS0ixCounter { flags: EC_S0IX_COUNTER_RESET, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 5bd20a1..bc997e3 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -1588,22 +1588,10 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("s0ix_counter: Unknown"); } } else if args.hello { - const HELLO_DATA: u32 = 0xa0b0c0d0; - const HELLO_OFFSET: u32 = 0x01020304; - match ec.hello(HELLO_DATA) { - Ok(out_data) if out_data == HELLO_DATA.wrapping_add(HELLO_OFFSET) => { - println!("EC says hello!"); - } - Ok(out_data) => { - println!( - "EC returned invalid response: {:#010x}, expected {:#010x}", - out_data, - HELLO_DATA.wrapping_add(HELLO_OFFSET) - ); - return 1; - } + match ec.check_hello() { + Ok(()) => println!("EC says hello!"), Err(err) => { - println!("Failed to communicate with EC: {:?}", err); + println!("EC hello failed: {:?}", err); return 1; } } @@ -2136,6 +2124,10 @@ fn selftest(ec: &CrosEc) -> Option<()> { print_err(ec.check_mem_magic())?; println!(" Verified that Framework EC is present!"); + print!(" Checking basic EC communication"); + print_err(ec.check_hello())?; + println!(" - OK"); + println!(" Reading EC Build Version"); print_err(ec.version_info())?; From 4e8a19de638b770088961d260ff68719aecfbe11 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 13:56:31 +0800 Subject: [PATCH 3/9] --protoinfo: Add command > framework_tool --protoinfo Protocol info: protocol versions: 3 max request: 256 bytes max response: 256 bytes flags: 0x00000000 Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 25 +++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 5 ++++ framework_lib/src/commandline/clap_std.rs | 5 ++++ framework_lib/src/commandline/mod.rs | 27 +++++++++++++++++++ framework_lib/src/commandline/uefi.rs | 4 +++ .../completions/bash/framework_tool | 2 +- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 9 files changed, 70 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 64bad14..86f5946 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -21,6 +21,7 @@ pub enum EcCommands { /// Command to read data from EC memory map ReadMemMap = 0x07, GetCmdVersions = 0x08, + GetProtocolInfo = 0x0B, FlashInfo = 0x10, /// Write section of EC flash FlashRead = 0x11, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 90e4ce4..264e2fd 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -80,6 +80,31 @@ impl EcRequest for EcRequestGetCmdVersionsV1 { } } +#[repr(C, packed)] +pub struct EcRequestGetProtocolInfo {} + +/// Set if EC_RES_IN_PROGRESS may be returned by a slow command +pub const EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED: u32 = 0x01; + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct EcResponseGetProtocolInfo { + /// Bitmask of protocol versions supported (1 << n means version n) + pub protocol_versions: u32, + /// Maximum request packet size in bytes + pub max_request_packet_size: u16, + /// Maximum response packet size in bytes + pub max_response_packet_size: u16, + /// See EC_PROTOCOL_INFO_* + pub flags: u32, +} + +impl EcRequest for EcRequestGetProtocolInfo { + fn command_id() -> EcCommands { + EcCommands::GetProtocolInfo + } +} + pub struct EcRequestFlashInfo {} #[repr(C, packed)] diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index c6bbddb..f240ec8 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1693,6 +1693,11 @@ impl CrosEc { Ok(res.out_data) } + /// Get information about the EC host communication protocol + pub fn get_protocol_info(&self) -> EcResult { + EcRequestGetProtocolInfo {}.send_command(self) + } + /// Check basic communication with the EC works by sending a magic value /// and verifying the EC returns it incremented by the expected offset pub fn check_hello(&self) -> EcResult<()> { diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 783386f..8c54096 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -297,6 +297,10 @@ struct ClapCli { #[arg(long)] hello: bool, + /// Show EC host command protocol info + #[arg(long)] + protoinfo: bool, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -587,6 +591,7 @@ pub fn parse(args: &[String]) -> Cli { uptimeinfo: args.uptimeinfo, s0ix_counter: args.s0ix_counter, hello: args.hello, + protoinfo: args.protoinfo, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index bc997e3..252db0c 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -41,6 +41,7 @@ use crate::chromium_ec::commands::FpLedBrightnessLevel; use crate::chromium_ec::commands::RebootEcCmd; use crate::chromium_ec::commands::RgbS; use crate::chromium_ec::commands::TabletModeOverride; +use crate::chromium_ec::commands::EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED; use crate::chromium_ec::EcResponseStatus; use crate::chromium_ec::{print_err, EcFlashType}; use crate::chromium_ec::{CrosEcDriver, EcError, EcResult}; @@ -247,6 +248,7 @@ pub struct Cli { pub uptimeinfo: bool, pub s0ix_counter: bool, pub hello: bool, + pub protoinfo: bool, pub hash: Option, pub pd_addrs: Option<(u16, u16, u16)>, pub pd_ports: Option<(u8, u8, u8)>, @@ -340,6 +342,7 @@ pub fn parse(args: &[String]) -> Cli { uptimeinfo: cli.uptimeinfo, s0ix_counter: cli.s0ix_counter, hello: cli.hello, + protoinfo: cli.protoinfo, hash: cli.hash, pd_addrs: cli.pd_addrs, pd_ports: cli.pd_ports, @@ -1595,6 +1598,29 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { return 1; } } + } else if args.protoinfo { + if let Some(info) = print_err(ec.get_protocol_info()) { + let versions = { info.protocol_versions }; + let max_request = { info.max_request_packet_size }; + let max_response = { info.max_response_packet_size }; + let flags = { info.flags }; + println!("Protocol info:"); + print!(" protocol versions:"); + for i in 0..32 { + if versions & (1 << i) != 0 { + print!(" {}", i); + } + } + println!(); + println!(" max request: {:4} bytes", max_request); + println!(" max response: {:4} bytes", max_response); + println!(" flags: {:#010x}", flags); + if flags & EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED != 0 { + println!(" EC_RES_IN_PROGRESS supported"); + } + } else { + return 1; + } } else if args.test { println!("Self-Test"); let result = selftest(&ec); @@ -1995,6 +2021,7 @@ Options: --uptimeinfo Show EC uptime information --s0ix-counter Show S0ix counter --hello Check basic communication with EC + --protoinfo Show EC host command protocol info --intrusion Show status of intrusion switch --inputdeck Show status of the input deck --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Laptop 12, 13, 16) diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 93d5e78..38aec69 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -93,6 +93,7 @@ pub fn parse(args: &[String]) -> Cli { uptimeinfo: false, s0ix_counter: false, hello: false, + protoinfo: false, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), @@ -554,6 +555,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--hello" { cli.hello = true; found_an_option = true; + } else if arg == "--protoinfo" { + cli.protoinfo = true; + found_an_option = true; } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index 1279709..0a1303a 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index 979e5fb..f6bb639 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -107,6 +107,7 @@ complete -c framework_tool -l sysinfo -d 'Show system info (reset flags, current complete -c framework_tool -l uptimeinfo complete -c framework_tool -l s0ix-counter complete -c framework_tool -l hello -d 'Check basic communication with EC' +complete -c framework_tool -l protoinfo -d 'Show EC host command protocol info' complete -c framework_tool -s t -l test -d 'Run self-test to check if interaction with EC is possible' complete -c framework_tool -l test-retimer -d 'Run self-test to check if interaction with retimers is possible' complete -c framework_tool -l boardid -d 'Print all board IDs' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 1233e66..8d43362 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -91,6 +91,7 @@ _framework_tool() { '--uptimeinfo[]' \ '--s0ix-counter[]' \ '--hello[Check basic communication with EC]' \ +'--protoinfo[Show EC host command protocol info]' \ '-t[Run self-test to check if interaction with EC is possible]' \ '--test[Run self-test to check if interaction with EC is possible]' \ '--test-retimer[Run self-test to check if interaction with retimers is possible]' \ From 8765526fff8283f6676ce0d35db24abff566a4b7 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 14:08:27 +0800 Subject: [PATCH 4/9] --switches: Add command Signed-off-by: Daniel Schaefer --- EXAMPLES_ADVANCED.md | 14 ++++++ framework_lib/src/commandline/clap_std.rs | 5 ++ framework_lib/src/commandline/mod.rs | 8 +++ framework_lib/src/commandline/uefi.rs | 4 ++ framework_lib/src/power.rs | 49 ++++++++++++++++++- .../completions/bash/framework_tool | 2 +- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 8 files changed, 81 insertions(+), 3 deletions(-) diff --git a/EXAMPLES_ADVANCED.md b/EXAMPLES_ADVANCED.md index 5461a41..302f0a8 100644 --- a/EXAMPLES_ADVANCED.md +++ b/EXAMPLES_ADVANCED.md @@ -232,6 +232,20 @@ Tablet mode: > framework_tool --tablet-mode auto ``` +## Check EC switch positions + +Show the current positions of the switches the EC monitors +(e.g. to debug lid or power button issues): + +``` +> framework_tool --switches +Current switches: 0x01 +Lid switch: OPEN +Power button: UP +Write protect: DISABLED +Dedicated recovery: DISABLED +``` + ## Flashing EC firmware **IMPORTANT** Flashing EC firmware yourself is not recommended. It may render diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 8c54096..4aa785f 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -301,6 +301,10 @@ struct ClapCli { #[arg(long)] protoinfo: bool, + /// Show current EC switch positions (lid, power button, ...) + #[arg(long)] + switches: bool, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -592,6 +596,7 @@ pub fn parse(args: &[String]) -> Cli { s0ix_counter: args.s0ix_counter, hello: args.hello, protoinfo: args.protoinfo, + switches: args.switches, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 252db0c..9b4af39 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -249,6 +249,7 @@ pub struct Cli { pub s0ix_counter: bool, pub hello: bool, pub protoinfo: bool, + pub switches: bool, pub hash: Option, pub pd_addrs: Option<(u16, u16, u16)>, pub pd_ports: Option<(u8, u8, u8)>, @@ -343,6 +344,7 @@ pub fn parse(args: &[String]) -> Cli { s0ix_counter: cli.s0ix_counter, hello: cli.hello, protoinfo: cli.protoinfo, + switches: cli.switches, hash: cli.hash, pd_addrs: cli.pd_addrs, pd_ports: cli.pd_ports, @@ -1621,6 +1623,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else { return 1; } + } else if args.switches { + if power::print_switches(&ec).is_none() { + println!("Failed to read EC switch state"); + return 1; + } } else if args.test { println!("Self-Test"); let result = selftest(&ec); @@ -2022,6 +2029,7 @@ Options: --s0ix-counter Show S0ix counter --hello Check basic communication with EC --protoinfo Show EC host command protocol info + --switches Show current EC switch positions (lid, power button, ...) --intrusion Show status of intrusion switch --inputdeck Show status of the input deck --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Laptop 12, 13, 16) diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 38aec69..82f318f 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -94,6 +94,7 @@ pub fn parse(args: &[String]) -> Cli { s0ix_counter: false, hello: false, protoinfo: false, + switches: false, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), @@ -558,6 +559,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--protoinfo" { cli.protoinfo = true; found_an_option = true; + } else if arg == "--switches" { + cli.switches = true; + found_an_option = true; } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index 3ebff27..9751290 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -32,8 +32,8 @@ const EC_MEMMAP_SWITCHES_VERSION: u16 = 0x25; // Version of data in 0x30 - 0x33 const EC_MEMMAP_EVENTS_VERSION: u16 = 0x26; // Version of data in 0x34 - 0x3f const _EC_MEMMAP_HOST_CMD_FLAGS: u16 = 0x27; // Host cmd interface flags (8 bits) // Unused 0x28 - 0x2f -const _EC_MEMMAP_SWITCHES: u16 = 0x30; // 8 bits - // Unused 0x31 - 0x33 +const EC_MEMMAP_SWITCHES: u16 = 0x30; // 8 bits + // Unused 0x31 - 0x33 const _EC_MEMMAP_HOST_EVENTS: u16 = 0x34; // 64 bits // Battery values are all 32 bits, unless otherwise noted. const EC_MEMMAP_BATT_VOLT: u16 = 0x40; // Battery Present Voltage @@ -64,6 +64,12 @@ const LID_ANGLE_UNRELIABLE: u16 = 500; const _EC_MEMMAP_GYRO_DATA: u16 = 0xa0; // Gyroscope data 0xa0 - 0xa5 // Unused 0xa6 - 0xdf +// Switch flags at EC_MEMMAP_SWITCHES +const EC_SWITCH_LID_OPEN: u8 = 0x01; +const EC_SWITCH_POWER_BUTTON_PRESSED: u8 = 0x02; +const EC_SWITCH_WRITE_PROTECT_DISABLED: u8 = 0x04; +const EC_SWITCH_DEDICATED_RECOVERY: u8 = 0x10; + // Battery bit flags at EC_MEMMAP_BATT_FLAG. const EC_BATT_FLAG_AC_PRESENT: u8 = 0x01; const EC_BATT_FLAG_BATT_PRESENT: u8 = 0x02; @@ -244,6 +250,45 @@ pub fn print_memmap_version_info(ec: &CrosEc) { let _events_ver = ec.read_memory(EC_MEMMAP_EVENTS_VERSION, 2).unwrap(); } +/// Print the current EC switch positions (lid, power button, ...) +pub fn print_switches(ec: &CrosEc) -> Option<()> { + let switches = *ec.read_memory(EC_MEMMAP_SWITCHES, 1)?.first()?; + println!("Current switches: {:#04x}", switches); + println!( + "Lid switch: {}", + if switches & EC_SWITCH_LID_OPEN != 0 { + "OPEN" + } else { + "CLOSED" + } + ); + println!( + "Power button: {}", + if switches & EC_SWITCH_POWER_BUTTON_PRESSED != 0 { + "DOWN" + } else { + "UP" + } + ); + println!( + "Write protect: {}ABLED", + if switches & EC_SWITCH_WRITE_PROTECT_DISABLED != 0 { + "DIS" + } else { + "EN" + } + ); + println!( + "Dedicated recovery: {}ABLED", + if switches & EC_SWITCH_DEDICATED_RECOVERY != 0 { + "EN" + } else { + "DIS" + } + ); + Some(()) +} + /// Not supported on TGL EC pub fn get_als_reading(ec: &CrosEc, index: usize) -> Option { let als = ec.read_memory(EC_MEMMAP_ALS, 0x04)?; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index 0a1303a..bbbdb77 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index f6bb639..ff27ff6 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -108,6 +108,7 @@ complete -c framework_tool -l uptimeinfo complete -c framework_tool -l s0ix-counter complete -c framework_tool -l hello -d 'Check basic communication with EC' complete -c framework_tool -l protoinfo -d 'Show EC host command protocol info' +complete -c framework_tool -l switches -d 'Show current EC switch positions (lid, power button, ...)' complete -c framework_tool -s t -l test -d 'Run self-test to check if interaction with EC is possible' complete -c framework_tool -l test-retimer -d 'Run self-test to check if interaction with retimers is possible' complete -c framework_tool -l boardid -d 'Print all board IDs' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 8d43362..004be50 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -92,6 +92,7 @@ _framework_tool() { '--s0ix-counter[]' \ '--hello[Check basic communication with EC]' \ '--protoinfo[Show EC host command protocol info]' \ +'--switches[Show current EC switch positions (lid, power button, ...)]' \ '-t[Run self-test to check if interaction with EC is possible]' \ '--test[Run self-test to check if interaction with EC is possible]' \ '--test-retimer[Run self-test to check if interaction with retimers is possible]' \ From fbe477f91778b24a81d57055b25713ae3b9e0822 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 14:24:52 +0800 Subject: [PATCH 5/9] --port80read: Add command Signed-off-by: Daniel Schaefer --- EXAMPLES_ADVANCED.md | 15 ++++++ framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 46 ++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 53 +++++++++++++++++++ framework_lib/src/commandline/clap_std.rs | 5 ++ framework_lib/src/commandline/mod.rs | 44 +++++++++++++++ framework_lib/src/commandline/uefi.rs | 4 ++ .../completions/bash/framework_tool | 2 +- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 10 files changed, 171 insertions(+), 1 deletion(-) diff --git a/EXAMPLES_ADVANCED.md b/EXAMPLES_ADVANCED.md index 302f0a8..67a1607 100644 --- a/EXAMPLES_ADVANCED.md +++ b/EXAMPLES_ADVANCED.md @@ -246,6 +246,21 @@ Write protect: DISABLED Dedicated recovery: DISABLED ``` +## Check port 80 POST code history + +The EC records all POST codes the BIOS writes to port 80 during boot. +Useful to debug where the BIOS got stuck if the system does not boot: + +``` +> framework_tool --port80read +Port 80 writes + 25 25 73 93 74 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 + 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 + 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 76 77 79 9c + 7a fe 7b f8 00 00 00 00 <--new +done. +``` + ## Flashing EC firmware **IMPORTANT** Flashing EC firmware yourself is not recommended. It may render diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 86f5946..87e3f84 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -39,6 +39,7 @@ pub enum EcCommands { MotionSense = 0x002B, PwmGetDuty = 0x0026, SetTabletMode = 0x0031, + Port80Read = 0x0048, AutoFanCtrl = 0x0052, GpioSet = 0x0092, GpioGet = 0x0093, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 264e2fd..9783a72 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -519,6 +519,52 @@ impl EcRequest<()> for EcRequestSetTabletMode { } } +#[repr(u16)] +pub enum Port80Subcommand { + /// Get general information about the port 80 history buffer + GetInfo = 0, + /// Read from the port 80 history buffer + ReadBuffer = 1, +} + +/// Maximum entries that can be read in a single command +pub const EC_PORT80_SIZE_MAX: u32 = 32; + +/// EC inserted the marker into the history when the system resumed (S3->S0) +pub const PORT_80_EVENT_RESUME: u16 = 0x1001; +/// EC inserted the marker into the history when the system reset +pub const PORT_80_EVENT_RESET: u16 = 0x1002; + +#[repr(C, packed)] +pub struct EcRequestPort80Read { + /// See Port80Subcommand + pub subcmd: u16, + /// Entry offset from the beginning of the history buffer (ReadBuffer only) + pub offset: u32, + /// Number of entries to read (ReadBuffer only) + pub num_entries: u32, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct EcResponsePort80GetInfo { + /// Total number of port 80 writes so far + pub writes: u32, + /// Size of the history buffer in entries + pub history_size: u32, + pub last_boot: u32, +} + +/// ReadBuffer responses are variable length, read with send_command_vec +impl EcRequest for EcRequestPort80Read { + fn command_id() -> EcCommands { + EcCommands::Port80Read + } + fn command_version() -> u8 { + 1 + } +} + #[repr(C, packed)] pub struct EcRequestAutoFanCtrlV0 {} diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f240ec8..a64af06 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -91,6 +91,16 @@ pub enum MecFlashNotify { FlashPd = 0x16, } +/// Port 80 history read from the EC +pub struct Port80History { + /// Total number of port 80 writes so far + pub writes: u32, + /// Size of the history buffer in entries + pub history_size: u32, + /// Contents of the history buffer, in buffer order + pub codes: Vec, +} + pub type EcResult = Result; #[derive(Debug, PartialEq)] @@ -1698,6 +1708,49 @@ impl CrosEc { EcRequestGetProtocolInfo {}.send_command(self) } + /// Read the port 80 code history from the EC + /// + /// The returned codes hold the entire history buffer, in buffer order. + /// Use `writes % history_size` to find the position of the newest entry. + pub fn port80_read(&self) -> EcResult { + let info = EcRequestPort80Read { + subcmd: Port80Subcommand::GetInfo as u16, + offset: 0, + num_entries: 0, + } + .send_command(self)?; + let writes = { info.writes }; + let history_size = { info.history_size }; + + let mut codes = Vec::new(); + let mut offset = 0; + while offset < history_size { + let num_entries = core::cmp::min(EC_PORT80_SIZE_MAX, history_size - offset); + let data = EcRequestPort80Read { + subcmd: Port80Subcommand::ReadBuffer as u16, + offset, + num_entries, + } + .send_command_vec(self)?; + if data.len() != 2 * num_entries as usize { + return Err(EcError::DeviceError(format!( + "Expected {} entries, got {} bytes", + num_entries, + data.len() + ))); + } + for chunk in data.chunks_exact(2) { + codes.push(u16::from_le_bytes([chunk[0], chunk[1]])); + } + offset += num_entries; + } + Ok(Port80History { + writes, + history_size, + codes, + }) + } + /// Check basic communication with the EC works by sending a magic value /// and verifying the EC returns it incremented by the expected offset pub fn check_hello(&self) -> EcResult<()> { diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 4aa785f..3f34981 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -305,6 +305,10 @@ struct ClapCli { #[arg(long)] switches: bool, + /// Show history of port 80 writes (POST codes) + #[arg(long)] + port80read: bool, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -597,6 +601,7 @@ pub fn parse(args: &[String]) -> Cli { hello: args.hello, protoinfo: args.protoinfo, switches: args.switches, + port80read: args.port80read, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 9b4af39..99febd5 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -42,7 +42,9 @@ use crate::chromium_ec::commands::RebootEcCmd; use crate::chromium_ec::commands::RgbS; use crate::chromium_ec::commands::TabletModeOverride; use crate::chromium_ec::commands::EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED; +use crate::chromium_ec::commands::{PORT_80_EVENT_RESET, PORT_80_EVENT_RESUME}; use crate::chromium_ec::EcResponseStatus; +use crate::chromium_ec::Port80History; use crate::chromium_ec::{print_err, EcFlashType}; use crate::chromium_ec::{CrosEcDriver, EcError, EcResult}; use crate::csme; @@ -250,6 +252,7 @@ pub struct Cli { pub hello: bool, pub protoinfo: bool, pub switches: bool, + pub port80read: bool, pub hash: Option, pub pd_addrs: Option<(u16, u16, u16)>, pub pd_ports: Option<(u8, u8, u8)>, @@ -345,6 +348,7 @@ pub fn parse(args: &[String]) -> Cli { hello: cli.hello, protoinfo: cli.protoinfo, switches: cli.switches, + port80read: cli.port80read, hash: cli.hash, pd_addrs: cli.pd_addrs, pd_ports: cli.pd_ports, @@ -1628,6 +1632,14 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("Failed to read EC switch state"); return 1; } + } else if args.port80read { + match ec.port80_read() { + Ok(history) => print_port80_history(&history), + Err(err) => { + println!("Failed to read port 80 history: {:?}", err); + return 1; + } + } } else if args.test { println!("Self-Test"); let result = selftest(&ec); @@ -2030,6 +2042,7 @@ Options: --hello Check basic communication with EC --protoinfo Show EC host command protocol info --switches Show current EC switch positions (lid, power button, ...) + --port80read Show history of port 80 writes (POST codes) --intrusion Show status of intrusion switch --inputdeck Show status of the input deck --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Laptop 12, 13, 16) @@ -2136,6 +2149,37 @@ fn print_board_ids(ec: &CrosEc) { ); } +/// Print the port 80 history like ectool does, oldest to newest code, +/// with markers where the system reset or resumed +fn print_port80_history(history: &Port80History) { + let head = history.writes as usize; + let tail = head.saturating_sub(history.history_size as usize); + print!("Port 80 writes"); + let mut printed = 0; + for i in tail..head { + let code = history.codes[i % history.codes.len()]; + match code { + PORT_80_EVENT_RESUME => { + print!("\n(S3->S0)"); + printed = 0; + } + PORT_80_EVENT_RESET => { + print!("\n(RESET)"); + printed = 0; + } + _ => { + if printed % 20 == 0 { + print!("\n "); + } + printed += 1; + print!(" {:02x}", code); + } + } + } + println!(" <--new"); + println!("done."); +} + fn selftest(ec: &CrosEc) -> Option<()> { if let Some(platform) = smbios::get_platform() { println!(" SMBIOS Platform: {:?}", platform); diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 82f318f..bd4eed4 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -95,6 +95,7 @@ pub fn parse(args: &[String]) -> Cli { hello: false, protoinfo: false, switches: false, + port80read: false, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), @@ -562,6 +563,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--switches" { cli.switches = true; found_an_option = true; + } else if arg == "--port80read" { + cli.port80read = true; + found_an_option = true; } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index bbbdb77..3155bac 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index ff27ff6..62b2c26 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -109,6 +109,7 @@ complete -c framework_tool -l s0ix-counter complete -c framework_tool -l hello -d 'Check basic communication with EC' complete -c framework_tool -l protoinfo -d 'Show EC host command protocol info' complete -c framework_tool -l switches -d 'Show current EC switch positions (lid, power button, ...)' +complete -c framework_tool -l port80read -d 'Show history of port 80 writes (POST codes)' complete -c framework_tool -s t -l test -d 'Run self-test to check if interaction with EC is possible' complete -c framework_tool -l test-retimer -d 'Run self-test to check if interaction with retimers is possible' complete -c framework_tool -l boardid -d 'Print all board IDs' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 004be50..7ea0170 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -93,6 +93,7 @@ _framework_tool() { '--hello[Check basic communication with EC]' \ '--protoinfo[Show EC host command protocol info]' \ '--switches[Show current EC switch positions (lid, power button, ...)]' \ +'--port80read[Show history of port 80 writes (POST codes)]' \ '-t[Run self-test to check if interaction with EC is possible]' \ '--test[Run self-test to check if interaction with EC is possible]' \ '--test-retimer[Run self-test to check if interaction with retimers is possible]' \ From 271f3c2c6800c8f44cc2f3d5c5cdef7d80d916d9 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 14:48:14 +0800 Subject: [PATCH 6/9] --panicinfo: Add command Signed-off-by: Daniel Schaefer --- EXAMPLES_ADVANCED.md | 17 + framework_lib/src/chromium_ec/command.rs | 2 + framework_lib/src/chromium_ec/commands.rs | 31 ++ framework_lib/src/chromium_ec/mod.rs | 18 + framework_lib/src/chromium_ec/panic.rs | 324 ++++++++++++++++++ framework_lib/src/commandline/clap_std.rs | 5 + framework_lib/src/commandline/mod.rs | 17 + framework_lib/src/commandline/uefi.rs | 4 + .../completions/bash/framework_tool | 2 +- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 11 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 framework_lib/src/chromium_ec/panic.rs diff --git a/EXAMPLES_ADVANCED.md b/EXAMPLES_ADVANCED.md index 67a1607..c52b296 100644 --- a/EXAMPLES_ADVANCED.md +++ b/EXAMPLES_ADVANCED.md @@ -453,3 +453,20 @@ Comparison Result: 1 Shell> echo %lasterror% 0x1 ``` + +## EC Panic Info + +See what happened when the EC last crashed: + +``` +> framework_tool --panicinfo +Saved panic data: +Flags: 0x0e (OLD_CONSOLE | OLD_HOSTCMD | OLD_HOSTEVENT) +=== PROCESS EXCEPTION: 09 ====== xPSR: ffffffff === +r0 : r1 : r2 : r3 : +r4 :dead6664 r5 :00000000 r6 :00000000 r7 :00000000 +r8 :00000000 r9 :00000000 r10:00000000 r11:00000000 +r12: sp :00000000 lr : pc : + +cfsr = 0, shcsr = 0, hfsr = 0, dfsr = 0 +``` diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 87e3f84..1c5f359 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -56,6 +56,8 @@ pub enum EcCommands { Reboot = 0x00D1, /// Control EC boot RebootEc = 0x00D2, + /// Get information about the last EC panic + GetPanicInfo = 0x00D3, /// Get information about PD controller power UsbPdPowerInfo = 0x0103, GetUptimeInfo = 0x0121, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 9783a72..3656d11 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -987,6 +987,37 @@ pub enum RebootEcFlags { ClearApidle = 0x08, } +/// Response is variable length (struct panic_data), read with send_command_vec. +/// An empty response means there is no panic data. +#[repr(C, packed)] +pub struct EcRequestGetPanicInfoV0 {} + +impl EcRequest<()> for EcRequestGetPanicInfoV0 { + fn command_id() -> EcCommands { + EcCommands::GetPanicInfo + } + fn command_version() -> u8 { + 0 + } +} + +/// Response is variable length (struct panic_data), read with send_command_vec. +/// An empty response means there is no panic data. +#[repr(C, packed)] +pub struct EcRequestGetPanicInfoV1 { + /// Do not set PANIC_DATA_FLAG_OLD_HOSTCMD when reading panic info + pub preserve_old_hostcmd_flag: u8, +} + +impl EcRequest<()> for EcRequestGetPanicInfoV1 { + fn command_id() -> EcCommands { + EcCommands::GetPanicInfo + } + fn command_version() -> u8 { + 1 + } +} + pub struct EcRequestRebootEc { /// See enum RebootEcCmd pub cmd: u8, diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index a64af06..6e90a47 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -27,6 +27,7 @@ pub mod commands; mod cros_ec; pub mod i2c_passthrough; pub mod input_deck; +pub mod panic; #[cfg(all(not(windows), target_arch = "x86_64"))] mod portio; #[cfg(all(not(windows), target_arch = "x86_64"))] @@ -1708,6 +1709,23 @@ impl CrosEc { EcRequestGetProtocolInfo {}.send_command(self) } + /// Get the panic data saved from the last EC panic + /// + /// Returns an empty vector if there is no panic data. + pub fn get_panic_info(&self) -> EcResult> { + // Reading with v0 marks the panic data as "already read by the host" + // (PANIC_DATA_FLAG_OLD_HOSTCMD). V1 lets us read without setting that + // flag, so we don't hide the panic from other consumers. + if let Ok(true) = self.cmd_version_supported(EcCommands::GetPanicInfo as u32, 1) { + EcRequestGetPanicInfoV1 { + preserve_old_hostcmd_flag: 1, + } + .send_command_vec(self) + } else { + EcRequestGetPanicInfoV0 {}.send_command_vec(self) + } + } + /// Read the port 80 code history from the EC /// /// The returned codes hold the entire history buffer, in buffer order. diff --git a/framework_lib/src/chromium_ec/panic.rs b/framework_lib/src/chromium_ec/panic.rs new file mode 100644 index 0000000..48afacb --- /dev/null +++ b/framework_lib/src/chromium_ec/panic.rs @@ -0,0 +1,324 @@ +//! Decode and print EC panic data (struct panic_data) +//! +//! Port of the EC's panic printing from core/cortex-m/panic.c. +//! See the EC's include/panic_defs.h for the struct layout. + +use alloc::vec::Vec; +use core::convert::TryInto; + +use crate::util; + +/// "Pnc!" if valid +pub const PANIC_DATA_MAGIC: u32 = 0x21636e50; + +// Flags for panic_data.flags +/// panic_data.frame is valid +const PANIC_DATA_FLAG_FRAME_VALID: u8 = 0x01; +/// Already printed at console +const PANIC_DATA_FLAG_OLD_CONSOLE: u8 = 0x02; +/// Already returned via host command +const PANIC_DATA_FLAG_OLD_HOSTCMD: u8 = 0x04; +/// Already reported via host event +const PANIC_DATA_FLAG_OLD_HOSTEVENT: u8 = 0x08; +/// The data was truncated to fit panic info host cmd +const PANIC_DATA_FLAG_TRUNCATED: u8 = 0x10; + +const FLAG_NAMES: [(u8, &str); 5] = [ + (PANIC_DATA_FLAG_FRAME_VALID, "FRAME_VALID"), + (PANIC_DATA_FLAG_OLD_CONSOLE, "OLD_CONSOLE"), + (PANIC_DATA_FLAG_OLD_HOSTCMD, "OLD_HOSTCMD"), + (PANIC_DATA_FLAG_OLD_HOSTEVENT, "OLD_HOSTEVENT"), + (PANIC_DATA_FLAG_TRUNCATED, "TRUNCATED"), +]; + +const PANIC_ARCH_CORTEX_M: u8 = 1; + +// Fault status register bits, see the EC's core/cortex-m/cpu.h +const CPU_NVIC_CFSR_BFARVALID: u32 = 1 << 15; +const CPU_NVIC_CFSR_MFARVALID: u32 = 1 << 7; +const CPU_NVIC_HFSR_DEBUGEVT: u32 = 1 << 31; +const CPU_NVIC_HFSR_FORCED: u32 = 1 << 30; +const CPU_NVIC_HFSR_VECTTBL: u32 = 1 << 1; + +/// Names for each of the bits in the CFSR register, starting at bit 0 +const CFSR_NAME: [(u32, &str); 15] = [ + // MMFSR + (0, "Instruction access violation"), + (1, "Data access violation"), + (3, "Unstack from exception violation"), + (4, "Stack from exception violation"), + // BFSR + (8, "Instruction bus error"), + (9, "Precise data bus error"), + (10, "Imprecise data bus error"), + (11, "Unstack from exception bus fault"), + (12, "Stack from exception bus fault"), + // UFSR + (16, "Undefined instructions"), + (17, "Invalid state"), + (18, "Invalid PC"), + (19, "No coprocessor"), + (24, "Unaligned"), + (25, "Divide by 0"), +]; + +/// Names for the first 5 bits in the DFSR +const DFSR_NAME: [&str; 5] = [ + "Halt request", + "Breakpoint", + "Data watchpoint/trace", + "Vector catch", + "External debug request", +]; + +fn u32_at(data: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) +} + +/// The least significant 4 bits of the exception LR (EXC_RETURN) determine +/// the exception stack and context. See B1.5.8 of ARM DDI 0403D. +fn is_frame_in_handler_stack(exc_return: u32) -> bool { + matches!( + exc_return, + 0xfffffff1 | 0xfffffff9 | 0xffffffe1 | 0xffffffe9 + ) +} + +fn is_exception_from_handler_mode(exc_return: u32) -> bool { + matches!(exc_return, 0xfffffff1 | 0xffffffe1) +} + +/// Print a single register, unavailable registers print as blank +fn print_reg(regnum: usize, value: Option) { + const NAMES: [&str; 6] = ["r10", "r11", "r12", "sp ", "lr ", "pc "]; + if regnum < 10 { + print!("r{:<2}:", regnum); + } else { + print!("{}:", NAMES[regnum - 10]); + } + if let Some(value) = value { + print!("{:08x}", value); + } else { + print!(" "); + } + if regnum & 3 == 3 { + println!(); + } else { + print!(" "); + } +} + +/// Print a textual representation of the fault registers +fn print_fault(cfsr: u32, hfsr: u32, dfsr: u32) { + let mut names = CFSR_NAME + .iter() + .filter(|(bit, _)| cfsr & (1 << bit) != 0) + .map(|(_, name)| *name) + .collect::>(); + if hfsr & CPU_NVIC_HFSR_DEBUGEVT != 0 { + names.push("Debug event"); + } + if hfsr & CPU_NVIC_HFSR_FORCED != 0 { + names.push("Forced hard fault"); + } + if hfsr & CPU_NVIC_HFSR_VECTTBL != 0 { + names.push("Vector table bus fault"); + } + for (bit, name) in DFSR_NAME.iter().enumerate() { + if dfsr & (1 << bit) != 0 { + names.push(name); + } + } + print!("{}", names.join(", ")); +} + +/// Print panic data of a Cortex-M EC +/// +/// Port of panic_data_print() in the EC's core/cortex-m/panic.c, with +/// handling for the older struct version 1 (missing MSP, LR at another +/// position) like the EC's util/ec_panicinfo.c. +fn print_panic_info_cm(data: &[u8], struct_version: u8, flags: u8) -> Option<()> { + // Register offsets into the data blob. Registers not saved on the + // exception stack frame come first (lregs), the stack frame follows + // (sregs). See struct cortex_panic_data(_v1) in the EC. + // + // lregs v2: psp, ipsr, msp, r4-r11, exc_lr (12 entries) + // lregs v1: psp, ipsr, exc_lr, r4-r11 (11 entries) + // sregs: r0-r3, r12, lr, pc, xpsr (8 entries) + // fault: cfsr, bfar, mfar, shcsr, hfsr, dfsr + let (num_lregs, exc_lr_idx) = if struct_version == 1 { + (11, 2) + } else { + (12, 11) + }; + let frame_offset = 4 + 4 * num_lregs; + let fault_offset = frame_offset + 4 * 8; + if data.len() < fault_offset + 4 * 6 { + return None; + } + + let lreg = |i: usize| u32_at(data, 4 + 4 * i); + let frame_valid = flags & PANIC_DATA_FLAG_FRAME_VALID != 0; + let sreg = |i: usize| frame_valid.then(|| u32_at(data, frame_offset + 4 * i)); + + let exc_lr = lreg(exc_lr_idx); + println!( + "=== {} EXCEPTION: {:02x} ====== xPSR: {:08x} ===", + if is_exception_from_handler_mode(exc_lr) { + "HANDLER" + } else { + "PROCESS" + }, + lreg(1) & 0xff, + sreg(7).unwrap_or(0xffffffff), + ); + for i in 0..4 { + print_reg(i, sreg(i)); + } + for i in 4..10 { + print_reg(i, Some(lreg(i - 1))); + } + print_reg(10, Some(lreg(9))); + print_reg(11, Some(lreg(10))); + print_reg(12, sreg(4)); + // v1 does not save the MSP, fall back to the PSP + let sp = if struct_version != 1 && is_frame_in_handler_stack(exc_lr) { + lreg(2) // msp + } else { + lreg(0) // psp + }; + print_reg(13, Some(sp)); + print_reg(14, sreg(5)); + print_reg(15, sreg(6)); + + let cfsr = u32_at(data, fault_offset); + let bfar = u32_at(data, fault_offset + 4); + let mfar = u32_at(data, fault_offset + 8); + let shcsr = u32_at(data, fault_offset + 12); + let hfsr = u32_at(data, fault_offset + 16); + let dfsr = u32_at(data, fault_offset + 20); + + print_fault(cfsr, hfsr, dfsr); + if cfsr & CPU_NVIC_CFSR_BFARVALID != 0 { + print!(", bfar = {:x}", bfar); + } + if cfsr & CPU_NVIC_CFSR_MFARVALID != 0 { + print!(", mfar = {:x}", mfar); + } + println!(); + println!( + "cfsr = {:x}, shcsr = {:x}, hfsr = {:x}, dfsr = {:x}", + cfsr, shcsr, hfsr, dfsr + ); + + Some(()) +} + +/// Parse and print panic data as returned by EC_CMD_GET_PANIC_INFO +/// +/// The data must not be empty. Prints warnings if the data looks +/// implausible and falls back to a hex dump if it cannot be decoded. +pub fn print_panic_info(data: &[u8]) { + // arch, struct_version, flags, reserved + const HEADER_SIZE: usize = 4; + // struct_size, magic - at the very end of the struct + const TRAILER_SIZE: usize = 8; + if data.len() < HEADER_SIZE + TRAILER_SIZE { + println!("Panic data too short ({} bytes), hex dump:", data.len()); + util::print_multiline_buffer(data, 0); + return; + } + + let arch = data[0]; + let struct_version = data[1]; + let flags = data[2]; + let struct_size = u32_at(data, data.len() - 8); + let magic = u32_at(data, data.len() - 4); + + if magic != PANIC_DATA_MAGIC { + println!( + "WARNING: Incorrect panic magic ({:#010x}), following data may be incorrect!", + magic + ); + } + if struct_size as usize != data.len() { + println!( + "WARNING: Panic struct size inconsistent ({} vs {}), following data may be incorrect!", + struct_size, + data.len() + ); + } + if struct_version > 2 { + println!( + "WARNING: Unknown panic data version ({}), following data may be incorrect!", + struct_version + ); + } + + println!( + "Saved panic data:{}", + if flags & PANIC_DATA_FLAG_OLD_HOSTCMD != 0 { + "" + } else { + " (NEW)" + } + ); + let flag_names = FLAG_NAMES + .iter() + .filter(|(bit, _)| flags & bit != 0) + .map(|(_, name)| *name) + .collect::>(); + println!("Flags: {:#04x} ({})", flags, flag_names.join(" | ")); + + let decoded = match arch { + PANIC_ARCH_CORTEX_M => print_panic_info_cm(data, struct_version, flags), + _ => { + println!("Unknown architecture ({})", arch); + None + } + }; + if decoded.is_none() { + println!("Cannot decode panic data, hex dump:"); + util::print_multiline_buffer(data, 0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + /// Build a struct version 2 Cortex-M panic data blob (116 bytes) + fn cm_v2_blob() -> Vec { + let mut data = vec![0u8; 116]; + data[0] = PANIC_ARCH_CORTEX_M; + data[1] = 2; // struct_version + data[2] = PANIC_DATA_FLAG_FRAME_VALID; + // exc_lr (lregs[11]): exception from process mode, PSP used + data[4 + 4 * 11..4 + 4 * 12].copy_from_slice(&0xfffffffdu32.to_le_bytes()); + let len = data.len(); + data[len - 8..len - 4].copy_from_slice(&116u32.to_le_bytes()); + data[len - 4..].copy_from_slice(&PANIC_DATA_MAGIC.to_le_bytes()); + data + } + + #[test] + fn decode_cm_v2() { + let data = cm_v2_blob(); + assert!(print_panic_info_cm(&data, data[1], data[2]).is_some()); + // Must not panic, falls back to hex dump on unknown arch + print_panic_info(&data); + let mut unknown_arch = cm_v2_blob(); + unknown_arch[0] = 42; + print_panic_info(&unknown_arch); + print_panic_info(&[1, 2, 3]); + } + + #[test] + fn too_short_for_registers() { + // Valid header/trailer but not enough space for Cortex-M registers + let mut data = cm_v2_blob(); + data.truncate(50); + assert!(print_panic_info_cm(&data, 2, 0).is_none()); + } +} diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 3f34981..6864a78 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -309,6 +309,10 @@ struct ClapCli { #[arg(long)] port80read: bool, + /// Show saved EC panic info + #[arg(long)] + panicinfo: bool, + /// Hash a file of arbitrary data #[arg(long)] hash: Option, @@ -602,6 +606,7 @@ pub fn parse(args: &[String]) -> Cli { protoinfo: args.protoinfo, switches: args.switches, port80read: args.port80read, + panicinfo: args.panicinfo, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), driver: args.driver, pd_addrs, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 99febd5..8573dfc 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -253,6 +253,7 @@ pub struct Cli { pub protoinfo: bool, pub switches: bool, pub port80read: bool, + pub panicinfo: bool, pub hash: Option, pub pd_addrs: Option<(u16, u16, u16)>, pub pd_ports: Option<(u8, u8, u8)>, @@ -349,6 +350,7 @@ pub fn parse(args: &[String]) -> Cli { protoinfo: cli.protoinfo, switches: cli.switches, port80read: cli.port80read, + panicinfo: cli.panicinfo, hash: cli.hash, pd_addrs: cli.pd_addrs, pd_ports: cli.pd_ports, @@ -1640,6 +1642,20 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { return 1; } } + } else if args.panicinfo { + match ec.get_panic_info() { + Ok(data) => { + if data.is_empty() { + println!("No panic data."); + } else { + chromium_ec::panic::print_panic_info(&data); + } + } + Err(err) => { + println!("Failed to get panic info: {:?}", err); + return 1; + } + } } else if args.test { println!("Self-Test"); let result = selftest(&ec); @@ -2043,6 +2059,7 @@ Options: --protoinfo Show EC host command protocol info --switches Show current EC switch positions (lid, power button, ...) --port80read Show history of port 80 writes (POST codes) + --panicinfo Show saved EC panic info --intrusion Show status of intrusion switch --inputdeck Show status of the input deck --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Laptop 12, 13, 16) diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index bd4eed4..e59826b 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -96,6 +96,7 @@ pub fn parse(args: &[String]) -> Cli { protoinfo: false, switches: false, port80read: false, + panicinfo: false, hash: None, // This is the only driver that works on UEFI driver: Some(CrosEcDriverType::Portio), @@ -566,6 +567,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--port80read" { cli.port80read = true; found_an_option = true; + } else if arg == "--panicinfo" { + cli.panicinfo = true; + found_an_option = true; } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index 3155bac..4408fe0 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --panicinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index 62b2c26..f36fc3d 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -110,6 +110,7 @@ complete -c framework_tool -l hello -d 'Check basic communication with EC' complete -c framework_tool -l protoinfo -d 'Show EC host command protocol info' complete -c framework_tool -l switches -d 'Show current EC switch positions (lid, power button, ...)' complete -c framework_tool -l port80read -d 'Show history of port 80 writes (POST codes)' +complete -c framework_tool -l panicinfo -d 'Show saved EC panic info' complete -c framework_tool -s t -l test -d 'Run self-test to check if interaction with EC is possible' complete -c framework_tool -l test-retimer -d 'Run self-test to check if interaction with retimers is possible' complete -c framework_tool -l boardid -d 'Print all board IDs' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 7ea0170..12c13fd 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -94,6 +94,7 @@ _framework_tool() { '--protoinfo[Show EC host command protocol info]' \ '--switches[Show current EC switch positions (lid, power button, ...)]' \ '--port80read[Show history of port 80 writes (POST codes)]' \ +'--panicinfo[Show saved EC panic info]' \ '-t[Run self-test to check if interaction with EC is possible]' \ '--test[Run self-test to check if interaction with EC is possible]' \ '--test-retimer[Run self-test to check if interaction with retimers is possible]' \ From 2c83095d258de45e163abddcfbf5ce615fafe92e Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 14:55:57 +0800 Subject: [PATCH 7/9] --thermalget: Add command Show thermal thresholds Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 17 ++++++ framework_lib/src/chromium_ec/command.rs | 2 + framework_lib/src/chromium_ec/commands.rs | 60 +++++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 16 +++++ framework_lib/src/commandline/clap_std.rs | 5 ++ framework_lib/src/commandline/mod.rs | 8 +++ framework_lib/src/commandline/uefi.rs | 4 ++ framework_lib/src/power.rs | 45 ++++++++++++++ .../completions/bash/framework_tool | 2 +- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 11 files changed, 160 insertions(+), 1 deletion(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 3a96cf2..8d56a5f 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -493,6 +493,23 @@ sensors and number of fans, so your output may look different: Hard: false ``` +## Check thermal thresholds + +Show at which temperatures the EC throttles the system (warn), shuts it +down (high, halt) and how the fan speed is scaled (0% at fan_off, 100% at +fan_max). A `-` means the threshold is disabled: + +``` +> sudo framework_tool --thermalget +sensor warn high halt fan_off fan_max name + 0 - 88 98 40 75 local_f75397@4c + 1 - 88 98 40 78 cpu_f75303@4d + 2 - 50 60 40 50 battery_temp@b + 3 - 87 97 40 50 ddr_f75303@4d + 4 - 120 127 103 105 peci-temp +(all temps in degrees Celsius) +``` + ## Check sensors ### Ambient Light (Laptop 13, Laptop 16) diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 1c5f359..dbc3fc1 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -40,7 +40,9 @@ pub enum EcCommands { PwmGetDuty = 0x0026, SetTabletMode = 0x0031, Port80Read = 0x0048, + ThermalGetThreshold = 0x0051, AutoFanCtrl = 0x0052, + TempSensorGetInfo = 0x0070, GpioSet = 0x0092, GpioGet = 0x0093, I2cPassthrough = 0x009e, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 3656d11..68affb4 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -565,6 +565,66 @@ impl EcRequest for EcRequestPort80Read { } } +/// Number of temperature thresholds in EcThermalConfig, see EcTempThreshold +pub const EC_TEMP_THRESH_COUNT: usize = 3; + +/// Indices into EcThermalConfig::temp_host +#[repr(usize)] +pub enum EcTempThreshold { + Warn = 0, + High = 1, + Halt = 2, +} + +/// Thermal configuration for one temperature sensor. +/// Temps are in degrees Kelvin, zero values are ignored by the thermal task. +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct EcThermalConfig { + /// Levels of hotness at which the EC throttles the AP or shuts down + pub temp_host: [u32; EC_TEMP_THRESH_COUNT], + /// Levels at which the EC releases the matching temp_host condition, + /// zero for the default 1 degree hysteresis + pub temp_host_release: [u32; EC_TEMP_THRESH_COUNT], + /// No active cooling needed below this temperature + pub temp_fan_off: u32, + /// Maximum active cooling needed above this temperature + pub temp_fan_max: u32, +} + +#[repr(C, packed)] +pub struct EcRequestThermalGetThresholdV1 { + pub sensor_num: u32, +} + +impl EcRequest for EcRequestThermalGetThresholdV1 { + fn command_id() -> EcCommands { + EcCommands::ThermalGetThreshold + } + fn command_version() -> u8 { + 1 + } +} + +#[repr(C, packed)] +pub struct EcRequestTempSensorGetInfo { + pub id: u8, +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct EcResponseTempSensorGetInfo { + /// Null-terminated name of the sensor + pub sensor_name: [u8; 32], + pub sensor_type: u8, +} + +impl EcRequest for EcRequestTempSensorGetInfo { + fn command_id() -> EcCommands { + EcCommands::TempSensorGetInfo + } +} + #[repr(C, packed)] pub struct EcRequestAutoFanCtrlV0 {} diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 6e90a47..bb26970 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1709,6 +1709,22 @@ impl CrosEc { EcRequestGetProtocolInfo {}.send_command(self) } + /// Get the thermal thresholds (in degrees Kelvin) of a temperature sensor + pub fn get_thermal_threshold(&self, sensor_num: u32) -> EcResult { + EcRequestThermalGetThresholdV1 { sensor_num }.send_command(self) + } + + /// Get the name of a temperature sensor + pub fn get_temp_sensor_name(&self, id: u8) -> EcResult { + let res = EcRequestTempSensorGetInfo { id }.send_command(self)?; + Ok(std::str::from_utf8(&res.sensor_name) + .map_err(|utf8_err| { + EcError::DeviceError(format!("Failed to decode sensor name: {:?}", utf8_err)) + })? + .trim_end_matches(char::from(0)) + .to_string()) + } + /// Get the panic data saved from the last EC panic /// /// Returns an empty vector if there is no panic data. diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 6864a78..0853f21 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -66,6 +66,10 @@ struct ClapCli { #[arg(long)] thermal: bool, + /// Print thermal thresholds of the temperature sensors + #[arg(long)] + thermalget: bool, + /// Print sensor information (ALS, G-Sensor) #[arg(long)] sensors: bool, @@ -533,6 +537,7 @@ pub fn parse(args: &[String]) -> Cli { .map(|opt| opt.map(|x| x.into_os_string().into_string().unwrap())), smartbattery_auth: args.smartbattery_auth, thermal: args.thermal, + thermalget: args.thermalget, sensors: args.sensors, fansetduty, fansetrpm, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 8573dfc..ab29150 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -194,6 +194,7 @@ pub struct Cli { pub smartbattery: Option>, pub smartbattery_auth: bool, pub thermal: bool, + pub thermalget: bool, pub sensors: bool, pub fansetduty: Option<(Option, u32)>, pub fansetrpm: Option<(Option, u32)>, @@ -293,6 +294,7 @@ pub fn parse(args: &[String]) -> Cli { compare_version: cli.compare_version, power: cli.power, thermal: cli.thermal, + thermalget: cli.thermalget, sensors: cli.sensors, // fansetduty // fansetrpm @@ -1701,6 +1703,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if args.thermal { power::print_thermal(&ec); + } else if args.thermalget { + if power::print_thermal_thresholds(&ec).is_none() { + println!("Failed to read thermal thresholds"); + return 1; + } } else if args.sensors { power::print_sensors(&ec); } else if let Some((fan, percent)) = args.fansetduty { @@ -2028,6 +2035,7 @@ Options: --compare-version Version string used to match firmware version (use with --device) --power Show current power status (battery and AC) --thermal Print thermal information (Temperatures and Fan speed) + --thermalget Print thermal thresholds of the temperature sensors --sensors Print sensor information (ALS, G-Sensor) --fansetduty Set fan duty cycle (0-100%) --fansetrpm Set fan RPM (limited by EC fan table max RPM) diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index e59826b..32718b5 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -43,6 +43,7 @@ pub fn parse(args: &[String]) -> Cli { smartbattery: None, smartbattery_auth: false, thermal: false, + thermalget: false, sensors: false, fansetduty: None, fansetrpm: None, @@ -157,6 +158,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--thermal" { cli.thermal = true; found_an_option = true; + } else if arg == "--thermalget" { + cli.thermalget = true; + found_an_option = true; } else if arg == "--sensors" { cli.sensors = true; found_an_option = true; diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index 9751290..54b7b50 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -250,6 +250,51 @@ pub fn print_memmap_version_info(ec: &CrosEc) { let _events_ver = ec.read_memory(EC_MEMMAP_EVENTS_VERSION, 2).unwrap(); } +/// Format a thermal threshold in degrees Celsius, zero means disabled +fn format_threshold(kelvin: u32) -> String { + if kelvin == 0 { + "-".to_string() + } else { + (kelvin as i32 - 273).to_string() + } +} + +/// Print the thermal thresholds of all temperature sensors +pub fn print_thermal_thresholds(ec: &CrosEc) -> Option<()> { + let temps = ec.read_memory(EC_MEMMAP_TEMP_SENSOR, 0x0F)?; + println!("sensor warn high halt fan_off fan_max name"); + let mut printed = 0; + for (i, temp) in temps.iter().enumerate() { + if TempSensor::from(*temp) == TempSensor::NotPresent { + continue; + } + // Stop on the first failure, like ectool + let Ok(cfg) = ec.get_thermal_threshold(i as u32) else { + break; + }; + let name = ec + .get_temp_sensor_name(i as u8) + .unwrap_or_else(|_| "?".to_string()); + // Copy out of the packed struct to allow taking references + let temp_host = { cfg.temp_host }; + println!( + " {:2} {:>3} {:>3} {:>3} {:>3} {:>3} {}", + i, + format_threshold(temp_host[EcTempThreshold::Warn as usize]), + format_threshold(temp_host[EcTempThreshold::High as usize]), + format_threshold(temp_host[EcTempThreshold::Halt as usize]), + format_threshold(cfg.temp_fan_off), + format_threshold(cfg.temp_fan_max), + name + ); + printed += 1; + } + if printed > 0 { + println!("(all temps in degrees Celsius)"); + } + Some(()) +} + /// Print the current EC switch positions (lid, power button, ...) pub fn print_switches(ec: &CrosEc) -> Option<()> { let switches = *ec.read_memory(EC_MEMMAP_SWITCHES, 1)?.first()?; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index 4408fe0..c8a86cc 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --panicinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --thermalget --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --panicinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index f36fc3d..36ede4e 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -91,6 +91,7 @@ complete -c framework_tool -l esrt -d 'Display the UEFI ESRT table' complete -c framework_tool -l power -d 'Show current power status of battery and AC (Add -vv for more details)' complete -c framework_tool -l smartbattery-auth -d 'Authenticate smart battery (requires unseal and auth keys)' complete -c framework_tool -l thermal -d 'Print thermal information (Temperatures and Fan speed)' +complete -c framework_tool -l thermalget -d 'Print thermal thresholds of the temperature sensors' complete -c framework_tool -l sensors -d 'Print sensor information (ALS, G-Sensor)' complete -c framework_tool -l pdports -d 'Show USB-C PD port state' complete -c framework_tool -l pdports-chromebook -d 'Show PD port info (generic Chromium EC)' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 12c13fd..0901ec2 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -75,6 +75,7 @@ _framework_tool() { '--power[Show current power status of battery and AC (Add -vv for more details)]' \ '--smartbattery-auth[Authenticate smart battery (requires unseal and auth keys)]' \ '--thermal[Print thermal information (Temperatures and Fan speed)]' \ +'--thermalget[Print thermal thresholds of the temperature sensors]' \ '--sensors[Print sensor information (ALS, G-Sensor)]' \ '--pdports[Show USB-C PD port state]' \ '--pdports-chromebook[Show PD port info (generic Chromium EC)]' \ From 8ee5f1776cfd9849510830f898fa0b85b8626fbc Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 15:22:19 +0800 Subject: [PATCH 8/9] --thermalset: Add command Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 41 +++++++++++++++++-- framework_lib/src/chromium_ec/command.rs | 1 + framework_lib/src/chromium_ec/commands.rs | 16 ++++++++ framework_lib/src/chromium_ec/mod.rs | 8 ++++ framework_lib/src/commandline/clap_std.rs | 12 ++++++ framework_lib/src/commandline/mod.rs | 18 ++++++++ framework_lib/src/commandline/uefi.rs | 19 +++++++++ framework_lib/src/power.rs | 31 ++++++++++++++ .../completions/bash/framework_tool | 6 ++- .../completions/fish/framework_tool.fish | 1 + .../completions/zsh/_framework_tool | 1 + 11 files changed, 150 insertions(+), 4 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 8d56a5f..1597517 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -495,9 +495,15 @@ sensors and number of fans, so your output may look different: ## Check thermal thresholds -Show at which temperatures the EC throttles the system (warn), shuts it -down (high, halt) and how the fan speed is scaled (0% at fan_off, 100% at -fan_max). A `-` means the threshold is disabled: +Show the temperatures at which the EC takes action, per sensor: + +- `warn`: Notify the OS with a thermal host event +- `high`: Throttle the CPU (PROCHOT) +- `halt`: Shut down the system +- `fan_off`/`fan_max`: Fan curve, fan is off below `fan_off` and at 100% + above `fan_max`. Note that not all sensors are used for fan control. + +A `-` means the threshold is disabled: ``` > sudo framework_tool --thermalget @@ -510,6 +516,35 @@ sensor warn high halt fan_off fan_max name (all temps in degrees Celsius) ``` +## Set thermal thresholds + +Adjust the thresholds shown by `--thermalget`. The arguments are +` [high [halt [fan_off [fan_max]]]]` in degrees Celsius, +in the same order as the `--thermalget` columns. Use `-1` to keep a +threshold and `0` to disable it. The tool prints the resulting +configuration: + +``` +# Lower the battery (sensor 2) high/halt thresholds to 45/55 degrees +> sudo framework_tool --thermalset 2 -1 45 55 +sensor warn high halt fan_off fan_max name + 0 - 88 98 40 75 local_f75397@4c + 1 - 88 98 40 78 cpu_f75303@4d + 2 - 45 55 40 50 battery_temp@b + 3 - 87 97 40 50 ddr_f75303@4d + 4 - 120 127 103 105 peci-temp +(all temps in degrees Celsius) + +# Make the CPU sensor (sensor 1) spin the fan up at 45 and reach 100% at 70 degrees +> sudo framework_tool --thermalset 1 -1 -1 -1 45 70 +``` + +**Note:** There is no command to restore the default thresholds. They are +compiled into the EC firmware and only re-applied when the EC itself +reboots (e.g. after the system was powered off and disconnected from +power for a while). Check the current values with `--thermalget` before +changing them, so you can restore them manually. + ## Check sensors ### Ambient Light (Laptop 13, Laptop 16) diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index dbc3fc1..ea608b7 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -40,6 +40,7 @@ pub enum EcCommands { PwmGetDuty = 0x0026, SetTabletMode = 0x0031, Port80Read = 0x0048, + ThermalSetThreshold = 0x0050, ThermalGetThreshold = 0x0051, AutoFanCtrl = 0x0052, TempSensorGetInfo = 0x0070, diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 68affb4..900383b 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -606,6 +606,22 @@ impl EcRequest for EcRequestThermalGetThresholdV1 { } } +/// Use read (EcRequestThermalGetThresholdV1) - modify - write for best results! +#[repr(C, packed)] +pub struct EcRequestThermalSetThresholdV1 { + pub sensor_num: u32, + pub cfg: EcThermalConfig, +} + +impl EcRequest<()> for EcRequestThermalSetThresholdV1 { + fn command_id() -> EcCommands { + EcCommands::ThermalSetThreshold + } + fn command_version() -> u8 { + 1 + } +} + #[repr(C, packed)] pub struct EcRequestTempSensorGetInfo { pub id: u8, diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index bb26970..16fe3a3 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1714,6 +1714,14 @@ impl CrosEc { EcRequestThermalGetThresholdV1 { sensor_num }.send_command(self) } + /// Set the thermal thresholds (in degrees Kelvin) of a temperature sensor + /// + /// Zero values disable a threshold. Read the current config with + /// get_thermal_threshold and modify it for best results. + pub fn set_thermal_threshold(&self, sensor_num: u32, cfg: EcThermalConfig) -> EcResult<()> { + EcRequestThermalSetThresholdV1 { sensor_num, cfg }.send_command(self) + } + /// Get the name of a temperature sensor pub fn get_temp_sensor_name(&self, id: u8) -> EcResult { let res = EcRequestTempSensorGetInfo { id }.send_command(self)?; diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 0853f21..75f56c2 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -70,6 +70,13 @@ struct ClapCli { #[arg(long)] thermalget: bool, + /// Set thermal thresholds of a sensor in degrees Celsius: + /// [ [ [ []]]] + /// (-1 keeps the current threshold, 0 disables it) + #[clap(num_args = 2..=6, allow_negative_numbers = true)] + #[arg(long)] + thermalset: Vec, + /// Print sensor information (ALS, G-Sensor) #[arg(long)] sensors: bool, @@ -538,6 +545,11 @@ pub fn parse(args: &[String]) -> Cli { smartbattery_auth: args.smartbattery_auth, thermal: args.thermal, thermalget: args.thermalget, + thermalset: if args.thermalset.is_empty() { + None + } else { + Some(args.thermalset.clone()) + }, sensors: args.sensors, fansetduty, fansetrpm, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index ab29150..4147762 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -195,6 +195,7 @@ pub struct Cli { pub smartbattery_auth: bool, pub thermal: bool, pub thermalget: bool, + pub thermalset: Option>, pub sensors: bool, pub fansetduty: Option<(Option, u32)>, pub fansetrpm: Option<(Option, u32)>, @@ -295,6 +296,7 @@ pub fn parse(args: &[String]) -> Cli { power: cli.power, thermal: cli.thermal, thermalget: cli.thermalget, + thermalset: cli.thermalset, sensors: cli.sensors, // fansetduty // fansetrpm @@ -1708,6 +1710,19 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("Failed to read thermal thresholds"); return 1; } + } else if let Some(values) = &args.thermalset { + // Sensor number followed by up to 5 thresholds + let sensor = values[0]; + if sensor < 0 { + println!("Sensor number must not be negative"); + return 1; + } + if let Err(err) = power::set_thermal_thresholds(&ec, sensor as u32, &values[1..]) { + println!("Failed to set thermal thresholds: {:?}", err); + return 1; + } + // Show the resulting configuration + let _ = power::print_thermal_thresholds(&ec); } else if args.sensors { power::print_sensors(&ec); } else if let Some((fan, percent)) = args.fansetduty { @@ -2036,6 +2051,9 @@ Options: --power Show current power status (battery and AC) --thermal Print thermal information (Temperatures and Fan speed) --thermalget Print thermal thresholds of the temperature sensors + --thermalset [ [ [ []]]] + Set thermal thresholds of a sensor in degrees Celsius + (-1 keeps the current threshold, 0 disables it) --sensors Print sensor information (ALS, G-Sensor) --fansetduty Set fan duty cycle (0-100%) --fansetrpm Set fan RPM (limited by EC fan table max RPM) diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 32718b5..929673d 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -44,6 +44,7 @@ pub fn parse(args: &[String]) -> Cli { smartbattery_auth: false, thermal: false, thermalget: false, + thermalset: None, sensors: false, fansetduty: None, fansetrpm: None, @@ -161,6 +162,24 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--thermalget" { cli.thermalget = true; found_an_option = true; + } else if arg == "--thermalset" { + let mut values = Vec::new(); + for value_arg in &args[i + 1..] { + if let Ok(value) = value_arg.parse::() { + values.push(value); + } else { + break; + } + } + cli.thermalset = if (2..=6).contains(&values.len()) { + Some(values) + } else { + println!( + "--thermalset requires 2 to 6 numbers. [ [ [ []]]]" + ); + None + }; + found_an_option = true; } else if arg == "--sensors" { cli.sensors = true; found_an_option = true; diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index 54b7b50..afd7086 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -295,6 +295,37 @@ pub fn print_thermal_thresholds(ec: &CrosEc) -> Option<()> { Some(()) } +/// Set thermal thresholds (in degrees Celsius) of one temperature sensor +/// +/// Values are warn, high, halt, fan_off, fan_max in this order, trailing +/// values can be omitted. A negative value keeps the current threshold, +/// zero disables it. +pub fn set_thermal_thresholds(ec: &CrosEc, sensor: u32, values: &[i32]) -> EcResult<()> { + // Read-modify-write, only overwrite the provided thresholds + let mut cfg = ec.get_thermal_threshold(sensor)?; + let mut temp_host = cfg.temp_host; + for (i, &celsius) in values.iter().enumerate().take(5) { + if celsius < 0 { + continue; + } + // Zero means disabled, don't offset it to 273 K + let kelvin = if celsius == 0 { + 0 + } else { + celsius.checked_add(273).ok_or_else(|| { + EcError::DeviceError(format!("Temperature {} out of range", celsius)) + })? as u32 + }; + match i { + 0..=2 => temp_host[i] = kelvin, + 3 => cfg.temp_fan_off = kelvin, + _ => cfg.temp_fan_max = kelvin, + } + } + cfg.temp_host = temp_host; + ec.set_thermal_threshold(sensor, cfg) +} + /// Print the current EC switch positions (lid, power button, ...) pub fn print_switches(ec: &CrosEc) -> Option<()> { let switches = *ec.read_memory(EC_MEMMAP_SWITCHES, 1)?.first()?; diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index c8a86cc..bc04394 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -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 --smartbattery --smartbattery-auth --thermal --thermalget --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --panicinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --thermalget --thermalset --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --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-full-ec --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 --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --sysinfo --uptimeinfo --s0ix-counter --hello --protoinfo --switches --port80read --panicinfo --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -45,6 +45,10 @@ _framework_tool() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --thermalset) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --fansetduty) COMPREPLY=($(compgen -f "${cur}")) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index 36ede4e..97c2018 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -9,6 +9,7 @@ ac-left\t'' ac-right\t''" complete -c framework_tool -l compare-version -r complete -c framework_tool -l smartbattery -d 'Show detailed smart battery information, or load from dump file' -r -F +complete -c framework_tool -l thermalset -d 'Set thermal thresholds of a sensor in degrees Celsius: [ [ [ []]]] (-1 keeps the current threshold, 0 disables it)' -r complete -c framework_tool -l fansetduty -d 'Set fan duty cycle (0-100%)' -r complete -c framework_tool -l fansetrpm -d 'Set fan RPM (limited by EC fan table max RPM)' -r complete -c framework_tool -l autofanctrl -d 'Turn on automatic fan speed control' -r diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 0901ec2..e9adbb0 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -19,6 +19,7 @@ _framework_tool() { '--device=[]:DEVICE:(bios ec pd0 pd1 rtm01 rtm23 ac-left ac-right)' \ '--compare-version=[]:COMPARE_VERSION:_default' \ '--smartbattery=[Show detailed smart battery information, or load from dump file]::FILE:_files' \ +'*--thermalset=[Set thermal thresholds of a sensor in degrees Celsius\: \[ \[ \[ \[\]\]\]\] (-1 keeps the current threshold, 0 disables it)]:THERMALSET:_default:THERMALSET:_default' \ '*--fansetduty=[Set fan duty cycle (0-100%)]::FANSETDUTY:_default' \ '*--fansetrpm=[Set fan RPM (limited by EC fan table max RPM)]::FANSETRPM:_default' \ '--autofanctrl=[Turn on automatic fan speed control]::AUTOFANCTRL:_default' \ From dc460b99f5f9fd7b73aae3322368a492dbf4e519 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 6 Jul 2026 15:29:37 +0800 Subject: [PATCH 9/9] --thermal: Remove hardcoded names We can figure them out from EC_CMD_TEMP_SENSOR_GET_INFO Signed-off-by: Daniel Schaefer --- EXAMPLES.md | 46 +++++++++------- framework_lib/src/power.rs | 107 ++++++++----------------------------- 2 files changed, 47 insertions(+), 106 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 1597517..5e24685 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -481,13 +481,16 @@ Board IDs Example on one system, note that different systems have different thermal sensors and number of fans, so your output may look different: +The sensor names are reported by the EC firmware: + ``` > sudo framework_tool --thermal - F75303_Local: 43 C - F75303_CPU: 44 C - F75303_DDR: 39 C - APU: 62 C - API Fan: 0 RPM + local_f75397@4c: 36 C + cpu_f75303@4d: 37 C + battery_temp@b: 32 C + ddr_f75303@4d: 35 C + peci-temp: 42 C + APU Fan: 0 RPM AP Throttle Status Soft: false Hard: false @@ -571,30 +574,33 @@ Accelerometers: > sudo framework_tool --fansetduty 100 > sudo framework_tool --fansetduty 0 100 > sudo framework_tool --thermal - F75303_Local: 40 C - F75303_CPU: 41 C - F75303_DDR: 37 C - APU: 42 C - APU Fan: 7281 RPM + local_f75397@4c: 40 C + cpu_f75303@4d: 41 C + battery_temp@b: 32 C + ddr_f75303@4d: 37 C + peci-temp: 42 C + APU Fan: 7281 RPM # Set a target RPM (all or just fan ID=0) > sudo framework_tool --fansetrpm 3141 > sudo framework_tool --fansetrpm 0 3141 > sudo framework_tool --thermal - F75303_Local: 41 C - F75303_CPU: 42 C - F75303_DDR: 37 C - APU: 44 C - APU Fan: 3171 RPM + local_f75397@4c: 41 C + cpu_f75303@4d: 42 C + battery_temp@b: 32 C + ddr_f75303@4d: 37 C + peci-temp: 44 C + APU Fan: 3171 RPM # And back to normal > sudo framework_tool --autofanctrl > sudo framework_tool --thermal - F75303_Local: 40 C - F75303_CPU: 40 C - F75303_DDR: 38 C - APU: 42 C - APU Fan: 0 RPM + local_f75397@4c: 40 C + cpu_f75303@4d: 40 C + battery_temp@b: 32 C + ddr_f75303@4d: 38 C + peci-temp: 42 C + APU Fan: 0 RPM # Or just for a specific fan (e.g. on Framework Desktop) > sudo framework_tool --autofanctrl 0 diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index afd7086..0453165 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -14,7 +14,7 @@ use crate::chromium_ec::command::EcRequestRaw; use crate::chromium_ec::commands::*; use crate::chromium_ec::*; use crate::smbios; -use crate::util::{Platform, PlatformFamily}; +use crate::util::PlatformFamily; /// Maximum length of strings in memmap const EC_MEMMAP_TEXT_MAX: u16 = 8; @@ -477,92 +477,27 @@ pub fn print_thermal(ec: &CrosEc) { let temps = ec.read_memory(EC_MEMMAP_TEMP_SENSOR, 0x0F).unwrap(); let fans = ec.read_memory(EC_MEMMAP_FAN, 0x08).unwrap(); - let platform = smbios::get_platform(); let family = smbios::get_family(); - let remaining_sensors = match platform { - Some(Platform::IntelGen11) | Some(Platform::IntelGen12) | Some(Platform::IntelGen13) => { - println!(" F75303_Local: {:>4}", TempSensor::from(temps[0])); - println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1])); - println!(" F75303_DDR: {:>4}", TempSensor::from(temps[2])); - println!(" Battery: {:>4}", TempSensor::from(temps[3])); - println!(" PECI: {:>4}", TempSensor::from(temps[4])); - if matches!( - platform, - Some(Platform::IntelGen12) | Some(Platform::IntelGen13) - ) { - println!(" F57397_VCCGT: {:>4}", TempSensor::from(temps[5])); - } - 2 - } - - Some(Platform::IntelCoreUltra1) | Some(Platform::IntelCoreUltra3) => { - println!(" F75303_Local: {:>4}", TempSensor::from(temps[0])); - println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1])); - println!(" Battery: {:>4}", TempSensor::from(temps[2])); - println!(" F75303_DDR: {:>4}", TempSensor::from(temps[3])); - println!(" PECI: {:>4}", TempSensor::from(temps[4])); - 3 - } - - Some(Platform::Framework12IntelGen13) => { - println!(" F75303_CPU: {:>4}", TempSensor::from(temps[0])); - println!(" F75303_Skin: {:>4}", TempSensor::from(temps[1])); - println!(" F75303_Local: {:>4}", TempSensor::from(temps[2])); - println!(" Battery: {:>4}", TempSensor::from(temps[3])); - println!(" PECI: {:>4}", TempSensor::from(temps[4])); - println!(" Charger IC {:>4}", TempSensor::from(temps[5])); - 2 - } - - Some( - Platform::Framework13Amd7080 - | Platform::Framework13AmdAi300 - | Platform::Framework16Amd7080 - | Platform::Framework16AmdAi300, - ) => { - println!(" F75303_Local: {:>4}", TempSensor::from(temps[0])); - println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1])); - println!(" F75303_DDR: {:>4}", TempSensor::from(temps[2])); - println!(" APU: {:>4}", TempSensor::from(temps[3])); - if family == Some(PlatformFamily::Framework16) { - println!(" dGPU VR: {:>4}", TempSensor::from(temps[4])); - println!(" dGPU VRAM: {:>4}", TempSensor::from(temps[5])); - println!(" dGPU AMB: {:>4}", TempSensor::from(temps[6])); - println!(" dGPU temp: {:>4}", TempSensor::from(temps[7])); - 0 - } else { - 4 - } - } - Some(Platform::FrameworkDesktopAmdAiMax300) => { - println!(" F75303_APU: {:>4}", TempSensor::from(temps[0])); - println!(" F75303_DDR: {:>4}", TempSensor::from(temps[1])); - println!(" F75303_AMB: {:>4}", TempSensor::from(temps[2])); - println!(" APU: {:>4}", TempSensor::from(temps[3])); - println!(" Virtual: {:>4}", TempSensor::from(temps[4])); - 3 - } - - _ => { - println!(" Temp 0: {:>4}", TempSensor::from(temps[0])); - println!(" Temp 1: {:>4}", TempSensor::from(temps[1])); - println!(" Temp 2: {:>4}", TempSensor::from(temps[2])); - println!(" Temp 3: {:>4}", TempSensor::from(temps[3])); - println!(" Temp 4: {:>4}", TempSensor::from(temps[4])); - println!(" Temp 5: {:>4}", TempSensor::from(temps[5])); - println!(" Temp 6: {:>4}", TempSensor::from(temps[6])); - println!(" Temp 7: {:>4}", TempSensor::from(temps[7])); - 0 - } - }; - - // Just in case EC has more sensors than we know about, print them - for (i, temp) in temps.iter().enumerate().take(8).skip(8 - remaining_sensors) { + let mut sensors = vec![]; + for (i, temp) in temps.iter().enumerate() { let temp = TempSensor::from(*temp); - if temp != TempSensor::NotPresent { - println!(" Temp {}: {:>4}", i, temp); + if temp == TempSensor::NotPresent { + continue; } + // All our EC firmware supports reporting the sensor name + let name = ec + .get_temp_sensor_name(i as u8) + .unwrap_or_else(|_| format!("Temp {}", i)); + sensors.push((name, temp)); + } + let width = sensors + .iter() + .map(|(name, _)| name.len() + 1) + .max() + .unwrap_or(13); + for (name, temp) in sensors { + println!(" {:4}", format!("{name}:"), temp); } for i in 0..EC_FAN_SPEED_ENTRIES { @@ -580,11 +515,11 @@ pub fn print_thermal(ec: &CrosEc) { let fan = u16::from_le_bytes([fans[i * 2], fans[1 + i * 2]]); if fan == EC_FAN_SPEED_STALLED_DEPRECATED { - println!(" {name:<11} {:>4} RPM (Stalled)", fan); + println!(" {name:4} RPM (Stalled)", fan); } else if fan == EC_FAN_SPEED_NOT_PRESENT { - info!(" {name:<11} Not present"); + info!(" {name:4} RPM", fan); + println!(" {name:4} RPM", fan); } }