From 0411259365e39a287ccf8e18b37ffbdab3f9e9c6 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 30 May 2025 02:06:54 +0800 Subject: [PATCH 1/8] --versions: Add support for dumping NVIDIA graphics info Trying it out with thunderbolt eGPUs Signed-off-by: Daniel Schaefer --- Cargo.lock | 123 ++++++++++++++++++++++++++- framework_lib/Cargo.toml | 2 + framework_lib/src/commandline/mod.rs | 87 +++++++++++++++++++ 3 files changed, 210 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be5f5f4d..b1f289ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,6 +331,41 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.98", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.98", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -397,6 +432,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -425,6 +466,7 @@ dependencies = [ "num", "num-derive", "num-traits", + "nvml-wrapper", "plain", "redox_hwio", "regex", @@ -646,6 +688,12 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -733,6 +781,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.0", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -921,6 +979,29 @@ dependencies = [ "autocfg", ] +[[package]] +name = "nvml-wrapper" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5c6c0ef9702176a570f06ad94f3198bc29c524c8b498f1b9346e1b1bdcbb3a" +dependencies = [ + "bitflags 2.6.0", + "libloading", + "nvml-wrapper-sys", + "static_assertions", + "thiserror 1.0.69", + "wrapcenum-derive", +] + +[[package]] +name = "nvml-wrapper-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd23dbe2eb8d8335d2bce0299e0a07d6a63c089243d626ca75b770a962ff49e6" +dependencies = [ + "libloading", +] + [[package]] name = "once_cell" version = "1.16.0" @@ -1170,6 +1251,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "static_vcruntime" version = "2.0.0" @@ -1213,13 +1300,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -1856,7 +1963,19 @@ dependencies = [ "futures", "log", "serde", - "thiserror", + "thiserror 2.0.11", "windows 0.59.0", "windows-core", ] + +[[package]] +name = "wrapcenum-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76ff259533532054cfbaefb115c613203c73707017459206380f03b3b3f266e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.98", +] diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 162538f4..a1abfef3 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -52,6 +52,7 @@ clap-num = { version = "1.2.0" } clap-verbosity-flag = { version = "2.2.1" } windows-version = "0.1.4" winreg = "0.55.0" +nvml-wrapper = "0.11.0" [target.'cfg(unix)'.dependencies] libc = "0.2.155" @@ -62,6 +63,7 @@ env_logger = "0.11" clap = { version = "4.5", features = ["derive", "cargo"] } clap-num = { version = "1.2.0" } clap-verbosity-flag = { version = "2.2.1" } +nvml-wrapper = "0.11.0" [target.'cfg(windows)'.dependencies.windows] version = "0.59.0" diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index cf45bdbe..6573d084 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -72,6 +72,9 @@ use sha2::{Digest, Sha256, Sha384, Sha512}; //use smbioslib::*; use smbioslib::{DefinedStruct, SMBiosInformation}; +#[cfg(any(target_os = "linux", target_os = "windows"))] +use nvml_wrapper::{enum_wrappers::device::TemperatureSensor, Nvml}; + use crate::chromium_ec::{CrosEc, CrosEcDriverType, HardwareDeviceType}; #[cfg(feature = "uefi")] @@ -363,6 +366,8 @@ fn print_pd_details(ec: &CrosEc) { #[cfg(feature = "hidapi")] const NOT_SET: &str = "NOT SET"; +const UNKNOWN: &str = "Unknown"; + #[cfg(feature = "rusb")] fn print_audio_card_details() { check_synaptics_fw_version(); @@ -767,6 +772,88 @@ fn print_versions(ec: &CrosEc) { } } } + + #[cfg(any(target_os = "linux", target_os = "windows"))] + print_nvidia_details(); +} + +#[cfg(any(target_os = "linux", target_os = "windows"))] +fn print_nvidia_details() { + let nvml = match Nvml::init() { + Ok(nvml) => nvml, + Err(err) => { + debug!("Nvidia, library init fail: {:?}", err); + return; + } + }; + // Get the first `Device` (GPU) in the system + let device = match nvml.device_by_index(0) { + Ok(device) => device, + Err(err) => { + debug!("Nvidia, device not found: {:?}", err); + return; + } + }; + + println!("NVIDIA GPU"); + // GeForce + info!(" BRAND: {:?}", device.brand()); + println!( + " Name: {}", + device.name().unwrap_or(UNKNOWN.to_string()) + ); + println!(" Architecture: {:?}", device.architecture()); + println!( + " VBIOS Version: {}", + device.vbios_version().unwrap_or(UNKNOWN.to_string()) + ); + println!( + " INFO ROM Ver: {}", + device + .info_rom_image_version() + .unwrap_or(UNKNOWN.to_string()) + ); + println!(" PCI Info: {:X?}", device.pci_info()); + println!(" Performance State:{:?}", device.performance_state()); + println!( + " Pwr Mgmt Limit Df:{:?}mW", + device.power_management_limit_default() + ); + // NotSupported + println!( + " Pwr Mgmt Limit: {:?}mW", + device.power_management_limit() + ); + println!( + " Pwr Mgmt Limit Cs:{:?}", + device.power_management_limit_constraints() + ); + println!(" Pwr Usage: {:?}mW", device.power_usage()); + println!( + " Total Energy: {:?}mJ", + device.total_energy_consumption() + ); + // 0/NotSupported right now + println!(" Serialnum: {:?}", device.serial()); + println!( + " Throttle Reason: {:?}", + device.current_throttle_reasons() + ); + println!( + " Temperature: {:?}C", + device.temperature(TemperatureSensor::Gpu) + ); + //println!(" Temperature Thres:{:?}C", device.temperature_threshold()); + println!(" Util Rate: {:?}", device.utilization_rates()); + println!(" Memory Info: {:?}", device.memory_info()); + // Not supported + println!(" Part Number: {:?}", device.board_part_number()); + println!(" Board ID: {:?}", device.board_id()); + // 0 + println!(" Num Fans: {:?}", device.num_fans()); + // Works + println!(" Display Active?: {:?}", device.is_display_active()); + println!(" Display Conn?: {:?}", device.is_display_connected()); } fn print_esrt() { From 59ea59cba80771df61f5ecbbb721665604b0a664 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 19 Sep 2025 12:58:01 +0800 Subject: [PATCH 2/8] link to nvidia lib only if nvidia feature it's proprietary, many distributions won't ship it by default. And almost all of our systems don't use nvidia Signed-off-by: Daniel Schaefer --- framework_lib/Cargo.toml | 5 +++-- framework_lib/src/commandline/mod.rs | 14 ++++++-------- framework_tool/Cargo.toml | 1 + 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index a1abfef3..4213b22c 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -18,6 +18,7 @@ readonly = [ ] rusb = ["dep:rusb"] hidapi = ["dep:hidapi"] uefi = [ "lazy_static/spin_no_std" ] +nvidia = ["dep:nvml-wrapper"] [build-dependencies] built = { version = "0.5", features = ["chrono", "git2"] } @@ -52,7 +53,7 @@ clap-num = { version = "1.2.0" } clap-verbosity-flag = { version = "2.2.1" } windows-version = "0.1.4" winreg = "0.55.0" -nvml-wrapper = "0.11.0" +nvml-wrapper = { version = "0.11.0", optional = true } [target.'cfg(unix)'.dependencies] libc = "0.2.155" @@ -63,7 +64,7 @@ env_logger = "0.11" clap = { version = "4.5", features = ["derive", "cargo"] } clap-num = { version = "1.2.0" } clap-verbosity-flag = { version = "2.2.1" } -nvml-wrapper = "0.11.0" +nvml-wrapper = { version = "0.11.0", optional = true } [target.'cfg(windows)'.dependencies.windows] version = "0.59.0" diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 6573d084..ec6dcb47 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -72,7 +72,7 @@ use sha2::{Digest, Sha256, Sha384, Sha512}; //use smbioslib::*; use smbioslib::{DefinedStruct, SMBiosInformation}; -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(feature = "nvidia")] use nvml_wrapper::{enum_wrappers::device::TemperatureSensor, Nvml}; use crate::chromium_ec::{CrosEc, CrosEcDriverType, HardwareDeviceType}; @@ -366,8 +366,6 @@ fn print_pd_details(ec: &CrosEc) { #[cfg(feature = "hidapi")] const NOT_SET: &str = "NOT SET"; -const UNKNOWN: &str = "Unknown"; - #[cfg(feature = "rusb")] fn print_audio_card_details() { check_synaptics_fw_version(); @@ -773,11 +771,11 @@ fn print_versions(ec: &CrosEc) { } } - #[cfg(any(target_os = "linux", target_os = "windows"))] + #[cfg(feature = "nvidia")] print_nvidia_details(); } -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(feature = "nvidia")] fn print_nvidia_details() { let nvml = match Nvml::init() { Ok(nvml) => nvml, @@ -800,18 +798,18 @@ fn print_nvidia_details() { info!(" BRAND: {:?}", device.brand()); println!( " Name: {}", - device.name().unwrap_or(UNKNOWN.to_string()) + device.name().unwrap_or("Unknown".to_string()) ); println!(" Architecture: {:?}", device.architecture()); println!( " VBIOS Version: {}", - device.vbios_version().unwrap_or(UNKNOWN.to_string()) + device.vbios_version().unwrap_or("Unknown".to_string()) ); println!( " INFO ROM Ver: {}", device .info_rom_image_version() - .unwrap_or(UNKNOWN.to_string()) + .unwrap_or("Unknown".to_string()) ); println!(" PCI Info: {:X?}", device.pci_info()); println!(" Performance State:{:?}", device.performance_state()); diff --git a/framework_tool/Cargo.toml b/framework_tool/Cargo.toml index 6dceeabb..27c3c034 100644 --- a/framework_tool/Cargo.toml +++ b/framework_tool/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [features] default = [ ] readonly = [ "framework_lib/readonly" ] +nvidia = [ "framework_lib/nvidia" ] [dependencies.framework_lib] path = "../framework_lib" From c08f6cce934079b16d75156e8b78a64b7f967bee Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 19 Oct 2025 14:41:45 +0800 Subject: [PATCH 3/8] gh-actions: Build release builds with nvidia library Signed-off-by: Daniel Schaefer --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7354b8a0..a46fc070 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: path: target/debug/framework_tool - name: Build Linux tool (Release) - run: cargo build -p framework_tool --release + run: cargo build -p framework_tool --release --features nvidia - name: Upload Linux App uses: actions/upload-artifact@v4 @@ -124,7 +124,7 @@ jobs: - name: Build Windows tool run: | cargo build -p framework_tool - cargo build -p framework_tool --release + cargo build -p framework_tool --release --features nvidia - name: Check if Windows tool can start run: cargo run -- --help --release From 2328b391fb4bf7ea7c4847313da7ad207cc0a11a Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 13 Jan 2026 22:50:08 +0800 Subject: [PATCH 4/8] eeprom: Make --dump-gpu-descriptor-file more reliable I often noticed it returning invalid data. Using 16 bit addressing seems to work better. Signed-off-by: Daniel Schaefer --- .../src/chromium_ec/i2c_passthrough.rs | 60 +++++++++++++++++++ framework_lib/src/chromium_ec/mod.rs | 3 +- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/framework_lib/src/chromium_ec/i2c_passthrough.rs b/framework_lib/src/chromium_ec/i2c_passthrough.rs index 6b0a0f00..019fcdee 100644 --- a/framework_lib/src/chromium_ec/i2c_passthrough.rs +++ b/framework_lib/src/chromium_ec/i2c_passthrough.rs @@ -121,6 +121,66 @@ pub fn i2c_read( }) } +/// I2C read with 16-bit addressing (for larger EEPROMs like 24C32+) +/// Always sends a 2-byte address, even for addr=0 +pub fn i2c_read_16bit_addr( + ec: &CrosEc, + i2c_port: u8, + i2c_addr: u16, + addr: u16, + len: u16, +) -> EcResult { + trace!( + "i2c_read_16bit_addr(i2c_port: 0x{:X}, i2c_addr: 0x{:X}, addr: 0x{:X}, len: 0x{:X})", + i2c_port, + i2c_addr, + addr, + len + ); + if usize::from(len) > MAX_I2C_CHUNK { + return EcResult::Err(EcError::DeviceError(format!( + "i2c_read too long. Must be <128, is: {}", + len + ))); + } + // Always use 16-bit addressing (big-endian for EEPROM) + let addr_bytes = u16::to_be_bytes(addr).to_vec(); + let messages = vec![ + EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr, + transfer_len: addr_bytes.len() as u16, + }, + EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr + I2C_READ_FLAG, + transfer_len: len, // How much to read + }, + ]; + let msgs_len = size_of::() * messages.len(); + let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; + + let params = EcParamsI2cPassthru { + port: i2c_port, + messages: messages.len() as u8, + msg: [], // Messages are copied right after this struct + }; + let params_len = size_of::(); + let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; + + let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len()]; + buffer[0..params_len].copy_from_slice(params_buffer); + buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); + buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes); + + let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?; + let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + let res_data = &data[size_of::<_EcI2cPassthruResponse>()..]; + debug_assert!(res.messages as usize == messages.len() || res.messages == 0); + Ok(EcI2cPassthruResponse { + i2c_status: res.i2c_status, + data: res_data.to_vec(), + }) +} + /* Write address and bytes in a single I2C transfer */ pub fn i2c_write_block( ec: &CrosEc, diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f2d980f6..9fe52081 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1295,7 +1295,8 @@ impl CrosEc { let remaining = len - data.len() as u16; let chunk_len = std::cmp::min(i2c_passthrough::MAX_I2C_CHUNK, remaining.into()); let offset = addr + data.len() as u16; - let i2c_response = i2c_passthrough::i2c_read( + // Use 16-bit addressing for GPU EEPROM (required for larger EEPROMs) + let i2c_response = i2c_passthrough::i2c_read_16bit_addr( self, eeprom_port, eeprom_addr, From c2e93850ba2d334b0b2bc799bc2d7eb9149039b4 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 13 Jan 2026 22:51:17 +0800 Subject: [PATCH 5/8] nvidia: Improve printing Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/mod.rs | 54 ++++++++++++---------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index ec6dcb47..2e492cbb 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -780,7 +780,7 @@ fn print_nvidia_details() { let nvml = match Nvml::init() { Ok(nvml) => nvml, Err(err) => { - debug!("Nvidia, library init fail: {:?}", err); + error!("Nvidia, library init fail: {:?}", err); return; } }; @@ -788,70 +788,62 @@ fn print_nvidia_details() { let device = match nvml.device_by_index(0) { Ok(device) => device, Err(err) => { - debug!("Nvidia, device not found: {:?}", err); + error!("Nvidia, device not found: {:?}", err); return; } }; println!("NVIDIA GPU"); - // GeForce - info!(" BRAND: {:?}", device.brand()); println!( - " Name: {}", + " Name: {}", device.name().unwrap_or("Unknown".to_string()) ); - println!(" Architecture: {:?}", device.architecture()); + println!(" Architecture: {:?}", device.architecture()); println!( - " VBIOS Version: {}", + " VBIOS Version: {}", device.vbios_version().unwrap_or("Unknown".to_string()) ); println!( - " INFO ROM Ver: {}", + " INFO ROM Ver: {}", device .info_rom_image_version() .unwrap_or("Unknown".to_string()) ); - println!(" PCI Info: {:X?}", device.pci_info()); - println!(" Performance State:{:?}", device.performance_state()); + println!(" PCI Info: {:X?}", device.pci_info()); + println!(" Performance State: {:?}", device.performance_state()); println!( - " Pwr Mgmt Limit Df:{:?}mW", + " Pwr Mgmt Limit Def: {:?}mW", device.power_management_limit_default() ); - // NotSupported println!( - " Pwr Mgmt Limit: {:?}mW", + " Pwr Mgmt Limit: {:?}mW", device.power_management_limit() ); println!( - " Pwr Mgmt Limit Cs:{:?}", + " Pwr Mgmt Limit Con: {:?}", device.power_management_limit_constraints() ); - println!(" Pwr Usage: {:?}mW", device.power_usage()); + println!(" Pwr Usage: {:?}mW", device.power_usage()); println!( - " Total Energy: {:?}mJ", + " Total Energy: {:?}mJ", device.total_energy_consumption() ); - // 0/NotSupported right now - println!(" Serialnum: {:?}", device.serial()); + println!(" Serial Number: {:?}", device.serial()); println!( - " Throttle Reason: {:?}", + " Throttle Reason: {:?}", device.current_throttle_reasons() ); println!( - " Temperature: {:?}C", + " Temperature: {:?}C", device.temperature(TemperatureSensor::Gpu) ); - //println!(" Temperature Thres:{:?}C", device.temperature_threshold()); - println!(" Util Rate: {:?}", device.utilization_rates()); - println!(" Memory Info: {:?}", device.memory_info()); - // Not supported - println!(" Part Number: {:?}", device.board_part_number()); - println!(" Board ID: {:?}", device.board_id()); - // 0 - println!(" Num Fans: {:?}", device.num_fans()); - // Works - println!(" Display Active?: {:?}", device.is_display_active()); - println!(" Display Conn?: {:?}", device.is_display_connected()); + println!(" Util Rate: {:?}", device.utilization_rates()); + println!(" Memory Info: {:?}", device.memory_info()); + println!(" Part Number: {:?}", device.board_part_number()); + println!(" Board ID: {:?}", device.board_id()); + println!(" Num Fans: {:?}", device.num_fans()); + println!(" Display Active?: {:?}", device.is_display_active()); + println!(" Display Conn?: {:?}", device.is_display_connected()); } fn print_esrt() { From 50ca94ba73a3130ec4f6e9c8a19c1753d246e777 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 13 Jan 2026 22:58:30 +0800 Subject: [PATCH 6/8] Improve --dump-gpu-descriptor-file `--dump-gpu-descriptor-file -` now prints to stdout instead of writing to a file for easier debugging. If the structure is corrupt, we can still dump all 256 bytes. More error reporting to figure out what's wrong if something is wrong. Signed-off-by: Daniel Schaefer --- framework_lib/src/chromium_ec/mod.rs | 2 +- framework_lib/src/commandline/mod.rs | 64 +++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 9fe52081..f69e395e 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -1312,7 +1312,7 @@ impl CrosEc { data.extend(i2c_response.data); } - Ok(data) + Ok(data[..(len.into())].to_vec()) } pub fn write_ec_gpu_chunk(&self, offset: u16, data: &[u8]) -> EcResult<()> { diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 2e492cbb..43bdb31f 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -899,18 +899,66 @@ fn dump_ec_flash(ec: &CrosEc, dump_path: &str) { } fn dump_dgpu_eeprom(ec: &CrosEc, dump_path: &str) { - let flash_bin = ec.read_gpu_descriptor().unwrap(); + // Read raw bytes from EEPROM + let raw_bytes = match ec.read_ec_gpu_chunk(0x00, 256) { + Ok(data) => data, + Err(err) => { + error!("Failed to read EEPROM: {:?}", err); + return; + } + }; + + // For stdout, just print the raw bytes + if dump_path == "-" { + println!("{:02X?}", raw_bytes); + println!("Read {} bytes from EEPROM", raw_bytes.len()); + return; + } + + // Check if header is valid by examining magic bytes + let expected_magic = [0x32, 0xAC, 0x00, 0x00]; + let has_valid_header = raw_bytes.len() >= 4 && raw_bytes[0..4] == expected_magic; + + let flash_bin = if has_valid_header { + // Header looks valid, try to read the full descriptor + match ec.read_gpu_descriptor() { + Ok(data) => data, + Err(err) => { + error!("GPU descriptor read failed: {:?}", err); + println!("Falling back to raw EEPROM dump (256 bytes)"); + raw_bytes + } + } + } else { + error!( + "GPU descriptor invalid: magic {:02X?} != expected {:02X?}", + &raw_bytes[0..4], + expected_magic + ); + println!("Dumping raw EEPROM (256 bytes)"); + raw_bytes + }; #[cfg(not(feature = "uefi"))] { - let mut file = fs::File::create(dump_path).unwrap(); - file.write_all(&flash_bin).unwrap(); + match fs::File::create(dump_path) { + Ok(mut file) => { + if let Err(err) = file.write_all(&flash_bin) { + error!("Failed to write file: {:?}", err); + return; + } + } + Err(err) => { + error!("Failed to create file: {:?}", err); + return; + } + } } #[cfg(feature = "uefi")] { - let ret = crate::uefi::fs::shell_write_file(dump_path, &flash_bin); - if ret.is_err() { - println!("Failed to dump EC FW image."); + if let Err(err) = crate::uefi::fs::shell_write_file(dump_path, &flash_bin) { + error!("Failed to dump EC FW image: {:?}", err); + return; } } println!("Wrote {} bytes to {}", flash_bin.len(), dump_path); @@ -1531,7 +1579,9 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("Unsupported on this platform"); } } else if let Some(dump_path) = &args.dump_gpu_descriptor_file { - println!("Dumping to {}", dump_path); + if dump_path != "-" { + println!("Dumping to {}", dump_path); + } dump_dgpu_eeprom(&ec, dump_path); } From c7b5065c7b2b65a5c8204401148fe4c6560ca858 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 13 Jan 2026 23:00:43 +0800 Subject: [PATCH 7/8] --version: Add better debug messages for parade_retimer -v or -vv includes the necessary debug info. Signed-off-by: Daniel Schaefer --- framework_lib/src/parade_retimer.rs | 32 ++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/framework_lib/src/parade_retimer.rs b/framework_lib/src/parade_retimer.rs index f5d3b1f7..6f0cf8cd 100644 --- a/framework_lib/src/parade_retimer.rs +++ b/framework_lib/src/parade_retimer.rs @@ -10,10 +10,12 @@ use crate::os_specific; pub fn get_version(ec: &CrosEc) -> EcResult>> { let res = EcRequestGetGpuPcie {}.send_command(ec)?; let vendor: Option = FromPrimitive::from_u8(res.gpu_vendor); + info!("GPU vendor: {:?}", vendor); if vendor != Some(GpuVendor::NvidiaGn22) { - debug!("No compatible retimer present"); + info!("No compatible retimer present (vendor mismatch)"); return Ok(None); }; + info!("NVIDIA GPU detected, checking retimer..."); // I2C Port on the EC let i2c_port = 5; @@ -22,8 +24,27 @@ pub fn get_version(ec: &CrosEc) -> EcResult>> { let i2c_addr = 0x10; // Check safe mode + info!( + "Reading retimer at I2C port {} addr 0x{:02X}", + i2c_port, + i2c_addr >> 1 + ); let i2c_response = i2c_read(ec, i2c_port, i2c_addr >> 1, 0x00, 0x01)?; - if i2c_response.data[0] == 0 { + info!( + "I2C response: status=0x{:02X}, data_len={}", + i2c_response.i2c_status, + i2c_response.data.len() + ); + if i2c_response.i2c_status == 0x01 { + // NAK + warn!("Unable to communicate with dGPU Retimer. Try to force it on by plugging a cable into the dGPU"); + } + let Some(&safe_mode) = i2c_response.data.first() else { + info!("Failed to read retimer safe mode status (empty response)"); + return Ok(None); + }; + info!("Retimer safe mode status: {}", safe_mode); + if safe_mode == 0 { // Safe mode not enabled, enable it i2c_write(ec, i2c_port, i2c_addr >> 1, 0x00, &[0x01])?; } @@ -31,10 +52,11 @@ pub fn get_version(ec: &CrosEc) -> EcResult>> { // Wake up from low power mode for _ in 0..3 { let i2c_response = i2c_read(ec, i2c_port, (i2c_addr + 2) >> 1, 0x70, 0x01)?; - if i2c_response.data[0] != 0 { - i2c_write(ec, i2c_port, (i2c_addr + 2) >> 1, 0x70, &[0x00])?; - os_specific::sleep(50_000); + if i2c_response.data.first() == Some(&0) { + continue; } + i2c_write(ec, i2c_port, (i2c_addr + 2) >> 1, 0x70, &[0x00])?; + os_specific::sleep(50_000); } // Read version From f696b64f2391c3b6a543f830b716a8fe12ef2b7f Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Tue, 13 Jan 2026 16:05:55 +0800 Subject: [PATCH 8/8] nvidia: Split details into new --nvidia command PS C:\Users\zoid\framework-system> .\target\debug\framework_tool.exe --nvidia NVIDIA GPU Identification Name: NVIDIA GeForce RTX 5070 Laptop GPU Architecture: Blackwell Board ID: 49664 Firmware VBIOS Version: 98.06.3e.00.0a InfoROM Version: G005.0000.98.01 PCI Bus: C2 Device: 00 Domain: 0000 Device ID: 2D5810DE Subsystem ID: 000EF111 Power Performance State: Eight Current Usage: 7.50 W Default Limit: 100.00 W Min Limit: 5.00 W Max Limit: 100.00 W Total Energy: 26303.86 J Thermal GPU Temperature: 38C Number of Fans: 0 Throttle Reasons None Utilization GPU: 5% Memory: 5% Memory Total: 7.96 GB Used: 0.24 GB Free: 7.38 GB Display Active: Yes Connected: Yes PS C:\Users\zoid\framework-system> .\target\debug\framework_tool.exe --versions Tool Version: 0.4.5 OS Version: 10.0.0.26200 Mainboard Hardware Type: Laptop 16 (AMD Ryzen AI 300 Series) Revision: Unknown UEFI BIOS Version: 03.04 Release Date: 11/06/2025 EC Firmware Build version: tulip-3.0.4-ea09e7a 2025-11-06 10:52:13 lotus@ip-172-26-3-226 Current image: RO PD Controllers PD 0: 0.0.22 (MainFw) PD 1: 0.0.22 (MainFw) PD 2: 0.0.22 (MainFw) Parade Retimers dGPU: 83.0.0.5 Laptop Webcam Module (2nd Gen) Firmware Version: 1.1.1 USB Hub RTL5432 Firmware Version: 0.0.7 Laptop 16 RGB Macropad Firmware Version: 0.3.1 Location: [ ] [ ] [ ] [X] [ ] Laptop 16 Keyboard Module - ANSI Firmware Version: 0.3.1 Location: [X] [ ] [ ] [ ] [ ] Touchpad Firmware Version: v0905 DisplayPort Expansion Card Active Firmware: 101 (3.0.11.065) Inactive Firmware: 008 (3.0.11.008) Operating Mode: MainFw (#2) NVIDIA GPU Name: NVIDIA GeForce RTX 5070 Laptop GPU VBIOS Version: 98.06.3e.00.0a Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/clap_std.rs | 5 + framework_lib/src/commandline/mod.rs | 222 ++++++++++++++++++---- framework_lib/src/commandline/uefi.rs | 4 + 3 files changed, 192 insertions(+), 39 deletions(-) diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 8df96ed9..a2727903 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -288,6 +288,10 @@ struct ClapCli { /// File to dump the gpu EEPROM to #[arg(long)] dump_gpu_descriptor_file: Option, + + /// Show NVIDIA GPU information (Framework 16 only) + #[arg(long)] + nvidia: bool, } /// Parse a list of commandline arguments and return the struct @@ -484,6 +488,7 @@ pub fn parse(args: &[String]) -> Cli { dump_gpu_descriptor_file: args .dump_gpu_descriptor_file .map(|x| x.into_os_string().into_string().unwrap()), + nvidia: args.nvidia, raw_command: vec![], } } diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 43bdb31f..ccc37490 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -224,6 +224,7 @@ pub struct Cli { pub flash_gpu_descriptor: Option<(u8, String)>, pub flash_gpu_descriptor_file: Option, pub dump_gpu_descriptor_file: Option, + pub nvidia: bool, // UEFI only pub allupdate: bool, pub paginate: bool, @@ -309,6 +310,7 @@ pub fn parse(args: &[String]) -> Cli { info: cli.info, // flash_gpu_descriptor // flash_gpu_descriptor_file + nvidia: cli.nvidia, // allupdate paginate: cli.paginate, // raw_command @@ -775,6 +777,7 @@ fn print_versions(ec: &CrosEc) { print_nvidia_details(); } +/// Brief NVIDIA details for --version output #[cfg(feature = "nvidia")] fn print_nvidia_details() { let nvml = match Nvml::init() { @@ -784,7 +787,6 @@ fn print_nvidia_details() { return; } }; - // Get the first `Device` (GPU) in the system let device = match nvml.device_by_index(0) { Ok(device) => device, Err(err) => { @@ -795,55 +797,191 @@ fn print_nvidia_details() { println!("NVIDIA GPU"); println!( - " Name: {}", + " Name: {}", device.name().unwrap_or("Unknown".to_string()) ); - println!(" Architecture: {:?}", device.architecture()); println!( - " VBIOS Version: {}", + " VBIOS Version: {}", device.vbios_version().unwrap_or("Unknown".to_string()) ); +} + +/// Detailed NVIDIA information for --nvidia command +#[cfg(feature = "nvidia")] +fn print_nvidia_info() { + let nvml = match Nvml::init() { + Ok(nvml) => nvml, + Err(err) => { + error!("Nvidia, library init fail: {:?}", err); + return; + } + }; + let device = match nvml.device_by_index(0) { + Ok(device) => device, + Err(err) => { + error!("Nvidia, device not found: {:?}", err); + return; + } + }; + + println!("NVIDIA GPU"); + + // Basic identification + println!("Identification"); println!( - " INFO ROM Ver: {}", - device - .info_rom_image_version() - .unwrap_or("Unknown".to_string()) - ); - println!(" PCI Info: {:X?}", device.pci_info()); - println!(" Performance State: {:?}", device.performance_state()); - println!( - " Pwr Mgmt Limit Def: {:?}mW", - device.power_management_limit_default() - ); - println!( - " Pwr Mgmt Limit: {:?}mW", - device.power_management_limit() - ); - println!( - " Pwr Mgmt Limit Con: {:?}", - device.power_management_limit_constraints() - ); - println!(" Pwr Usage: {:?}mW", device.power_usage()); - println!( - " Total Energy: {:?}mJ", - device.total_energy_consumption() + " Name: {}", + device.name().unwrap_or("Unknown".to_string()) ); - println!(" Serial Number: {:?}", device.serial()); + if let Ok(arch) = device.architecture() { + println!(" Architecture: {:?}", arch); + } + if let Ok(serial) = device.serial() { + println!(" Serial Number: {}", serial); + } + if let Ok(part_number) = device.board_part_number() { + println!(" Part Number: {}", part_number); + } + if let Ok(board_id) = device.board_id() { + println!(" Board ID: {}", board_id); + } + + // Firmware versions + println!("Firmware"); println!( - " Throttle Reason: {:?}", - device.current_throttle_reasons() + " VBIOS Version: {}", + device.vbios_version().unwrap_or("Unknown".to_string()) ); println!( - " Temperature: {:?}C", - device.temperature(TemperatureSensor::Gpu) + " InfoROM Version: {}", + device + .info_rom_image_version() + .unwrap_or("Unknown".to_string()) ); - println!(" Util Rate: {:?}", device.utilization_rates()); - println!(" Memory Info: {:?}", device.memory_info()); - println!(" Part Number: {:?}", device.board_part_number()); - println!(" Board ID: {:?}", device.board_id()); - println!(" Num Fans: {:?}", device.num_fans()); - println!(" Display Active?: {:?}", device.is_display_active()); - println!(" Display Conn?: {:?}", device.is_display_connected()); + + // PCI information + println!("PCI"); + if let Ok(pci) = device.pci_info() { + println!(" Bus: {:02X}", pci.bus); + println!(" Device: {:02X}", pci.device); + println!(" Domain: {:04X}", pci.domain); + println!(" Device ID: {:04X}", pci.pci_device_id); + if let Some(sub_id) = pci.pci_sub_system_id { + println!(" Subsystem ID: {:08X}", sub_id); + } + } + + // Power information + println!("Power"); + if let Ok(state) = device.performance_state() { + println!(" Performance State: {:?}", state); + } + if let Ok(power) = device.power_usage() { + println!(" Current Usage: {:.2} W", power as f64 / 1000.0); + } + if let Ok(limit) = device.power_management_limit() { + println!(" Power Limit: {:.2} W", limit as f64 / 1000.0); + } + if let Ok(default) = device.power_management_limit_default() { + println!(" Default Limit: {:.2} W", default as f64 / 1000.0); + } + if let Ok(constraints) = device.power_management_limit_constraints() { + println!( + " Min Limit: {:.2} W", + constraints.min_limit as f64 / 1000.0 + ); + println!( + " Max Limit: {:.2} W", + constraints.max_limit as f64 / 1000.0 + ); + } + if let Ok(energy) = device.total_energy_consumption() { + println!(" Total Energy: {:.2} J", energy as f64 / 1000.0); + } + + // Thermal information + println!("Thermal"); + if let Ok(temp) = device.temperature(TemperatureSensor::Gpu) { + println!(" GPU Temperature: {}C", temp); + } + if let Ok(num_fans) = device.num_fans() { + println!(" Number of Fans: {}", num_fans); + } + + // Throttling + if let Ok(throttle) = device.current_throttle_reasons() { + println!("Throttle Reasons"); + if throttle.is_empty() { + println!(" None"); + } else { + if throttle.contains(nvml_wrapper::bitmasks::device::ThrottleReasons::GPU_IDLE) { + println!(" GPU Idle"); + } + if throttle.contains( + nvml_wrapper::bitmasks::device::ThrottleReasons::APPLICATIONS_CLOCKS_SETTING, + ) { + println!(" Applications Clocks Setting"); + } + if throttle.contains(nvml_wrapper::bitmasks::device::ThrottleReasons::SW_POWER_CAP) { + println!(" Software Power Cap"); + } + if throttle.contains(nvml_wrapper::bitmasks::device::ThrottleReasons::HW_SLOWDOWN) { + println!(" Hardware Slowdown"); + } + if throttle.contains(nvml_wrapper::bitmasks::device::ThrottleReasons::SYNC_BOOST) { + println!(" Sync Boost"); + } + if throttle + .contains(nvml_wrapper::bitmasks::device::ThrottleReasons::SW_THERMAL_SLOWDOWN) + { + println!(" Software Thermal Slowdown"); + } + if throttle + .contains(nvml_wrapper::bitmasks::device::ThrottleReasons::HW_THERMAL_SLOWDOWN) + { + println!(" Hardware Thermal Slowdown"); + } + if throttle + .contains(nvml_wrapper::bitmasks::device::ThrottleReasons::HW_POWER_BRAKE_SLOWDOWN) + { + println!(" Hardware Power Brake Slowdown"); + } + if throttle + .contains(nvml_wrapper::bitmasks::device::ThrottleReasons::DISPLAY_CLOCK_SETTING) + { + println!(" Display Clock Setting"); + } + } + } + + // Utilization + println!("Utilization"); + if let Ok(util) = device.utilization_rates() { + println!(" GPU: {}%", util.gpu); + println!(" Memory: {}%", util.memory); + } + + // Memory information + println!("Memory"); + if let Ok(mem) = device.memory_info() { + let total_gb = mem.total as f64 / (1024.0 * 1024.0 * 1024.0); + let used_gb = mem.used as f64 / (1024.0 * 1024.0 * 1024.0); + let free_gb = mem.free as f64 / (1024.0 * 1024.0 * 1024.0); + println!(" Total: {:.2} GB", total_gb); + println!(" Used: {:.2} GB", used_gb); + println!(" Free: {:.2} GB", free_gb); + } + + // Display status + println!("Display"); + if let Ok(active) = device.is_display_active() { + println!(" Active: {}", if active { "Yes" } else { "No" }); + } + if let Ok(connected) = device.is_display_connected() { + println!( + " Connected: {}", + if connected { "Yes" } else { "No" } + ); + } } fn print_esrt() { @@ -1197,6 +1335,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!(" Hdr CRC: {:X}", { header.crc32 }); } } + } else if args.nvidia { + #[cfg(feature = "nvidia")] + print_nvidia_info(); + #[cfg(not(feature = "nvidia"))] + error!("Not built with nvidia feature"); } else if let Some(maybe_limit) = args.charge_limit { print_err(handle_charge_limit(&ec, maybe_limit)); } else if let Some((limit, soc)) = args.charge_current_limit { @@ -1629,6 +1772,7 @@ Options: --inputdeck Show status of the input deck --inputdeck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) --expansion-bay Show status of the expansion bay (Framework 16 only) + --nvidia Show NVIDIA GPU information (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') --charge-current-limit [] Get or set battery current charge limit (Percentage number as arg, e.g. '100') --get-gpio Get GPIO value by name or all, if no name provided diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 877b7dac..cb2b4c5d 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 { dump_gpu_descriptor_file: None, allupdate: false, info: false, + nvidia: false, raw_command: vec![], }; @@ -250,6 +251,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--expansion-bay" { cli.expansion_bay = true; found_an_option = true; + } else if arg == "--nvidia" { + cli.nvidia = true; + found_an_option = true; } else if arg == "--charge-limit" { cli.charge_limit = if args.len() > i + 1 { if let Ok(percent) = args[i + 1].parse::() {