Skip to content

Commit 3230ff3

Browse files
committed
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 - [ ] Support Framework 12 - [x] Support Framework 13 - [ ] Support Framework 16 Signed-off-by: Daniel Schaefer <dhs@frame.work>
1 parent 19da5ae commit 3230ff3

File tree

6 files changed

+236
-2
lines changed

6 files changed

+236
-2
lines changed

framework_lib/src/commandline/clap_std.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,14 @@ struct ClapCli {
7575
#[arg(long)]
7676
pdports: bool,
7777

78-
/// Show info from SMBIOS (Only on UEFI)
78+
/// Show info from SMBIOS
7979
#[arg(long)]
8080
info: bool,
8181

82+
/// Show info about system serial numbers
83+
#[arg(long)]
84+
serialnums: bool,
85+
8286
/// Show details about the PD controllers
8387
#[arg(long)]
8488
pd_info: bool,
@@ -471,6 +475,7 @@ pub fn parse(args: &[String]) -> Cli {
471475
dump_gpu_descriptor_file: args
472476
.dump_gpu_descriptor_file
473477
.map(|x| x.into_os_string().into_string().unwrap()),
478+
serialnums: args.serialnums,
474479
raw_command: vec![],
475480
}
476481
}

framework_lib/src/commandline/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ pub struct Cli {
218218
pub flash_gpu_descriptor: Option<(u8, String)>,
219219
pub flash_gpu_descriptor_file: Option<String>,
220220
pub dump_gpu_descriptor_file: Option<String>,
221+
pub serialnums: bool,
221222
// UEFI only
222223
pub allupdate: bool,
223224
pub paginate: bool,
@@ -1190,6 +1191,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
11901191
power::get_and_print_pd_info(&ec);
11911192
} else if args.info {
11921193
smbios_info();
1194+
} else if args.serialnums {
1195+
serialnum_info();
11931196
} else if args.pd_info {
11941197
print_pd_details(&ec);
11951198
} else if let Some(pd) = args.pd_reset {
@@ -1459,7 +1462,8 @@ Options:
14591462
--fansetrpm Set fan RPM (limited by EC fan table max RPM)
14601463
--autofanctrl Turn on automatic fan speed control
14611464
--pdports Show information about USB-C PD ports
1462-
--info Show info from SMBIOS (Only on UEFI)
1465+
--info Show info from SMBIOS
1466+
--serialnums Show info about system serial numbers
14631467
--pd-info Show details about the PD controllers
14641468
--privacy Show privacy switch statuses (camera and microphone)
14651469
--pd-bin <PD_BIN> Parse versions from PD firmware binary file
@@ -1700,6 +1704,19 @@ fn smbios_info() {
17001704
}
17011705
}
17021706

1707+
fn serialnum_info() {
1708+
let smbios = get_smbios();
1709+
if smbios.is_none() {
1710+
error!("Failed to find SMBIOS");
1711+
return;
1712+
}
1713+
for undefined_struct in smbios.unwrap().iter() {
1714+
if let DefinedStruct::OemStrings(data) = undefined_struct.defined_struct() {
1715+
smbios::dump_oem_strings(data.oem_strings());
1716+
}
1717+
}
1718+
}
1719+
17031720
fn analyze_ccgx_pd_fw(data: &[u8]) {
17041721
if let Some(versions) = ccgx::binary::read_versions(data, Ccg3) {
17051722
println!("Detected CCG3 firmware");

framework_lib/src/commandline/uefi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub fn parse(args: &[String]) -> Cli {
9292
dump_gpu_descriptor_file: None,
9393
allupdate: false,
9494
info: false,
95+
serialnums: false,
9596
raw_command: vec![],
9697
};
9798

@@ -206,6 +207,9 @@ pub fn parse(args: &[String]) -> Cli {
206207
} else if arg == "--info" {
207208
cli.info = true;
208209
found_an_option = true;
210+
} else if arg == "--serialnums" {
211+
cli.serialnums = true;
212+
found_an_option = true;
209213
} else if arg == "--intrusion" {
210214
cli.intrusion = true;
211215
found_an_option = true;

framework_lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod esrt;
4545
mod os_specific;
4646
pub mod parade_retimer;
4747
pub mod power;
48+
pub mod serialnum;
4849
pub mod smbios;
4950
#[cfg(feature = "uefi")]
5051
pub mod uefi;

framework_lib/src/serialnum.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use alloc::string::{String, ToString};
2+
use core::str::FromStr;
3+
4+
#[derive(Debug)]
5+
pub struct FrameworkSerial {
6+
// brand: Always FR for Framework
7+
// format: Always A
8+
/// Three letter string
9+
pub product: String,
10+
/// Two letter string
11+
pub oem: String,
12+
/// Development state
13+
pub cfg0: Cfg0,
14+
/// Defines config of that specific product
15+
pub cfg1: char,
16+
pub year: u16,
17+
pub week: u8,
18+
pub day: WeekDay,
19+
/// Four letter/digit string
20+
pub part: String,
21+
}
22+
23+
#[derive(Debug)]
24+
pub enum Cfg0 {
25+
SKU = 0x00,
26+
Poc1 = 0x01,
27+
Proto1 = 0x02,
28+
Proto2 = 0x03,
29+
Evt1 = 0x04,
30+
Evt2 = 0x05,
31+
Reserved = 0x06,
32+
Dvt1 = 0x07,
33+
Dvt2 = 0x08,
34+
Pvt = 0x09,
35+
Mp = 0x0A,
36+
}
37+
38+
#[derive(Debug)]
39+
pub enum WeekDay {
40+
Monday = 1,
41+
Tuesday,
42+
Wednesday,
43+
Thursday,
44+
Friday,
45+
Saturday,
46+
Sunday,
47+
}
48+
49+
impl FromStr for FrameworkSerial {
50+
type Err = String;
51+
52+
// TODO: !!! PROPER ERROR HANDLING !!!
53+
fn from_str(s: &str) -> Result<Self, Self::Err> {
54+
let pattern =
55+
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})";
56+
let re = regex::Regex::new(pattern).unwrap();
57+
58+
let caps = re.captures(s).ok_or("Invalid Serial".to_string())?;
59+
60+
let cfg0 = match caps.get(3).unwrap().as_str().chars().next().unwrap() {
61+
'0' => Cfg0::SKU,
62+
'1' => Cfg0::Poc1,
63+
'2' => Cfg0::Proto1,
64+
'3' => Cfg0::Proto2,
65+
'4' => Cfg0::Evt1,
66+
'5' => Cfg0::Evt2,
67+
'6' => Cfg0::Reserved,
68+
'7' => Cfg0::Dvt1,
69+
'8' => Cfg0::Dvt2,
70+
'9' => Cfg0::Pvt,
71+
'A' => Cfg0::Mp,
72+
_ => return Err("Invalid CFG0".to_string()),
73+
};
74+
let cfg1 = caps.get(4).unwrap().as_str().chars().next().unwrap();
75+
let year = str::parse::<u16>(caps.get(5).unwrap().as_str()).unwrap();
76+
let year = 2020 + year;
77+
let week = str::parse::<u8>(caps.get(6).unwrap().as_str()).unwrap();
78+
// TODO: Decode into date
79+
let day = match str::parse::<u8>(caps.get(7).unwrap().as_str()).unwrap() {
80+
1 => WeekDay::Monday,
81+
2 => WeekDay::Tuesday,
82+
3 => WeekDay::Wednesday,
83+
4 => WeekDay::Thursday,
84+
5 => WeekDay::Friday,
85+
6 => WeekDay::Saturday,
86+
7 => WeekDay::Sunday,
87+
_ => return Err("Invalid Day".to_string()),
88+
};
89+
90+
Ok(FrameworkSerial {
91+
product: caps.get(1).unwrap().as_str().to_string(),
92+
oem: caps.get(2).unwrap().as_str().to_string(),
93+
cfg0,
94+
cfg1,
95+
year,
96+
week,
97+
day,
98+
part: caps.get(2).unwrap().as_str().to_string(),
99+
})
100+
}
101+
}

framework_lib/src/smbios.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! Retrieve SMBIOS tables and extract information from them
22
3+
use core::str::FromStr;
34
use std::prelude::v1::*;
45

56
#[cfg(all(not(feature = "uefi"), not(target_os = "freebsd")))]
67
use std::io::ErrorKind;
78

9+
use crate::serialnum::FrameworkSerial;
810
use crate::util::Config;
911
pub use crate::util::{Platform, PlatformFamily};
1012
use num_derive::FromPrimitive;
@@ -352,3 +354,107 @@ fn kenv_get(name: &str) -> nix::Result<String> {
352354
Ok(value)
353355
}
354356
}
357+
358+
#[derive(Debug)]
359+
enum SmbiosSerialNumber {
360+
Mainboard = 1,
361+
Laptop,
362+
Camera,
363+
Display,
364+
Battery,
365+
Touchpad,
366+
Keyboard,
367+
Fingerprint,
368+
AudioDaughtercard,
369+
ACover,
370+
BCover,
371+
CCover,
372+
AntennaMain,
373+
AntennaAux,
374+
TouchpadFpc,
375+
FingerprintFfc,
376+
EdpCable,
377+
LcdCable,
378+
ThermalAssembly,
379+
WifiModule,
380+
Speaker,
381+
RamSlot1,
382+
RamSlot2,
383+
Ssd,
384+
AudioFfc,
385+
}
386+
387+
pub fn dump_oem_strings(strings: &SMBiosStringSet) {
388+
for (i, s) in strings.into_iter().enumerate() {
389+
let idx = i + 1;
390+
let sn = match idx {
391+
1 => Some(SmbiosSerialNumber::Mainboard),
392+
2 => Some(SmbiosSerialNumber::Laptop),
393+
3 => Some(SmbiosSerialNumber::Camera),
394+
4 => Some(SmbiosSerialNumber::Display),
395+
5 => Some(SmbiosSerialNumber::Battery),
396+
6 => Some(SmbiosSerialNumber::Touchpad),
397+
7 => Some(SmbiosSerialNumber::Keyboard),
398+
8 => Some(SmbiosSerialNumber::Fingerprint),
399+
10 => Some(SmbiosSerialNumber::AudioDaughtercard),
400+
11 => Some(SmbiosSerialNumber::ACover),
401+
12 => Some(SmbiosSerialNumber::BCover),
402+
13 => Some(SmbiosSerialNumber::CCover),
403+
14 => Some(SmbiosSerialNumber::AntennaMain),
404+
15 => Some(SmbiosSerialNumber::AntennaAux),
405+
16 => Some(SmbiosSerialNumber::TouchpadFpc),
406+
17 => Some(SmbiosSerialNumber::FingerprintFfc),
407+
18 => Some(SmbiosSerialNumber::EdpCable),
408+
19 => Some(SmbiosSerialNumber::LcdCable),
409+
20 => Some(SmbiosSerialNumber::ThermalAssembly),
410+
21 => Some(SmbiosSerialNumber::WifiModule),
411+
22 => Some(SmbiosSerialNumber::Speaker),
412+
23 => Some(SmbiosSerialNumber::RamSlot1),
413+
24 => Some(SmbiosSerialNumber::RamSlot2),
414+
25 => Some(SmbiosSerialNumber::Ssd),
415+
26 => Some(SmbiosSerialNumber::AudioFfc),
416+
_ => None,
417+
};
418+
match sn {
419+
Some(SmbiosSerialNumber::RamSlot1)
420+
| Some(SmbiosSerialNumber::RamSlot2)
421+
| Some(SmbiosSerialNumber::Ssd)
422+
| Some(SmbiosSerialNumber::WifiModule) => {
423+
println!("{} {:<20} (Unused)", s, format!("{:?}", sn.unwrap()))
424+
}
425+
Some(SmbiosSerialNumber::Fingerprint) | Some(SmbiosSerialNumber::CCover) => {
426+
println!("{}", s);
427+
println!(" {:<20} (Only Pre-Built)", format!("{:?}", sn.unwrap()));
428+
if let Ok(serial) = FrameworkSerial::from_str(&format!("{:?}", s)) {
429+
println!(
430+
" {} (Config {}) by {}, {:>4} Phase ({:?}, Week {}, {})",
431+
serial.product,
432+
serial.cfg1,
433+
serial.oem,
434+
format!("{:?}", serial.cfg0),
435+
serial.day,
436+
serial.week,
437+
serial.year,
438+
);
439+
}
440+
}
441+
Some(sn) => {
442+
println!("{}", s);
443+
println!(" {:?}", sn);
444+
if let Ok(serial) = FrameworkSerial::from_str(&format!("{:?}", s)) {
445+
println!(
446+
" {} (Config {}) by {}, {:>4} Phase ({:?}, Week {}, {})",
447+
serial.product,
448+
serial.cfg1,
449+
serial.oem,
450+
format!("{:?}", serial.cfg0),
451+
serial.day,
452+
serial.week,
453+
serial.year,
454+
);
455+
}
456+
}
457+
_ => println!("{} Unknown/Reserved", s),
458+
}
459+
}
460+
}

0 commit comments

Comments
 (0)