| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
use std::fs; |
| 9 |
|
| 10 |
use everycycle_hal::HostIdentity; |
| 11 |
|
| 12 |
const UNKNOWN: &str = "unknown"; |
| 13 |
|
| 14 |
#[must_use] |
| 15 |
pub fn host_identity() -> HostIdentity { |
| 16 |
HostIdentity { |
| 17 |
hostname: read_hostname(), |
| 18 |
dmi_string: read_dmi_product_name(), |
| 19 |
cpu_model: read_cpu_model(), |
| 20 |
} |
| 21 |
} |
| 22 |
|
| 23 |
fn read_hostname() -> String { |
| 24 |
fs::read_to_string("/etc/hostname") |
| 25 |
.map(|s| s.trim().to_string()) |
| 26 |
.ok() |
| 27 |
.filter(|s| !s.is_empty()) |
| 28 |
.unwrap_or_else(|| UNKNOWN.to_string()) |
| 29 |
} |
| 30 |
|
| 31 |
fn read_dmi_product_name() -> Option<String> { |
| 32 |
fs::read_to_string("/sys/class/dmi/id/product_name") |
| 33 |
.ok() |
| 34 |
.map(|s| s.trim().to_string()) |
| 35 |
.filter(|s| !s.is_empty()) |
| 36 |
} |
| 37 |
|
| 38 |
|
| 39 |
fn read_cpu_model() -> String { |
| 40 |
fs::read_to_string("/proc/cpuinfo") |
| 41 |
.map_or_else(|_| UNKNOWN.to_string(), |c| parse_cpu_model(&c)) |
| 42 |
} |
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
fn parse_cpu_model(cpuinfo: &str) -> String { |
| 56 |
const PROSE_KEYS: &[&str] = &["model name", "Model", "Hardware"]; |
| 57 |
for key in PROSE_KEYS { |
| 58 |
if let Some(value) = field(cpuinfo, key) { |
| 59 |
return value; |
| 60 |
} |
| 61 |
} |
| 62 |
decode_midr(cpuinfo).unwrap_or_else(|| UNKNOWN.to_string()) |
| 63 |
} |
| 64 |
|
| 65 |
|
| 66 |
fn field(cpuinfo: &str, key: &str) -> Option<String> { |
| 67 |
cpuinfo |
| 68 |
.lines() |
| 69 |
.filter_map(|line| { |
| 70 |
let (name, value) = line.split_once(':')?; |
| 71 |
(name.trim() == key).then(|| value.trim().to_string()) |
| 72 |
}) |
| 73 |
.find(|value| !value.is_empty()) |
| 74 |
} |
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
fn implementer_name(implementer: u32) -> Option<&'static str> { |
| 80 |
Some(match implementer { |
| 81 |
0x41 => "ARM", |
| 82 |
0x42 => "Broadcom", |
| 83 |
0x43 => "Cavium", |
| 84 |
0x4e => "NVIDIA", |
| 85 |
0x50 => "Applied Micro", |
| 86 |
0x51 => "Qualcomm", |
| 87 |
0x53 => "Samsung", |
| 88 |
0x61 => "Apple", |
| 89 |
0xc0 => "Ampere", |
| 90 |
_ => return None, |
| 91 |
}) |
| 92 |
} |
| 93 |
|
| 94 |
|
| 95 |
|
| 96 |
|
| 97 |
fn core_name(implementer: u32, part: u32) -> Option<&'static str> { |
| 98 |
if implementer != 0x41 { |
| 99 |
return None; |
| 100 |
} |
| 101 |
Some(match part { |
| 102 |
0xd03 => "Cortex-A53", |
| 103 |
0xd05 => "Cortex-A55", |
| 104 |
0xd07 => "Cortex-A57", |
| 105 |
0xd08 => "Cortex-A72", |
| 106 |
0xd09 => "Cortex-A73", |
| 107 |
0xd0b => "Cortex-A76", |
| 108 |
0xd0c => "Neoverse-N1", |
| 109 |
0xd40 => "Neoverse-V1", |
| 110 |
0xd49 => "Neoverse-N2", |
| 111 |
0xd4f => "Neoverse-V2", |
| 112 |
_ => return None, |
| 113 |
}) |
| 114 |
} |
| 115 |
|
| 116 |
|
| 117 |
|
| 118 |
fn decode_midr(cpuinfo: &str) -> Option<String> { |
| 119 |
let implementer_raw = field(cpuinfo, "CPU implementer")?; |
| 120 |
let part_raw = field(cpuinfo, "CPU part")?; |
| 121 |
let implementer = parse_hex(&implementer_raw)?; |
| 122 |
let part = parse_hex(&part_raw)?; |
| 123 |
|
| 124 |
let ids = format!("{implementer_raw}:{part_raw}"); |
| 125 |
Some( |
| 126 |
match (implementer_name(implementer), core_name(implementer, part)) { |
| 127 |
(Some(vendor), Some(core)) => format!("{vendor} {core} ({ids})"), |
| 128 |
(Some(vendor), None) => format!("{vendor} unknown core ({ids})"), |
| 129 |
(None, _) => format!("unknown implementer ({ids})"), |
| 130 |
}, |
| 131 |
) |
| 132 |
} |
| 133 |
|
| 134 |
fn parse_hex(value: &str) -> Option<u32> { |
| 135 |
let digits = value |
| 136 |
.trim() |
| 137 |
.strip_prefix("0x") |
| 138 |
.or_else(|| value.trim().strip_prefix("0X")) |
| 139 |
.unwrap_or(value.trim()); |
| 140 |
u32::from_str_radix(digits, 16).ok() |
| 141 |
} |
| 142 |
|
| 143 |
#[cfg(test)] |
| 144 |
mod tests { |
| 145 |
use super::*; |
| 146 |
|
| 147 |
|
| 148 |
|
| 149 |
|
| 150 |
const AARCH64_ALTRA: &str = "\ |
| 151 |
processor\t: 0 |
| 152 |
BogoMIPS\t: 50.00 |
| 153 |
Features\t: fp asimd evtstrm aes pmull sha1 sha2 crc32 |
| 154 |
CPU implementer\t: 0x41 |
| 155 |
CPU architecture: 8 |
| 156 |
CPU variant\t: 0x3 |
| 157 |
CPU part\t: 0xd0c |
| 158 |
CPU revision\t: 1 |
| 159 |
"; |
| 160 |
|
| 161 |
const X86_64: &str = "\ |
| 162 |
processor\t: 0 |
| 163 |
vendor_id\t: GenuineIntel |
| 164 |
model\t\t: 186 |
| 165 |
model name\t: 13th Gen Intel(R) Core(TM) i7-1370P |
| 166 |
stepping\t: 2 |
| 167 |
"; |
| 168 |
|
| 169 |
const RASPBERRY_PI: &str = "\ |
| 170 |
processor\t: 0 |
| 171 |
BogoMIPS\t: 108.00 |
| 172 |
CPU implementer\t: 0x41 |
| 173 |
CPU part\t: 0xd08 |
| 174 |
Hardware\t: BCM2835 |
| 175 |
Model\t\t: Raspberry Pi 4 Model B Rev 1.4 |
| 176 |
"; |
| 177 |
|
| 178 |
#[test] |
| 179 |
fn aarch64_midr_decodes_to_a_core_name() { |
| 180 |
assert_eq!( |
| 181 |
parse_cpu_model(AARCH64_ALTRA), |
| 182 |
"ARM Neoverse-N1 (0x41:0xd0c)" |
| 183 |
); |
| 184 |
} |
| 185 |
|
| 186 |
#[test] |
| 187 |
fn x86_prefers_the_model_name_line() { |
| 188 |
assert_eq!( |
| 189 |
parse_cpu_model(X86_64), |
| 190 |
"13th Gen Intel(R) Core(TM) i7-1370P" |
| 191 |
); |
| 192 |
} |
| 193 |
|
| 194 |
#[test] |
| 195 |
fn prose_keys_win_over_the_midr_decode() { |
| 196 |
|
| 197 |
|
| 198 |
|
| 199 |
assert_eq!( |
| 200 |
parse_cpu_model(RASPBERRY_PI), |
| 201 |
"Raspberry Pi 4 Model B Rev 1.4" |
| 202 |
); |
| 203 |
} |
| 204 |
|
| 205 |
#[test] |
| 206 |
fn unknown_part_stays_honest() { |
| 207 |
let unknown_core = AARCH64_ALTRA.replace("0xd0c", "0xd8e"); |
| 208 |
assert_eq!( |
| 209 |
parse_cpu_model(&unknown_core), |
| 210 |
"ARM unknown core (0x41:0xd8e)" |
| 211 |
); |
| 212 |
|
| 213 |
let unknown_vendor = AARCH64_ALTRA.replace("0x41", "0x69"); |
| 214 |
assert_eq!( |
| 215 |
parse_cpu_model(&unknown_vendor), |
| 216 |
"unknown implementer (0x69:0xd0c)" |
| 217 |
); |
| 218 |
} |
| 219 |
|
| 220 |
#[test] |
| 221 |
fn nothing_recognizable_reports_unknown() { |
| 222 |
assert_eq!(parse_cpu_model(""), UNKNOWN); |
| 223 |
assert_eq!(parse_cpu_model("processor\t: 0\n"), UNKNOWN); |
| 224 |
} |
| 225 |
|
| 226 |
#[test] |
| 227 |
fn empty_values_do_not_win() { |
| 228 |
let blank = "model name\t:\nModel\t\t: Thelio Astra\n"; |
| 229 |
assert_eq!(parse_cpu_model(blank), "Thelio Astra"); |
| 230 |
} |
| 231 |
|
| 232 |
#[test] |
| 233 |
fn values_containing_colons_survive() { |
| 234 |
let clocked = "model name\t: Some CPU @ 3.00GHz: turbo\n"; |
| 235 |
assert_eq!(parse_cpu_model(clocked), "Some CPU @ 3.00GHz: turbo"); |
| 236 |
} |
| 237 |
} |
| 238 |
|