From 7dd55b162607004a330a75e31fd240eabd05066f Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 29 Apr 2024 11:34:13 +0800 Subject: [PATCH] Allow decoding of SMBIOS Type 11 serialnumbers They're the serialnumbers of what the system was originally assembled with in the factory. TODO - [ ] Cleanup code - [ ] Make safer with fewer unwraps - [ ] Custom command, not in info - [ ] Make sure date is decoded correctly Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/clap_std.rs | 7 +- framework_lib/src/commandline/mod.rs | 19 +++- framework_lib/src/commandline/uefi.rs | 4 + framework_lib/src/lib.rs | 1 + framework_lib/src/serialnum.rs | 101 +++++++++++++++++++++ framework_lib/src/smbios.rs | 106 ++++++++++++++++++++++ 6 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 framework_lib/src/serialnum.rs diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index c12e9b4..34218fe 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -41,10 +41,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, @@ -255,6 +259,7 @@ pub fn parse(args: &[String]) -> Cli { // UEFI only - every command needs to implement a parameter to enable the pager paginate: false, info: args.info, + serialnums: args.serialnums, raw_command: vec![], } } diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 591014b..26a477a 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -154,6 +154,7 @@ pub struct Cli { pub has_mec: Option, pub help: bool, pub info: bool, + pub serialnums: bool, // UEFI only pub allupdate: bool, pub paginate: bool, @@ -662,6 +663,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 args.dp_hdmi_info { @@ -839,7 +842,8 @@ Options: --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 --privacy Show privacy switch statuses (camera and microphone) --pd-bin Parse versions from PD firmware binary file @@ -1056,6 +1060,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 4d230bf..a9c0165 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 { help: false, allupdate: false, info: false, + serialnums: false, raw_command: vec![], }; @@ -144,6 +145,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 40be376..12a092a 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -31,6 +31,7 @@ pub mod esrt; pub mod guid; 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..39a647d --- /dev/null +++ b/framework_lib/src/serialnum.rs @@ -0,0 +1,101 @@ +use alloc::string::{String, ToString}; +use core::str::FromStr; + +#[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, +} + +#[derive(Debug)] +pub enum Cfg0 { + SKU = 0x00, + Poc1 = 0x01, + Proto1 = 0x02, + Proto2 = 0x03, + Evt1 = 0x04, + Evt2 = 0x05, + Reserved = 0x06, + Dvt1 = 0x07, + Dvt2 = 0x08, + Pvt = 0x09, + Mp = 0x0A, +} + +#[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 = match caps.get(3).unwrap().as_str().chars().next().unwrap() { + '0' => Cfg0::SKU, + '1' => Cfg0::Poc1, + '2' => Cfg0::Proto1, + '3' => Cfg0::Proto2, + '4' => Cfg0::Evt1, + '5' => Cfg0::Evt2, + '6' => Cfg0::Reserved, + '7' => Cfg0::Dvt1, + '8' => Cfg0::Dvt2, + '9' => Cfg0::Pvt, + 'A' => Cfg0::Mp, + _ => return Err("Invalid CFG0".to_string()), + }; + 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 0981dfb..79214dc 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -1,10 +1,12 @@ //! 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::FrameworkSerial; use crate::util::{Config, Platform}; use num_derive::FromPrimitive; use smbioslib::*; @@ -303,3 +305,107 @@ 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, +} + +pub fn dump_oem_strings(strings: &SMBiosStringSet) { + for (i, s) in strings.into_iter().enumerate() { + let idx = i + 1; + let sn = 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), + } + } +}