Skip to content

Commit

Permalink
Allow decoding of SMBIOS Type 11 serialnumbers
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
JohnAZoidberg committed Oct 21, 2024
1 parent 8ff67b6 commit 7dd55b1
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 2 deletions.
7 changes: 6 additions & 1 deletion framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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![],
}
}
19 changes: 18 additions & 1 deletion framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub struct Cli {
pub has_mec: Option<bool>,
pub help: bool,
pub info: bool,
pub serialnums: bool,
// UEFI only
pub allupdate: bool,
pub paginate: bool,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 <PD_BIN> Parse versions from PD firmware binary file
Expand Down Expand Up @@ -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");
Expand Down
4 changes: 4 additions & 0 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub fn parse(args: &[String]) -> Cli {
help: false,
allupdate: false,
info: false,
serialnums: false,
raw_command: vec![],
};

Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions framework_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
101 changes: 101 additions & 0 deletions framework_lib/src/serialnum.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
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::<u16>(caps.get(5).unwrap().as_str()).unwrap();
let year = 2020 + year;
let week = str::parse::<u8>(caps.get(6).unwrap().as_str()).unwrap();
// TODO: Decode into date
let day = match str::parse::<u8>(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(),
})
}
}
106 changes: 106 additions & 0 deletions framework_lib/src/smbios.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -303,3 +305,107 @@ fn kenv_get(name: &str) -> nix::Result<String> {
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),
}
}
}

0 comments on commit 7dd55b1

Please sign in to comment.