diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 52eddbe..bfc7ab5 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -75,10 +75,14 @@ struct ClapCli { #[arg(long)] pdports: bool, - /// Show info from SMBIOS (Only on UEFI) + /// Show info from SMBIOS #[arg(long)] info: bool, + /// Show info about system serial numbers + #[arg(long)] + serialnums: bool, + /// Show details about the PD controllers #[arg(long)] pd_info: bool, @@ -457,6 +461,7 @@ pub fn parse(args: &[String]) -> Cli { paginate: false, info: args.info, flash_gpu_descriptor, + serialnums: args.serialnums, raw_command: vec![], } } diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index ca92266..feccb8f 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -50,8 +50,8 @@ use crate::esrt; #[cfg(feature = "rusb")] use crate::inputmodule::check_inputmodule_version; 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; @@ -201,6 +201,7 @@ pub struct Cli { pub help: bool, pub info: bool, pub flash_gpu_descriptor: Option<(u8, String)>, + pub serialnums: bool, // UEFI only pub allupdate: bool, pub paginate: bool, @@ -1012,6 +1013,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { power::get_and_print_pd_info(&ec); } else if args.info { smbios_info(); + } else if args.serialnums { + serialnum_info(); } else if args.pd_info { print_pd_details(&ec); } else if let Some(pd) = args.pd_reset { @@ -1249,7 +1252,8 @@ Options: --fansetrpm Set fan RPM (limited by EC fan table max RPM) --autofanctrl Turn on automatic fan speed control --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 @@ -1426,8 +1430,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 { @@ -1465,8 +1468,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 { @@ -1488,6 +1490,19 @@ fn smbios_info() { } } +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 d21938a..b519d91 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -118,6 +118,7 @@ pub fn parse(args: &[String]) -> Cli { flash_gpu_descriptor: None, allupdate: false, info: false, + serialnums: false, raw_command: vec![], }; @@ -232,6 +233,9 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "--info" { cli.info = true; 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 93d91e2..a04931d 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -39,6 +39,7 @@ pub mod ec_binary; pub mod esrt; mod os_specific; pub mod power; +pub mod serialnum; pub mod smbios; #[cfg(feature = "uefi")] pub mod uefi; diff --git a/framework_lib/src/serialnum.rs b/framework_lib/src/serialnum.rs new file mode 100644 index 0000000..74a8945 --- /dev/null +++ b/framework_lib/src/serialnum.rs @@ -0,0 +1,104 @@ +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 144ad21..2f3fccf 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 { error!(" Invalid BaseBoard Version: {}'", version); @@ -351,3 +332,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), + } + } +}