Skip to content

Commit 4e9b304

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 5a45784 commit 4e9b304

9 files changed

Lines changed: 250 additions & 34 deletions

File tree

completions/bash/framework_tool

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ _framework_tool() {
2323

2424
case "${cmd}" in
2525
framework_tool)
26-
opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help"
26+
opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --info --meinfo --serialnums --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --nvidia --host-command --generate-completions --help"
2727
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
2828
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
2929
return 0

completions/fish/framework_tool.fish

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ complete -c framework_tool -l power -d 'Show current power status of battery and
8080
complete -c framework_tool -l thermal -d 'Print thermal information (Temperatures and Fan speed)'
8181
complete -c framework_tool -l sensors -d 'Print sensor information (ALS, G-Sensor)'
8282
complete -c framework_tool -l pdports -d 'Show information about USB-C PD ports'
83-
complete -c framework_tool -l info -d 'Show info from SMBIOS (Only on UEFI)'
83+
complete -c framework_tool -l info -d 'Show info from SMBIOS'
84+
complete -c framework_tool -l serialnums -d 'Show info about system serial numbers'
8485
complete -c framework_tool -l pd-info -d 'Show details about the PD controllers'
8586
complete -c framework_tool -l dp-hdmi-info -d 'Show details about connected DP or HDMI Expansion Cards'
8687
complete -c framework_tool -l audio-card-info -d 'Show details about connected Audio Expansion Cards (Needs root privileges)'

completions/zsh/_framework_tool

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ _framework_tool() {
7171
'--thermal[Print thermal information (Temperatures and Fan speed)]' \
7272
'--sensors[Print sensor information (ALS, G-Sensor)]' \
7373
'--pdports[Show information about USB-C PD ports]' \
74-
'--info[Show info from SMBIOS (Only on UEFI)]' \
74+
'--info[Show info from SMBIOS]' \
75+
'--serialnums[Show info about system serial numbers]' \
7576
'--pd-info[Show details about the PD controllers]' \
7677
'--dp-hdmi-info[Show details about connected DP or HDMI Expansion Cards]' \
7778
'--audio-card-info[Show details about connected Audio Expansion Cards (Needs root privileges)]' \

framework_lib/src/commandline/clap_std.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct ClapCli {
7878
#[arg(long)]
7979
pdports: bool,
8080

81-
/// Show info from SMBIOS (Only on UEFI)
81+
/// Show info from SMBIOS
8282
#[arg(long)]
8383
info: bool,
8484

@@ -87,6 +87,10 @@ struct ClapCli {
8787
#[arg(long)]
8888
meinfo: Option<Option<String>>,
8989

90+
/// Show info about system serial numbers
91+
#[arg(long)]
92+
serialnums: bool,
93+
9094
/// Show details about the PD controllers
9195
#[arg(long)]
9296
pd_info: bool,
@@ -552,6 +556,7 @@ pub fn parse(args: &[String]) -> Cli {
552556
dump_gpu_descriptor_file: args
553557
.dump_gpu_descriptor_file
554558
.map(|x| x.into_os_string().into_string().unwrap()),
559+
serialnums: args.serialnums,
555560
nvidia: args.nvidia,
556561
host_command,
557562
}

framework_lib/src/commandline/mod.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ use crate::nvme;
5656
use crate::os_specific;
5757
use crate::parade_retimer;
5858
use crate::power;
59+
use crate::serialnum::Cfg0;
5960
use crate::smbios;
60-
use crate::smbios::ConfigDigit0;
6161
use crate::smbios::{dmidecode_string_val, get_smbios, is_framework};
6262
#[cfg(feature = "hidapi")]
6363
use crate::touchpad::print_touchpad_fw_ver;
@@ -226,6 +226,7 @@ pub struct Cli {
226226
pub flash_gpu_descriptor: Option<(u8, String)>,
227227
pub flash_gpu_descriptor_file: Option<String>,
228228
pub dump_gpu_descriptor_file: Option<String>,
229+
pub serialnums: bool,
229230
pub nvidia: bool,
230231
// UEFI only
231232
pub allupdate: bool,
@@ -1542,6 +1543,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
15421543
} else if let Some(dump_path) = &args.meinfo {
15431544
let verbose = args.verbosity.0 >= log::LevelFilter::Warn;
15441545
me_info(verbose, dump_path.as_deref());
1546+
} else if args.serialnums {
1547+
serialnum_info();
15451548
} else if args.pd_info {
15461549
print_pd_details(&ec);
15471550
} else if let Some(pd) = args.pd_reset {
@@ -1822,7 +1825,8 @@ Options:
18221825
--fansetrpm Set fan RPM (limited by EC fan table max RPM)
18231826
--autofanctrl [<FANID>]Turn on automatic fan speed control (optionally provide fan index)
18241827
--pdports Show information about USB-C PD ports
1825-
--info Show info from SMBIOS (Only on UEFI)
1828+
--info Show info from SMBIOS
1829+
--serialnums Show info about system serial numbers
18261830
--pd-info Show details about the PD controllers
18271831
--privacy Show privacy switch statuses (camera and microphone)
18281832
--pd-bin <PD_BIN> Parse versions from PD firmware binary file
@@ -2091,8 +2095,7 @@ fn smbios_info() {
20912095
// Assumes it's ASCII, which is guaranteed by SMBIOS
20922096
let config_digit0 = &version[0..1];
20932097
let config_digit0 = u8::from_str_radix(config_digit0, 16);
2094-
if let Ok(version_config) =
2095-
config_digit0.map(<ConfigDigit0 as FromPrimitive>::from_u8)
2098+
if let Ok(version_config) = config_digit0.map(<Cfg0 as FromPrimitive>::from_u8)
20962099
{
20972100
println!(" Version: {:?} ({})", version_config, version);
20982101
} else {
@@ -2130,8 +2133,7 @@ fn smbios_info() {
21302133
// Assumes it's ASCII, which is guaranteed by SMBIOS
21312134
let config_digit0 = &version[0..1];
21322135
let config_digit0 = u8::from_str_radix(config_digit0, 16);
2133-
if let Ok(version_config) =
2134-
config_digit0.map(<ConfigDigit0 as FromPrimitive>::from_u8)
2136+
if let Ok(version_config) = config_digit0.map(<Cfg0 as FromPrimitive>::from_u8)
21352137
{
21362138
println!(" Version: {:?} ({})", version_config, version);
21372139
} else {
@@ -2280,6 +2282,19 @@ fn me_info(verbose: bool, dump_path: Option<&str>) {
22802282
}
22812283
}
22822284

2285+
fn serialnum_info() {
2286+
let smbios = get_smbios();
2287+
if smbios.is_none() {
2288+
error!("Failed to find SMBIOS");
2289+
return;
2290+
}
2291+
for undefined_struct in smbios.unwrap().iter() {
2292+
if let DefinedStruct::OemStrings(data) = undefined_struct.defined_struct() {
2293+
smbios::dump_oem_strings(data.oem_strings());
2294+
}
2295+
}
2296+
}
2297+
22832298
fn analyze_ccgx_pd_fw(data: &[u8]) {
22842299
if let Some(versions) = ccgx::binary::read_versions(data, Ccg3) {
22852300
println!("Detected CCG3 firmware");

framework_lib/src/commandline/uefi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ pub fn parse(args: &[String]) -> Cli {
102102
allupdate: false,
103103
info: false,
104104
meinfo: None,
105+
serialnums: false,
105106
nvidia: false,
106107
host_command: None,
107108
};
@@ -236,6 +237,9 @@ pub fn parse(args: &[String]) -> Cli {
236237
Some(None)
237238
};
238239
found_an_option = true;
240+
} else if arg == "--serialnums" {
241+
cli.serialnums = true;
242+
found_an_option = true;
239243
} else if arg == "--intrusion" {
240244
cli.intrusion = true;
241245
found_an_option = true;

framework_lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub mod fw_uefi;
6161
mod os_specific;
6262
pub mod parade_retimer;
6363
pub mod power;
64+
pub mod serialnum;
6465
pub mod smbios;
6566
mod util;
6667

framework_lib/src/serialnum.rs

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

0 commit comments

Comments
 (0)