diff --git a/completions/bash/framework_tool b/completions/bash/framework_tool index ade3813d..d9811aed 100755 --- a/completions/bash/framework_tool +++ b/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 --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --info --meinfo --serialnums --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/completions/fish/framework_tool.fish b/completions/fish/framework_tool.fish index 00a70a97..0a8ebfaf 100644 --- a/completions/fish/framework_tool.fish +++ b/completions/fish/framework_tool.fish @@ -80,7 +80,8 @@ complete -c framework_tool -l power -d 'Show current power status of battery and complete -c framework_tool -l thermal -d 'Print thermal information (Temperatures and Fan speed)' complete -c framework_tool -l sensors -d 'Print sensor information (ALS, G-Sensor)' complete -c framework_tool -l pdports -d 'Show information about USB-C PD ports' -complete -c framework_tool -l info -d 'Show info from SMBIOS (Only on UEFI)' +complete -c framework_tool -l info -d 'Show info from SMBIOS' +complete -c framework_tool -l serialnums -d 'Show info about system serial numbers' complete -c framework_tool -l pd-info -d 'Show details about the PD controllers' complete -c framework_tool -l dp-hdmi-info -d 'Show details about connected DP or HDMI Expansion Cards' complete -c framework_tool -l audio-card-info -d 'Show details about connected Audio Expansion Cards (Needs root privileges)' diff --git a/completions/zsh/_framework_tool b/completions/zsh/_framework_tool index 3b80dcdc..ce4cf19d 100644 --- a/completions/zsh/_framework_tool +++ b/completions/zsh/_framework_tool @@ -71,7 +71,8 @@ _framework_tool() { '--thermal[Print thermal information (Temperatures and Fan speed)]' \ '--sensors[Print sensor information (ALS, G-Sensor)]' \ '--pdports[Show information about USB-C PD ports]' \ -'--info[Show info from SMBIOS (Only on UEFI)]' \ +'--info[Show info from SMBIOS]' \ +'--serialnums[Show info about system serial numbers]' \ '--pd-info[Show details about the PD controllers]' \ '--dp-hdmi-info[Show details about connected DP or HDMI Expansion Cards]' \ '--audio-card-info[Show details about connected Audio Expansion Cards (Needs root privileges)]' \ diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index ba1cc43f..25cf556c 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -78,7 +78,7 @@ struct ClapCli { #[arg(long)] pdports: bool, - /// Show info from SMBIOS (Only on UEFI) + /// Show info from SMBIOS #[arg(long)] info: bool, @@ -87,6 +87,10 @@ struct ClapCli { #[arg(long)] meinfo: Option>, + /// Show info about system serial numbers + #[arg(long)] + serialnums: bool, + /// Show details about the PD controllers #[arg(long)] pd_info: bool, @@ -552,6 +556,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()), + serialnums: args.serialnums, nvidia: args.nvidia, host_command, } diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 477f13a7..a59ca629 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -56,8 +56,8 @@ use crate::nvme; use crate::os_specific; use crate::parade_retimer; use crate::power; +use crate::serialnum::Cfg0; use crate::smbios; -use crate::smbios::ConfigDigit0; use crate::smbios::{dmidecode_string_val, get_smbios, is_framework}; #[cfg(feature = "hidapi")] use crate::touchpad::print_touchpad_fw_ver; @@ -226,6 +226,7 @@ pub struct Cli { pub flash_gpu_descriptor: Option<(u8, String)>, pub flash_gpu_descriptor_file: Option, pub dump_gpu_descriptor_file: Option, + pub serialnums: bool, pub nvidia: bool, // UEFI only pub allupdate: bool, @@ -1542,6 +1543,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else if let Some(dump_path) = &args.meinfo { let verbose = args.verbosity.0 >= log::LevelFilter::Warn; me_info(verbose, dump_path.as_deref()); + } else if args.serialnums { + serialnum_info(); } else if args.pd_info { print_pd_details(&ec); } else if let Some(pd) = args.pd_reset { @@ -1822,7 +1825,8 @@ Options: --fansetrpm Set fan RPM (limited by EC fan table max RPM) --autofanctrl []Turn on automatic fan speed control (optionally provide fan index) --pdports Show information about USB-C PD ports - --info Show info from SMBIOS (Only on UEFI) + --info Show info from SMBIOS + --serialnums Show info about system serial numbers --pd-info Show details about the PD controllers --privacy Show privacy switch statuses (camera and microphone) --pd-bin Parse versions from PD firmware binary file @@ -2091,8 +2095,7 @@ fn smbios_info() { // Assumes it's ASCII, which is guaranteed by SMBIOS let config_digit0 = &version[0..1]; let config_digit0 = u8::from_str_radix(config_digit0, 16); - if let Ok(version_config) = - config_digit0.map(::from_u8) + if let Ok(version_config) = config_digit0.map(::from_u8) { println!(" Version: {:?} ({})", version_config, version); } else { @@ -2130,8 +2133,7 @@ fn smbios_info() { // Assumes it's ASCII, which is guaranteed by SMBIOS let config_digit0 = &version[0..1]; let config_digit0 = u8::from_str_radix(config_digit0, 16); - if let Ok(version_config) = - config_digit0.map(::from_u8) + if let Ok(version_config) = config_digit0.map(::from_u8) { println!(" Version: {:?} ({})", version_config, version); } else { @@ -2280,6 +2282,19 @@ fn me_info(verbose: bool, dump_path: Option<&str>) { } } +fn serialnum_info() { + let smbios = get_smbios(); + if smbios.is_none() { + error!("Failed to find SMBIOS"); + return; + } + for undefined_struct in smbios.unwrap().iter() { + if let DefinedStruct::OemStrings(data) = undefined_struct.defined_struct() { + smbios::dump_oem_strings(data.oem_strings()); + } + } +} + fn analyze_ccgx_pd_fw(data: &[u8]) { if let Some(versions) = ccgx::binary::read_versions(data, Ccg3) { println!("Detected CCG3 firmware"); diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index d08e6ddd..1ec1904f 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -102,6 +102,7 @@ pub fn parse(args: &[String]) -> Cli { allupdate: false, info: false, meinfo: None, + serialnums: false, nvidia: false, host_command: None, }; @@ -236,6 +237,9 @@ pub fn parse(args: &[String]) -> Cli { Some(None) }; found_an_option = true; + } else if arg == "--serialnums" { + cli.serialnums = true; + found_an_option = true; } else if arg == "--intrusion" { cli.intrusion = true; found_an_option = true; diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index 5da49205..5f8e833d 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -61,6 +61,7 @@ pub mod fw_uefi; mod os_specific; pub mod parade_retimer; pub mod power; +pub mod serialnum; pub mod smbios; mod util; diff --git a/framework_lib/src/serialnum.rs b/framework_lib/src/serialnum.rs new file mode 100644 index 00000000..ebc172f3 --- /dev/null +++ b/framework_lib/src/serialnum.rs @@ -0,0 +1,105 @@ +use alloc::format; +use alloc::string::{String, ToString}; +use core::str::FromStr; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +#[derive(Debug)] +pub struct FrameworkSerial { + // brand: Always FR for Framework + // format: Always A + /// Three letter string + pub product: String, + /// Two letter string + pub oem: String, + /// Development state + pub cfg0: Cfg0, + /// Defines config of that specific product + pub cfg1: char, + pub year: u16, + pub week: u8, + pub day: WeekDay, + /// Four letter/digit string + pub part: String, +} + +#[repr(u8)] +#[derive(Debug, PartialEq, FromPrimitive, Clone, Copy)] +pub enum Cfg0 { + SKU = 0x00, + Poc1 = 0x01, + Proto1 = 0x02, + Proto2 = 0x03, + Evt1 = 0x04, + Evt2 = 0x05, + Reserved = 0x06, + Dvt1 = 0x07, + Dvt2 = 0x08, + Pvt = 0x09, + MassProduction = 0x0A, + MassProductionB = 0x0B, + MassProductionC = 0x0C, + MassProductionD = 0x0D, + MassProductionE = 0x0E, + MassProductionF = 0x0F, +} + +#[derive(Debug)] +pub enum WeekDay { + Monday = 1, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, +} + +impl FromStr for FrameworkSerial { + type Err = String; + + // TODO: !!! PROPER ERROR HANDLING !!! + fn from_str(s: &str) -> Result { + let pattern = + r"FRA([A-Z]{3})([A-Z]{2})([0-9A-F])([0-9A-F])([0-9A-Z])([0-9]{2})([0-7])([0-9A-Z]{4})"; + let re = regex::Regex::new(pattern).unwrap(); + + let caps = re.captures(s).ok_or("Invalid Serial".to_string())?; + + let cfg0 = caps.get(3).unwrap().as_str().chars().next(); + let cfg0 = cfg0.and_then(|x| str::parse::(&x.to_string()).ok()); + let cfg0 = cfg0.and_then(::from_u8); + let cfg0 = if let Some(cfg0) = cfg0 { + cfg0 + } else { + error!("Invalid CFG0 '{:?}'", cfg0); + return Err(format!("Invalid CFG0 '{:?}'", cfg0)); + }; + let cfg1 = caps.get(4).unwrap().as_str().chars().next().unwrap(); + let year = str::parse::(caps.get(5).unwrap().as_str()).unwrap(); + let year = 2020 + year; + let week = str::parse::(caps.get(6).unwrap().as_str()).unwrap(); + // TODO: Decode into date + let day = match str::parse::(caps.get(7).unwrap().as_str()).unwrap() { + 1 => WeekDay::Monday, + 2 => WeekDay::Tuesday, + 3 => WeekDay::Wednesday, + 4 => WeekDay::Thursday, + 5 => WeekDay::Friday, + 6 => WeekDay::Saturday, + 7 => WeekDay::Sunday, + _ => return Err("Invalid Day".to_string()), + }; + + Ok(FrameworkSerial { + product: caps.get(1).unwrap().as_str().to_string(), + oem: caps.get(2).unwrap().as_str().to_string(), + cfg0, + cfg1, + year, + week, + day, + part: caps.get(2).unwrap().as_str().to_string(), + }) + } +} diff --git a/framework_lib/src/smbios.rs b/framework_lib/src/smbios.rs index 76ce335e..af8b2dd3 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -1,13 +1,15 @@ //! Retrieve SMBIOS tables and extract information from them +use core::str::FromStr; use std::prelude::v1::*; #[cfg(all(not(feature = "uefi"), not(target_os = "freebsd")))] use std::io::ErrorKind; +use crate::serialnum::Cfg0; +use crate::serialnum::FrameworkSerial; use crate::util::Config; pub use crate::util::{Platform, PlatformFamily}; -use num_derive::FromPrimitive; use num_traits::FromPrimitive; use smbioslib::*; #[cfg(feature = "uefi")] @@ -24,25 +26,6 @@ static CACHED_PLATFORM: Mutex>> = Mutex::new(None); // TODO: Should cache SMBIOS and values gotten from it // SMBIOS is fixed after boot. Oh, so maybe not cache when we're running in UEFI -#[repr(u8)] -#[derive(Debug, PartialEq, FromPrimitive, Clone, Copy)] -pub enum ConfigDigit0 { - Poc1 = 0x01, - Proto1 = 0x02, - Proto2 = 0x03, - Evt1 = 0x04, - Evt2 = 0x05, - Dvt1 = 0x07, - Dvt2 = 0x08, - Pvt = 0x09, - MassProduction = 0x0A, - MassProductionB = 0x0B, - MassProductionC = 0x0C, - MassProductionD = 0x0D, - MassProductionE = 0x0E, - MassProductionF = 0x0F, -} - /// Check whether the manufacturer in the SMBIOS says Framework pub fn is_framework() -> bool { if matches!( @@ -239,7 +222,7 @@ pub fn get_product_name() -> Option { }) } -pub fn get_baseboard_version() -> Option { +pub fn get_baseboard_version() -> Option { // TODO: On FreeBSD we can short-circuit and avoid parsing SMBIOS // #[cfg(target_os = "freebsd")] // if let Ok(product) = kenv_get("smbios.system.product") { @@ -258,9 +241,7 @@ pub fn get_baseboard_version() -> Option { // Assumes it's ASCII, which is guaranteed by SMBIOS let config_digit0 = &version[0..1]; let config_digit0 = u8::from_str_radix(config_digit0, 16); - if let Ok(version_config) = - config_digit0.map(::from_u8) - { + if let Ok(version_config) = config_digit0.map(::from_u8) { return version_config; } else { debug!(" Invalid BaseBoard Version: {}'", version); @@ -352,3 +333,135 @@ fn kenv_get(name: &str) -> nix::Result { Ok(value) } } + +#[derive(Debug)] +enum SmbiosSerialNumber { + Mainboard = 1, + Laptop, + Camera, + Display, + Battery, + Touchpad, + Keyboard, + Fingerprint, + AudioDaughtercard, + ACover, + BCover, + CCover, + AntennaMain, + AntennaAux, + TouchpadFpc, + FingerprintFfc, + EdpCable, + LcdCable, + ThermalAssembly, + WifiModule, + Speaker, + RamSlot1, + RamSlot2, + Ssd, + AudioFfc, + + Heatsink, + Fan, + Chassis, + LeftPanel, + RightPanel, + FrontPanel, + PowerSupply, +} + +pub fn dump_oem_strings(strings: &SMBiosStringSet) { + for (i, s) in strings.into_iter().enumerate() { + let idx = i + 1; + let sn = if get_family() == Some(PlatformFamily::FrameworkDesktop) { + match idx { + 1 => Some(SmbiosSerialNumber::Mainboard), + 2 => Some(SmbiosSerialNumber::Heatsink), + 3 => Some(SmbiosSerialNumber::Fan), + 4 => Some(SmbiosSerialNumber::Chassis), + 5 => Some(SmbiosSerialNumber::AntennaMain), + 6 => Some(SmbiosSerialNumber::WifiModule), + 7 => Some(SmbiosSerialNumber::LeftPanel), + 8 => Some(SmbiosSerialNumber::RightPanel), + 9 => Some(SmbiosSerialNumber::FrontPanel), + 10 => Some(SmbiosSerialNumber::PowerSupply), + 11 => Some(SmbiosSerialNumber::RamSlot1), + 12 => Some(SmbiosSerialNumber::RamSlot2), + 13 => Some(SmbiosSerialNumber::Ssd), + 14 => Some(SmbiosSerialNumber::AudioFfc), + _ => None, + } + } else { + match idx { + 1 => Some(SmbiosSerialNumber::Mainboard), + 2 => Some(SmbiosSerialNumber::Laptop), + 3 => Some(SmbiosSerialNumber::Camera), + 4 => Some(SmbiosSerialNumber::Display), + 5 => Some(SmbiosSerialNumber::Battery), + 6 => Some(SmbiosSerialNumber::Touchpad), + 7 => Some(SmbiosSerialNumber::Keyboard), + 8 => Some(SmbiosSerialNumber::Fingerprint), + 10 => Some(SmbiosSerialNumber::AudioDaughtercard), + 11 => Some(SmbiosSerialNumber::ACover), + 12 => Some(SmbiosSerialNumber::BCover), + 13 => Some(SmbiosSerialNumber::CCover), + 14 => Some(SmbiosSerialNumber::AntennaMain), + 15 => Some(SmbiosSerialNumber::AntennaAux), + 16 => Some(SmbiosSerialNumber::TouchpadFpc), + 17 => Some(SmbiosSerialNumber::FingerprintFfc), + 18 => Some(SmbiosSerialNumber::EdpCable), + 19 => Some(SmbiosSerialNumber::LcdCable), + 20 => Some(SmbiosSerialNumber::ThermalAssembly), + 21 => Some(SmbiosSerialNumber::WifiModule), + 22 => Some(SmbiosSerialNumber::Speaker), + 23 => Some(SmbiosSerialNumber::RamSlot1), + 24 => Some(SmbiosSerialNumber::RamSlot2), + 25 => Some(SmbiosSerialNumber::Ssd), + 26 => Some(SmbiosSerialNumber::AudioFfc), + _ => None, + } + }; + match sn { + Some(SmbiosSerialNumber::RamSlot1) + | Some(SmbiosSerialNumber::RamSlot2) + | Some(SmbiosSerialNumber::Ssd) + | Some(SmbiosSerialNumber::WifiModule) => { + println!("{} {:<20} (Unused)", s, format!("{:?}", sn.unwrap())) + } + Some(SmbiosSerialNumber::Fingerprint) | Some(SmbiosSerialNumber::CCover) => { + println!("{}", s); + println!(" {:<20} (Only Pre-Built)", format!("{:?}", sn.unwrap())); + if let Ok(serial) = FrameworkSerial::from_str(&format!("{:?}", s)) { + println!( + " {} (Config {}) by {}, {:>4} Phase ({:?}, Week {}, {})", + serial.product, + serial.cfg1, + serial.oem, + format!("{:?}", serial.cfg0), + serial.day, + serial.week, + serial.year, + ); + } + } + Some(sn) => { + println!("{}", s); + println!(" {:?}", sn); + if let Ok(serial) = FrameworkSerial::from_str(&format!("{:?}", s)) { + println!( + " {} (Config {}) by {}, {:>4} Phase ({:?}, Week {}, {})", + serial.product, + serial.cfg1, + serial.oem, + format!("{:?}", serial.cfg0), + serial.day, + serial.week, + serial.year, + ); + } + } + _ => println!("{} Unknown/Reserved", s), + } + } +}