Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow decoding of SMBIOS Type 11 serialnumbers #55

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
}
}
}