use std::{
collections::HashSet,
io::{BufRead, BufReader},
};
use crate::{
CpuFeatures, Vendor,
platform::linux::{
cpu::features::detect_features_from_hashmap, utils::parse_vendor_from_string,
},
};
pub(crate) fn parse_proc_cpuinfo(
procfs_root: &std::path::Path,
) -> (
Option<String>, // vendor_id
Option<String>, // cpu_implementer
Option<String>, // model_name
Option<String>, // features_line
) {
let cpuinfo_file = if let Ok(file) = std::fs::File::open(procfs_root.join("cpuinfo")) {
file
} else {
return (None, None, None, None);
};
let mut parsed_vendor_id_proc: Option<String> = None;
let mut parsed_cpu_implementer_proc: Option<String> = None;
let mut parsed_model_name_proc: Option<String> = None;
let mut parsed_features_line_proc: Option<String> = None;
let reader = BufReader::new(cpuinfo_file);
let mut first_processor_block = true;
for line_res in reader.lines() {
let line = match line_res {
Ok(l) => l,
Err(_) => continue,
};
if line.trim().is_empty() {
if first_processor_block {
first_processor_block = false;
if (parsed_vendor_id_proc.is_some() || parsed_cpu_implementer_proc.is_some())
&& parsed_model_name_proc.is_some()
&& parsed_features_line_proc.is_some()
{
break;
}
}
continue;
}
if !first_processor_block {
continue;
}
let parts: Vec<&str> = line.split(':').map(|s| s.trim()).collect();
if parts.len() == 2 {
match parts[0] {
"vendor_id" => {
if parsed_vendor_id_proc.is_none() {
parsed_vendor_id_proc = Some(parts[1].to_string());
}
}
"CPU implementer" => {
if parsed_cpu_implementer_proc.is_none() {
parsed_cpu_implementer_proc = Some(parts[1].to_string());
}
}
"model name" | "Processor" => {
if parsed_model_name_proc.is_none() {
parsed_model_name_proc = Some(parts[1].to_string());
}
}
"flags" | "Features" if parsed_features_line_proc.is_none() => {
parsed_features_line_proc = Some(parts[1].to_string());
}
_ => {}
}
}
}
(
parsed_vendor_id_proc,
parsed_cpu_implementer_proc,
parsed_model_name_proc,
parsed_features_line_proc,
)
}
pub(crate) fn detect_via_proc_cpuinfo(
procfs_root: &std::path::Path,
vendor: &mut Vendor,
model_name: &mut String,
features: &mut CpuFeatures,
) {
let (
parsed_vendor_id_proc,
parsed_cpu_implementer_proc,
parsed_model_name_proc,
parsed_features_line_proc,
) = parse_proc_cpuinfo(procfs_root);
if parsed_vendor_id_proc.is_none()
&& parsed_cpu_implementer_proc.is_none()
&& parsed_features_line_proc.is_none()
&& parsed_model_name_proc.is_none()
{
return;
}
if *vendor == Vendor::Unknown {
*vendor = parse_vendor_from_string(&parsed_vendor_id_proc, &parsed_cpu_implementer_proc);
}
if model_name == "Unknown" || model_name.starts_with("Family") {
*model_name = parsed_model_name_proc.unwrap_or_else(|| "Unknown".to_string());
}
let mut cpu_features_from_proc_cpuinfo: HashSet<String> = HashSet::new();
if let Some(f_line) = parsed_features_line_proc {
cpu_features_from_proc_cpuinfo = f_line
.to_lowercase()
.split_whitespace()
.map(String::from)
.collect();
}
detect_features_from_hashmap(features, &cpu_features_from_proc_cpuinfo);
}
fn parse_hex_u16(s: &str) -> Option<u16> {
let s = s.trim();
let hex = s
.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.unwrap_or(s);
u16::from_str_radix(hex, 16).ok()
}
pub(crate) fn parse_cpu_parts(content: &str) -> Vec<(u16, u16)> {
let mut out = Vec::new();
let mut cur_proc: Option<u16> = None;
let mut cur_part: Option<u16> = None;
for line in content.lines() {
if line.trim().is_empty() {
if let (Some(p), Some(part)) = (cur_proc, cur_part) {
out.push((p, part));
}
cur_proc = None;
cur_part = None;
continue;
}
let mut it = line.splitn(2, ':');
let key = it.next().map(str::trim).unwrap_or("");
let Some(val) = it.next().map(str::trim) else {
continue;
};
match key {
"processor" => cur_proc = val.parse::<u16>().ok(),
"CPU part" => cur_part = parse_hex_u16(val),
_ => {}
}
}
if let (Some(p), Some(part)) = (cur_proc, cur_part) {
out.push((p, part));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpu_parts_walks_every_block() {
let cpuinfo = "\
processor\t: 0
CPU implementer\t: 0x41
CPU part\t: 0xd81
processor\t: 1
CPU part\t: 0xd81
processor\t: 2
CPU part\t: 0xd80
processor\t: 3
CPU part\t: 0xd80
";
assert_eq!(
parse_cpu_parts(cpuinfo),
vec![(0, 0xd81), (1, 0xd81), (2, 0xd80), (3, 0xd80)]
);
}
#[test]
fn cpu_parts_empty_when_no_part_field() {
let cpuinfo = "\
processor\t: 0
vendor_id\t: AuthenticAMD
model name\t: AMD Ryzen 9 5950X 16-Core Processor
processor\t: 1
vendor_id\t: AuthenticAMD
";
assert!(parse_cpu_parts(cpuinfo).is_empty());
}
}