From 0d749b903235022a13c77b6b93503806a5bff048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 08:36:00 +0200 Subject: [PATCH 01/12] make things options instead of crashing --- src/dmi.rs | 128 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/src/dmi.rs b/src/dmi.rs index 9cd5df0..33b85ec 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -31,17 +31,17 @@ use ratatui::{ #[derive(Debug)] pub struct DMI { - firmware: Firmware, - system: System, - baseboard: Baseboard, - chassis: Chassis, - memory: Memory, + firmware: Option, + system: Option, + baseboard: Option, + chassis: Option, + memory: Option, battery: Option, pub focused_section: FocusedSection, } #[non_exhaustive] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum FocusedSection { Firmware, System, @@ -109,13 +109,10 @@ impl DMI { match dmi_file_path.try_exists() { Ok(true) => {} - Ok(false) | Err(_) => { - eprintln!("No SMBIOS found"); - std::process::exit(1); - } + Ok(false) | Err(_) => bail!("No SMBIOS found"), } - let mem_file = File::open("/sys/firmware/dmi/tables/DMI")?; + let mem_file = File::open(dmi_file_path)?; let mut file = BufReader::new(mem_file); loop { @@ -193,35 +190,65 @@ impl DMI { } } + let focused_section = [ + (FocusedSection::Firmware, firmware.is_some()), + (FocusedSection::System, system.is_some()), + (FocusedSection::Baseboard, baseboard.is_some()), + (FocusedSection::Chassis, chassis.is_some()), + (FocusedSection::Memory, memory.is_some()), + (FocusedSection::Battery, battery.is_some()), + ] + .into_iter() + .find_map(|(s, present)| present.then_some(s)) + .ok_or_else(|| anyhow::anyhow!("No supported DMI structures found"))?; + Ok(Self { - firmware: firmware.unwrap(), - system: system.unwrap(), - baseboard: baseboard.unwrap(), - chassis: chassis.unwrap(), - memory: memory.unwrap(), + firmware, + system, + baseboard, + chassis, + memory, battery, - focused_section: FocusedSection::Firmware, + focused_section, }) } + fn available_sections(&self) -> Vec { + let mut sections = Vec::with_capacity(6); + if self.firmware.is_some() { + sections.push(FocusedSection::Firmware); + } + if self.system.is_some() { + sections.push(FocusedSection::System); + } + if self.baseboard.is_some() { + sections.push(FocusedSection::Baseboard); + } + if self.chassis.is_some() { + sections.push(FocusedSection::Chassis); + } + if self.memory.is_some() { + sections.push(FocusedSection::Memory); + } + if self.battery.is_some() { + sections.push(FocusedSection::Battery); + } + sections + } + pub fn handle_key_events(&mut self, key_event: KeyEvent) { + let sections = self.available_sections(); + let Some(idx) = sections.iter().position(|s| *s == self.focused_section) else { + return; + }; + match key_event.code { - KeyCode::Tab => match self.focused_section { - FocusedSection::Firmware => self.focused_section = FocusedSection::System, - FocusedSection::System => self.focused_section = FocusedSection::Baseboard, - FocusedSection::Baseboard => self.focused_section = FocusedSection::Chassis, - FocusedSection::Chassis => self.focused_section = FocusedSection::Memory, - FocusedSection::Memory => self.focused_section = FocusedSection::Battery, - FocusedSection::Battery => self.focused_section = FocusedSection::Firmware, - }, - KeyCode::BackTab => match self.focused_section { - FocusedSection::Firmware => self.focused_section = FocusedSection::Battery, - FocusedSection::System => self.focused_section = FocusedSection::Firmware, - FocusedSection::Baseboard => self.focused_section = FocusedSection::System, - FocusedSection::Chassis => self.focused_section = FocusedSection::Baseboard, - FocusedSection::Memory => self.focused_section = FocusedSection::Chassis, - FocusedSection::Battery => self.focused_section = FocusedSection::Memory, - }, + KeyCode::Tab => { + self.focused_section = sections[(idx + 1) % sections.len()]; + } + KeyCode::BackTab => { + self.focused_section = sections[(idx + sections.len() - 1) % sections.len()]; + } _ => {} } } @@ -303,16 +330,15 @@ impl DMI { (chunks[0], chunks[1]) }; + let title_spans: Vec> = self + .available_sections() + .into_iter() + .map(|s| self.title_span(s)) + .collect(); + frame.render_widget( Block::default() - .title(Line::from(vec![ - self.title_span(FocusedSection::Firmware), - self.title_span(FocusedSection::System), - self.title_span(FocusedSection::Baseboard), - self.title_span(FocusedSection::Chassis), - self.title_span(FocusedSection::Memory), - self.title_span(FocusedSection::Battery), - ])) + .title(Line::from(title_spans)) .title_alignment(Alignment::Left) .padding(Padding::top(1)) .borders(Borders::ALL) @@ -329,19 +355,29 @@ impl DMI { match self.focused_section { FocusedSection::Firmware => { - self.firmware.render(frame, section_block); + if let Some(firmware) = &self.firmware { + firmware.render(frame, section_block); + } } FocusedSection::System => { - self.system.render(frame, section_block); + if let Some(system) = &self.system { + system.render(frame, section_block); + } } FocusedSection::Baseboard => { - self.baseboard.render(frame, section_block); + if let Some(baseboard) = &self.baseboard { + baseboard.render(frame, section_block); + } } FocusedSection::Chassis => { - self.chassis.render(frame, section_block); + if let Some(chassis) = &self.chassis { + chassis.render(frame, section_block); + } } FocusedSection::Memory => { - self.memory.render(frame, section_block); + if let Some(memory) = &mut self.memory { + memory.render(frame, section_block); + } } FocusedSection::Battery => { if let Some(battery) = &self.battery { From 4f8abfe78a6700e2fa36d206e77ca4da1b902416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 08:39:28 +0200 Subject: [PATCH 02/12] reduce duplicate code --- src/dmi.rs | 78 +++++++++++------------------------------------------- 1 file changed, 16 insertions(+), 62 deletions(-) diff --git a/src/dmi.rs b/src/dmi.rs index 33b85ec..df405a2 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -254,68 +254,22 @@ impl DMI { } fn title_span(&self, header_section: FocusedSection) -> Span<'_> { - let is_focused = self.focused_section == header_section; - match header_section { - FocusedSection::Firmware => { - if is_focused { - Span::styled( - " Firmware ", - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) - } else { - Span::from(" Firmware ").fg(Color::DarkGray) - } - } - FocusedSection::System => { - if is_focused { - Span::styled( - " System ", - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) - } else { - Span::from(" System ").fg(Color::DarkGray) - } - } - FocusedSection::Baseboard => { - if is_focused { - Span::styled( - " Baseboard ", - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) - } else { - Span::from(" Baseboard ").fg(Color::DarkGray) - } - } - FocusedSection::Chassis => { - if is_focused { - Span::styled( - " Chassis ", - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) - } else { - Span::from(" Chassis ").fg(Color::DarkGray) - } - } - FocusedSection::Memory => { - if is_focused { - Span::styled( - " Memory ", - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) - } else { - Span::from(" Memory ").fg(Color::DarkGray) - } - } - FocusedSection::Battery => { - if is_focused { - Span::styled( - " Battery ", - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) - } else { - Span::from(" Battery ").fg(Color::DarkGray) - } - } + let label = match header_section { + FocusedSection::Firmware => " Firmware ", + FocusedSection::System => " System ", + FocusedSection::Baseboard => " Baseboard ", + FocusedSection::Chassis => " Chassis ", + FocusedSection::Memory => " Memory ", + FocusedSection::Battery => " Battery ", + }; + + if self.focused_section == header_section { + Span::styled( + label, + Style::default().bg(Color::Yellow).fg(Color::Black).bold(), + ) + } else { + Span::from(label).fg(Color::DarkGray) } } From ece6496beeaafd980a57f81534c3fd652e650f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 08:45:18 +0200 Subject: [PATCH 03/12] a bit of making code nicer --- src/dmi.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/dmi.rs b/src/dmi.rs index df405a2..ea97441 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -132,28 +132,26 @@ impl DMI { let mut data = vec![0; header.length.saturating_sub(4) as usize]; file.read_exact(&mut data)?; - // Read Strings + // Read strings. The string-set ends with an extra NUL after the + // last string's terminator, so for a structure with no strings the + // formatted area is followed by two NUL bytes. let mut text: Vec = Vec::new(); - - let mut previous_read_zero: bool = false; - let mut previous_read_string: bool = false; + let mut saw_leading_zero = false; loop { let mut string_buf = Vec::new(); - if let Ok(number_of_bytes_read) = file.read_until(0, &mut string_buf) { - if number_of_bytes_read == 1 { - if previous_read_zero { + match file.read_until(0, &mut string_buf)? { + 0 => break, + 1 => { + // Empty entry (just the terminator byte). + if !text.is_empty() || saw_leading_zero { break; - } else { - if previous_read_string { - break; - } - previous_read_zero = true; } - } else { + saw_leading_zero = true; + } + _ => { string_buf.pop(); text.push(String::from_utf8_lossy(&string_buf).to_string()); - previous_read_string = true; } } } From 90f8c075f730372a92dc0dbca83bbca71e570404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 08:56:36 +0200 Subject: [PATCH 04/12] memory devices --- src/dmi.rs | 36 +++- src/dmi/memory.rs | 424 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 449 insertions(+), 11 deletions(-) diff --git a/src/dmi.rs b/src/dmi.rs index ea97441..a5e8ec7 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -17,7 +17,7 @@ use crate::dmi::baseboard::Baseboard; use crate::dmi::battery::Battery; use crate::dmi::chassis::Chassis; use crate::dmi::firmware::Firmware; -use crate::dmi::memory::{Memory, PhysicalMemoryArray}; +use crate::dmi::memory::{Memory, MemoryDevice, PhysicalMemoryArray}; use crate::dmi::system::System; use crossterm::event::{KeyCode, KeyEvent}; @@ -67,6 +67,7 @@ impl From<[u8; 4]> for Header { 3 => StructureType::Chassis, 13 => StructureType::FirmwareLanguage, 16 => StructureType::PhysicalMemoryArray, + 17 => StructureType::MemoryDevice, 22 => StructureType::Battery, 127 => StructureType::End, _ => StructureType::Other, @@ -89,6 +90,7 @@ pub enum StructureType { Chassis = 3, FirmwareLanguage = 13, PhysicalMemoryArray = 16, + MemoryDevice = 17, Battery = 22, End = 127, Other = 255, @@ -102,7 +104,8 @@ impl DMI { let mut system: Option = None; let mut baseboard: Option = None; let mut chassis: Option = None; - let mut memory: Option = None; + let mut physical_memory_array: Option = None; + let mut memory_devices: Vec = Vec::new(); let mut battery: Option = None; let dmi_file_path = Path::new("/sys/firmware/dmi/tables/DMI"); @@ -177,9 +180,10 @@ impl DMI { } } StructureType::PhysicalMemoryArray => { - memory = Some(Memory { - physical_memory_array: PhysicalMemoryArray::from(data.as_slice()), - }); + physical_memory_array = Some(PhysicalMemoryArray::from(data.as_slice())); + } + StructureType::MemoryDevice => { + memory_devices.push(MemoryDevice::from((data, text))); } StructureType::Battery => { battery = Some(Battery::from((data, text))); @@ -188,6 +192,8 @@ impl DMI { } } + let memory = physical_memory_array.map(|pma| Memory::new(pma, memory_devices)); + let focused_section = [ (FocusedSection::Firmware, firmware.is_some()), (FocusedSection::System, system.is_some()), @@ -247,7 +253,13 @@ impl DMI { KeyCode::BackTab => { self.focused_section = sections[(idx + sections.len() - 1) % sections.len()]; } - _ => {} + _ => { + if self.focused_section == FocusedSection::Memory + && let Some(memory) = &mut self.memory + { + memory.handle_key_events(key_event); + } + } } } @@ -301,7 +313,17 @@ impl DMI { ); // Help banner - let message = Line::from("⇆ : Navigation").centered().cyan(); + let help_text = if self.focused_section == FocusedSection::Memory + && self + .memory + .as_ref() + .is_some_and(|m| !m.memory_devices.is_empty()) + { + "⇆ : Sections ↑↓ : Devices" + } else { + "⇆ : Navigation" + }; + let message = Line::from(help_text).centered().cyan(); frame.render_widget(message, help_block); diff --git a/src/dmi/memory.rs b/src/dmi/memory.rs index bd85e13..a29f4b6 100644 --- a/src/dmi/memory.rs +++ b/src/dmi/memory.rs @@ -1,18 +1,98 @@ +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ Frame, - layout::{Constraint, Margin, Rect}, - style::Stylize, - widgets::{Block, Cell, Padding, Row, Table}, + layout::{Constraint, Direction, Layout, Margin, Rect}, + style::{Color, Style, Stylize}, + text::{Line, Span}, + widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, }; #[derive(Debug)] pub struct Memory { pub physical_memory_array: PhysicalMemoryArray, + pub memory_devices: Vec, + selected_device: usize, } impl Memory { + pub fn new( + physical_memory_array: PhysicalMemoryArray, + memory_devices: Vec, + ) -> Self { + Self { + physical_memory_array, + memory_devices, + selected_device: 0, + } + } + + pub fn handle_key_events(&mut self, key_event: KeyEvent) { + if self.memory_devices.is_empty() { + return; + } + match key_event.code { + KeyCode::Down | KeyCode::Char('j') => { + self.selected_device = (self.selected_device + 1) % self.memory_devices.len(); + } + KeyCode::Up | KeyCode::Char('k') => { + self.selected_device = (self.selected_device + self.memory_devices.len() - 1) + % self.memory_devices.len(); + } + _ => {} + } + } + pub fn render(&mut self, frame: &mut Frame, block: Rect) { - self.physical_memory_array.render(frame, block); + if self.memory_devices.is_empty() { + self.physical_memory_array.render(frame, block); + return; + } + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(3), Constraint::Fill(1)]) + .split(block.inner(Margin::new(2, 1))); + + let summary = Line::from(vec![ + Span::from("Total Capacity: ").bold(), + Span::from(self.physical_memory_array.max_capacity.clone()), + Span::from(" "), + Span::from("Slots: ").bold(), + Span::from(self.physical_memory_array.number_memory_devices.to_string()), + Span::from(" "), + Span::from("ECC: ").bold(), + Span::from(self.physical_memory_array.error_correction.to_string()), + ]); + frame.render_widget(summary, chunks[0]); + + let body = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(22), Constraint::Fill(1)]) + .split(chunks[1]); + + let items: Vec> = self + .memory_devices + .iter() + .map(|d| ListItem::new(d.device_locator.clone())) + .collect(); + + let list = List::new(items) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .padding(Padding::horizontal(1)), + ) + .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black).bold()) + .highlight_symbol(""); + + let mut state = ListState::default(); + state.select(Some(self.selected_device)); + frame.render_stateful_widget(list, body[0], &mut state); + + if let Some(device) = self.memory_devices.get(self.selected_device) { + device.render(frame, body[1]); + } } } @@ -229,3 +309,339 @@ impl From for ErrorCorrection { } } } + +fn string_ref(idx: u8, text: &[String]) -> String { + if idx == 0 { + return "Not Specified".to_string(); + } + text.get((idx - 1) as usize) + .cloned() + .unwrap_or_else(|| "Not Specified".to_string()) +} + +#[derive(Debug)] +pub struct MemoryDevice { + device_locator: String, + bank_locator: String, + size: MemorySize, + form_factor: FormFactor, + memory_type: MemoryType, + speed: Option, + manufacturer: String, + serial_number: String, + asset_tag: String, + part_number: String, +} + +impl From<(Vec, Vec)> for MemoryDevice { + fn from((data, text): (Vec, Vec)) -> Self { + let size_field = u16::from_le_bytes(data[8..10].try_into().unwrap()); + let extended_size = if data.len() >= 28 { + Some(u32::from_le_bytes(data[24..28].try_into().unwrap())) + } else { + None + }; + let size = MemorySize::from_fields(size_field, extended_size); + + let form_factor = FormFactor::from(data[10]); + let memory_type = MemoryType::from(data[14]); + + let speed = { + let v = u16::from_le_bytes(data[17..19].try_into().unwrap()); + if v == 0 { None } else { Some(v) } + }; + + let manufacturer = data.get(19).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + let serial_number = data.get(20).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + let asset_tag = data.get(21).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + let part_number = data.get(22).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + + Self { + device_locator: string_ref(data[12], &text), + bank_locator: string_ref(data[13], &text), + size, + form_factor, + memory_type, + speed, + manufacturer, + serial_number, + asset_tag, + part_number, + } + } +} + +impl MemoryDevice { + fn render(&self, frame: &mut Frame, block: Rect) { + let rows = vec![ + Row::new(vec![ + Cell::from("Size").bold(), + Cell::from(self.size.to_string()), + ]), + Row::new(vec![ + Cell::from("Type").bold(), + Cell::from(self.memory_type.to_string()), + ]), + Row::new(vec![ + Cell::from("Form Factor").bold(), + Cell::from(self.form_factor.to_string()), + ]), + Row::new(vec![ + Cell::from("Speed").bold(), + Cell::from(match self.speed { + Some(v) => format!("{v} MT/s"), + None => "Unknown".to_string(), + }), + ]), + Row::new(vec![ + Cell::from("Bank Locator").bold(), + Cell::from(self.bank_locator.clone()), + ]), + Row::new(vec![ + Cell::from("Manufacturer").bold(), + Cell::from(self.manufacturer.clone()), + ]), + Row::new(vec![ + Cell::from("Part Number").bold(), + Cell::from(self.part_number.clone()), + ]), + Row::new(vec![ + Cell::from("Serial Number").bold(), + Cell::from(self.serial_number.clone()), + ]), + Row::new(vec![ + Cell::from("Asset Tag").bold(), + Cell::from(self.asset_tag.clone()), + ]), + ]; + + let widths = [Constraint::Length(16), Constraint::Fill(1)]; + let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); + frame.render_widget(table, block.inner(Margin::new(2, 0))); + } +} + +#[derive(Debug)] +enum MemorySize { + Empty, + Unknown, + Megabytes(u64), +} + +impl MemorySize { + fn from_fields(size_field: u16, extended_size: Option) -> Self { + match size_field { + 0 => MemorySize::Empty, + 0xFFFF => MemorySize::Unknown, + 0x7FFF => match extended_size { + Some(es) => MemorySize::Megabytes((es & 0x7FFF_FFFF) as u64), + None => MemorySize::Unknown, + }, + v if v & 0x8000 == 0 => MemorySize::Megabytes((v & 0x7FFF) as u64), + v => { + // KB granularity + let kb = (v & 0x7FFF) as u64; + MemorySize::Megabytes(kb / 1024) + } + } + } +} + +impl std::fmt::Display for MemorySize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MemorySize::Empty => write!(f, "Empty"), + MemorySize::Unknown => write!(f, "Unknown"), + MemorySize::Megabytes(mb) => { + if *mb >= 1024 && mb.is_multiple_of(1024) { + write!(f, "{} GB", mb / 1024) + } else if *mb >= 1024 { + write!(f, "{:.1} GB", *mb as f64 / 1024.0) + } else { + write!(f, "{mb} MB") + } + } + } + } +} + +#[derive(Debug, strum::Display)] +enum FormFactor { + #[strum(to_string = "Other")] + Other, + #[strum(to_string = "Unknown")] + Unknown, + #[strum(to_string = "SIMM")] + Simm, + #[strum(to_string = "SIP")] + Sip, + #[strum(to_string = "Chip")] + Chip, + #[strum(to_string = "DIP")] + Dip, + #[strum(to_string = "ZIP")] + Zip, + #[strum(to_string = "Proprietary Card")] + ProprietaryCard, + #[strum(to_string = "DIMM")] + Dimm, + #[strum(to_string = "TSOP")] + Tsop, + #[strum(to_string = "Row of chips")] + RowOfChips, + #[strum(to_string = "RIMM")] + Rimm, + #[strum(to_string = "SODIMM")] + Sodimm, + #[strum(to_string = "SRIMM")] + Srimm, + #[strum(to_string = "FB-DIMM")] + FbDimm, + #[strum(to_string = "Die")] + Die, +} + +impl From for FormFactor { + fn from(value: u8) -> Self { + match value { + 1 => Self::Other, + 3 => Self::Simm, + 4 => Self::Sip, + 5 => Self::Chip, + 6 => Self::Dip, + 7 => Self::Zip, + 8 => Self::ProprietaryCard, + 9 => Self::Dimm, + 10 => Self::Tsop, + 11 => Self::RowOfChips, + 12 => Self::Rimm, + 13 => Self::Sodimm, + 14 => Self::Srimm, + 15 => Self::FbDimm, + 16 => Self::Die, + _ => Self::Unknown, + } + } +} + +#[derive(Debug, strum::Display)] +enum MemoryType { + #[strum(to_string = "Other")] + Other, + #[strum(to_string = "Unknown")] + Unknown, + #[strum(to_string = "DRAM")] + Dram, + #[strum(to_string = "EDRAM")] + Edram, + #[strum(to_string = "VRAM")] + Vram, + #[strum(to_string = "SRAM")] + Sram, + #[strum(to_string = "RAM")] + Ram, + #[strum(to_string = "ROM")] + Rom, + #[strum(to_string = "FLASH")] + Flash, + #[strum(to_string = "EEPROM")] + Eeprom, + #[strum(to_string = "FEPROM")] + Feprom, + #[strum(to_string = "EPROM")] + Eprom, + #[strum(to_string = "CDRAM")] + Cdram, + #[strum(to_string = "3DRAM")] + Dram3D, + #[strum(to_string = "SDRAM")] + Sdram, + #[strum(to_string = "SGRAM")] + Sgram, + #[strum(to_string = "RDRAM")] + Rdram, + #[strum(to_string = "DDR")] + Ddr, + #[strum(to_string = "DDR2")] + Ddr2, + #[strum(to_string = "DDR2 FB-DIMM")] + Ddr2FbDimm, + #[strum(to_string = "DDR3")] + Ddr3, + #[strum(to_string = "FBD2")] + Fbd2, + #[strum(to_string = "DDR4")] + Ddr4, + #[strum(to_string = "LPDDR")] + LpDdr, + #[strum(to_string = "LPDDR2")] + LpDdr2, + #[strum(to_string = "LPDDR3")] + LpDdr3, + #[strum(to_string = "LPDDR4")] + LpDdr4, + #[strum(to_string = "Logical non-volatile device")] + LogicalNonVolatile, + #[strum(to_string = "HBM")] + Hbm, + #[strum(to_string = "HBM2")] + Hbm2, + #[strum(to_string = "DDR5")] + Ddr5, + #[strum(to_string = "LPDDR5")] + LpDdr5, + #[strum(to_string = "HBM3")] + Hbm3, +} + +impl From for MemoryType { + fn from(value: u8) -> Self { + match value { + 1 => Self::Other, + 3 => Self::Dram, + 4 => Self::Edram, + 5 => Self::Vram, + 6 => Self::Sram, + 7 => Self::Ram, + 8 => Self::Rom, + 9 => Self::Flash, + 10 => Self::Eeprom, + 11 => Self::Feprom, + 12 => Self::Eprom, + 13 => Self::Cdram, + 14 => Self::Dram3D, + 15 => Self::Sdram, + 16 => Self::Sgram, + 17 => Self::Rdram, + 18 => Self::Ddr, + 19 => Self::Ddr2, + 20 => Self::Ddr2FbDimm, + 24 => Self::Ddr3, + 25 => Self::Fbd2, + 26 => Self::Ddr4, + 27 => Self::LpDdr, + 28 => Self::LpDdr2, + 29 => Self::LpDdr3, + 30 => Self::LpDdr4, + 31 => Self::LogicalNonVolatile, + 32 => Self::Hbm, + 33 => Self::Hbm2, + 34 => Self::Ddr5, + 35 => Self::LpDdr5, + 36 => Self::Hbm3, + _ => Self::Unknown, + } + } +} From ebe251612b00869aae9e4bcfcd27fc7198628587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 09:13:31 +0200 Subject: [PATCH 05/12] processor decoding --- src/dmi.rs | 57 +++- src/dmi/processor.rs | 719 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 764 insertions(+), 12 deletions(-) create mode 100644 src/dmi/processor.rs diff --git a/src/dmi.rs b/src/dmi.rs index a5e8ec7..e6ae046 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -3,6 +3,7 @@ mod battery; mod chassis; mod firmware; mod memory; +mod processor; mod system; use std::{ @@ -18,6 +19,7 @@ use crate::dmi::battery::Battery; use crate::dmi::chassis::Chassis; use crate::dmi::firmware::Firmware; use crate::dmi::memory::{Memory, MemoryDevice, PhysicalMemoryArray}; +use crate::dmi::processor::{Processor, Processors}; use crate::dmi::system::System; use crossterm::event::{KeyCode, KeyEvent}; @@ -35,6 +37,7 @@ pub struct DMI { system: Option, baseboard: Option, chassis: Option, + processors: Option, memory: Option, battery: Option, pub focused_section: FocusedSection, @@ -47,6 +50,7 @@ pub enum FocusedSection { System, Baseboard, Chassis, + Processor, Memory, Battery, } @@ -65,6 +69,7 @@ impl From<[u8; 4]> for Header { 1 => StructureType::System, 2 => StructureType::Baseboard, 3 => StructureType::Chassis, + 4 => StructureType::Processor, 13 => StructureType::FirmwareLanguage, 16 => StructureType::PhysicalMemoryArray, 17 => StructureType::MemoryDevice, @@ -88,6 +93,7 @@ pub enum StructureType { System = 1, Baseboard = 2, Chassis = 3, + Processor = 4, FirmwareLanguage = 13, PhysicalMemoryArray = 16, MemoryDevice = 17, @@ -104,6 +110,7 @@ impl DMI { let mut system: Option = None; let mut baseboard: Option = None; let mut chassis: Option = None; + let mut processor_list: Vec = Vec::new(); let mut physical_memory_array: Option = None; let mut memory_devices: Vec = Vec::new(); let mut battery: Option = None; @@ -172,6 +179,9 @@ impl DMI { StructureType::Chassis => { chassis = Some(Chassis::from((data, text))); } + StructureType::Processor => { + processor_list.push(Processor::from((data, text))); + } StructureType::FirmwareLanguage => { let language_infos = firmware::LanguageInfos::from((data, text)); @@ -193,12 +203,14 @@ impl DMI { } let memory = physical_memory_array.map(|pma| Memory::new(pma, memory_devices)); + let processors = Processors::new(processor_list); let focused_section = [ (FocusedSection::Firmware, firmware.is_some()), (FocusedSection::System, system.is_some()), (FocusedSection::Baseboard, baseboard.is_some()), (FocusedSection::Chassis, chassis.is_some()), + (FocusedSection::Processor, processors.is_some()), (FocusedSection::Memory, memory.is_some()), (FocusedSection::Battery, battery.is_some()), ] @@ -211,6 +223,7 @@ impl DMI { system, baseboard, chassis, + processors, memory, battery, focused_section, @@ -218,7 +231,7 @@ impl DMI { } fn available_sections(&self) -> Vec { - let mut sections = Vec::with_capacity(6); + let mut sections = Vec::with_capacity(7); if self.firmware.is_some() { sections.push(FocusedSection::Firmware); } @@ -231,6 +244,9 @@ impl DMI { if self.chassis.is_some() { sections.push(FocusedSection::Chassis); } + if self.processors.is_some() { + sections.push(FocusedSection::Processor); + } if self.memory.is_some() { sections.push(FocusedSection::Memory); } @@ -253,13 +269,19 @@ impl DMI { KeyCode::BackTab => { self.focused_section = sections[(idx + sections.len() - 1) % sections.len()]; } - _ => { - if self.focused_section == FocusedSection::Memory - && let Some(memory) = &mut self.memory - { - memory.handle_key_events(key_event); + _ => match self.focused_section { + FocusedSection::Memory => { + if let Some(memory) = &mut self.memory { + memory.handle_key_events(key_event); + } } - } + FocusedSection::Processor => { + if let Some(processors) = &mut self.processors { + processors.handle_key_events(key_event); + } + } + _ => {} + }, } } @@ -269,6 +291,7 @@ impl DMI { FocusedSection::System => " System ", FocusedSection::Baseboard => " Baseboard ", FocusedSection::Chassis => " Chassis ", + FocusedSection::Processor => " Processor ", FocusedSection::Memory => " Memory ", FocusedSection::Battery => " Battery ", }; @@ -313,13 +336,18 @@ impl DMI { ); // Help banner - let help_text = if self.focused_section == FocusedSection::Memory - && self + let inner_nav = match self.focused_section { + FocusedSection::Memory => self .memory .as_ref() - .is_some_and(|m| !m.memory_devices.is_empty()) - { - "⇆ : Sections ↑↓ : Devices" + .is_some_and(|m| !m.memory_devices.is_empty()), + FocusedSection::Processor => { + self.processors.as_ref().is_some_and(Processors::has_multiple) + } + _ => false, + }; + let help_text = if inner_nav { + "⇆ : Sections ↑↓ : Cycle" } else { "⇆ : Navigation" }; @@ -348,6 +376,11 @@ impl DMI { chassis.render(frame, section_block); } } + FocusedSection::Processor => { + if let Some(processors) = &mut self.processors { + processors.render(frame, section_block); + } + } FocusedSection::Memory => { if let Some(memory) = &mut self.memory { memory.render(frame, section_block); diff --git a/src/dmi/processor.rs b/src/dmi/processor.rs new file mode 100644 index 0000000..e5736fd --- /dev/null +++ b/src/dmi/processor.rs @@ -0,0 +1,719 @@ +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::{ + Frame, + layout::{Constraint, Direction, Layout, Margin, Rect}, + style::{Color, Style, Stylize}, + widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, +}; + +fn string_ref(idx: u8, text: &[String]) -> String { + if idx == 0 { + return "Not Specified".to_string(); + } + text.get((idx - 1) as usize) + .cloned() + .unwrap_or_else(|| "Not Specified".to_string()) +} + +#[derive(Debug)] +pub struct Processors { + list: Vec, + selected: usize, +} + +impl Processors { + pub fn new(list: Vec) -> Option { + if list.is_empty() { + None + } else { + Some(Self { list, selected: 0 }) + } + } + + pub fn has_multiple(&self) -> bool { + self.list.len() >= 2 + } + + pub fn handle_key_events(&mut self, key_event: KeyEvent) { + if !self.has_multiple() { + return; + } + match key_event.code { + KeyCode::Down | KeyCode::Char('j') => { + self.selected = (self.selected + 1) % self.list.len(); + } + KeyCode::Up | KeyCode::Char('k') => { + self.selected = (self.selected + self.list.len() - 1) % self.list.len(); + } + _ => {} + } + } + + pub fn render(&mut self, frame: &mut Frame, block: Rect) { + if !self.has_multiple() { + self.list[0].render(frame, block); + return; + } + + let body = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(22), Constraint::Fill(1)]) + .split(block.inner(Margin::new(2, 1))); + + let items: Vec> = self + .list + .iter() + .map(|p| ListItem::new(p.socket_designation.clone())) + .collect(); + + let list = List::new(items) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .padding(Padding::horizontal(1)), + ) + .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black).bold()) + .highlight_symbol(""); + + let mut state = ListState::default(); + state.select(Some(self.selected)); + frame.render_stateful_widget(list, body[0], &mut state); + + if let Some(processor) = self.list.get(self.selected) { + processor.render(frame, body[1]); + } + } +} + +#[derive(Debug)] +pub struct Processor { + socket_designation: String, + processor_type: ProcessorType, + family: u16, + manufacturer: String, + version: String, + voltage: VoltageInfo, + max_speed: Option, + current_speed: Option, + status: ProcessorStatus, + upgrade: u8, + core_count: Option, + core_enabled: Option, + thread_count: Option, + serial_number: String, + asset_tag: String, + part_number: String, +} + +impl From<(Vec, Vec)> for Processor { + fn from((data, text): (Vec, Vec)) -> Self { + let family = { + let f1 = data[2]; + if f1 == 0xFE && data.len() >= 38 { + u16::from_le_bytes(data[36..38].try_into().unwrap()) + } else { + f1 as u16 + } + }; + + let max_speed = { + let v = u16::from_le_bytes(data[16..18].try_into().unwrap()); + (v != 0).then_some(v) + }; + let current_speed = { + let v = u16::from_le_bytes(data[18..20].try_into().unwrap()); + (v != 0).then_some(v) + }; + + let core_count = read_count(data.get(31).copied(), data.get(38..40)); + let core_enabled = read_count(data.get(32).copied(), data.get(40..42)); + let thread_count = read_count(data.get(33).copied(), data.get(42..44)); + + let serial_number = data.get(28).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + let asset_tag = data.get(29).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + let part_number = data.get(30).copied().map_or_else( + || "Not Specified".to_string(), + |b| string_ref(b, &text), + ); + + Self { + socket_designation: string_ref(data[0], &text), + processor_type: ProcessorType::from(data[1]), + family, + manufacturer: string_ref(data[3], &text), + version: string_ref(data[12], &text), + voltage: VoltageInfo::from(data[13]), + max_speed, + current_speed, + status: ProcessorStatus::from(data[20]), + upgrade: data[21], + core_count, + core_enabled, + thread_count, + serial_number, + asset_tag, + part_number, + } + } +} + +fn read_count(legacy: Option, extended: Option<&[u8]>) -> Option { + match legacy? { + 0 => None, + 0xFF => extended + .and_then(|s| s.try_into().ok()) + .map(u16::from_le_bytes), + v => Some(v as u16), + } +} + +impl Processor { + fn render(&self, frame: &mut Frame, block: Rect) { + let speed_cell = |v: Option| match v { + Some(s) => format!("{s} MHz"), + None => "Unknown".to_string(), + }; + let count_cell = |v: Option| match v { + Some(c) => c.to_string(), + None => "Unknown".to_string(), + }; + + let rows = vec![ + Row::new(vec![ + Cell::from("Socket").bold(), + Cell::from(self.socket_designation.clone()), + ]), + Row::new(vec![ + Cell::from("Type").bold(), + Cell::from(self.processor_type.to_string()), + ]), + Row::new(vec![ + Cell::from("Manufacturer").bold(), + Cell::from(self.manufacturer.clone()), + ]), + Row::new(vec![ + Cell::from("Version").bold(), + Cell::from(self.version.clone()), + ]), + Row::new(vec![ + Cell::from("Family").bold(), + Cell::from(family_name(self.family, &self.manufacturer)), + ]), + Row::new(vec![ + Cell::from("Max Speed").bold(), + Cell::from(speed_cell(self.max_speed)), + ]), + Row::new(vec![ + Cell::from("Current Speed").bold(), + Cell::from(speed_cell(self.current_speed)), + ]), + Row::new(vec![ + Cell::from("Cores").bold(), + Cell::from(count_cell(self.core_count)), + ]), + Row::new(vec![ + Cell::from("Cores Enabled").bold(), + Cell::from(count_cell(self.core_enabled)), + ]), + Row::new(vec![ + Cell::from("Threads").bold(), + Cell::from(count_cell(self.thread_count)), + ]), + Row::new(vec![ + Cell::from("Voltage").bold(), + Cell::from(self.voltage.to_string()), + ]), + Row::new(vec![ + Cell::from("Status").bold(), + Cell::from(self.status.to_string()), + ]), + Row::new(vec![ + Cell::from("Upgrade").bold(), + Cell::from(upgrade_name(self.upgrade)), + ]), + Row::new(vec![ + Cell::from("Part Number").bold(), + Cell::from(self.part_number.clone()), + ]), + Row::new(vec![ + Cell::from("Serial Number").bold(), + Cell::from(self.serial_number.clone()), + ]), + Row::new(vec![ + Cell::from("Asset Tag").bold(), + Cell::from(self.asset_tag.clone()), + ]), + ]; + + let widths = [Constraint::Length(18), Constraint::Fill(1)]; + let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); + frame.render_widget(table, block.inner(Margin::new(2, 0))); + } +} + +#[derive(Debug, strum::Display)] +enum ProcessorType { + #[strum(to_string = "Other")] + Other, + #[strum(to_string = "Unknown")] + Unknown, + #[strum(to_string = "Central Processor")] + Central, + #[strum(to_string = "Math Processor")] + Math, + #[strum(to_string = "DSP Processor")] + Dsp, + #[strum(to_string = "Video Processor")] + Video, +} + +impl From for ProcessorType { + fn from(value: u8) -> Self { + match value { + 3 => Self::Central, + 4 => Self::Math, + 5 => Self::Dsp, + 6 => Self::Video, + 2 => Self::Unknown, + _ => Self::Other, + } + } +} + +#[derive(Debug)] +struct ProcessorStatus { + populated: bool, + cpu_status: u8, +} + +impl From for ProcessorStatus { + fn from(value: u8) -> Self { + Self { + populated: (value & 0b0100_0000) != 0, + cpu_status: value & 0b0000_0111, + } + } +} + +impl std::fmt::Display for ProcessorStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.populated { + return write!(f, "Unpopulated"); + } + let cpu = match self.cpu_status { + 0 => "Unknown", + 1 => "Enabled", + 2 => "Disabled by user (BIOS Setup)", + 3 => "Disabled by BIOS (POST Error)", + 4 => "Idle, waiting to be enabled", + 7 => "Other", + _ => "Reserved", + }; + write!(f, "Populated, {cpu}") + } +} + +#[derive(Debug)] +enum VoltageInfo { + Current(u8), // tenths of a volt + Capabilities { v5: bool, v33: bool, v29: bool }, +} + +impl From for VoltageInfo { + fn from(value: u8) -> Self { + if value & 0b1000_0000 != 0 { + Self::Current(value & 0b0111_1111) + } else { + Self::Capabilities { + v5: value & 0b0000_0001 != 0, + v33: value & 0b0000_0010 != 0, + v29: value & 0b0000_0100 != 0, + } + } + } +} + +impl std::fmt::Display for VoltageInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Current(tenths) => { + let volts = *tenths as f64 / 10.0; + write!(f, "{volts:.1} V") + } + Self::Capabilities { v5, v33, v29 } => { + let mut parts: Vec<&str> = Vec::new(); + if *v5 { + parts.push("5 V"); + } + if *v33 { + parts.push("3.3 V"); + } + if *v29 { + parts.push("2.9 V"); + } + if parts.is_empty() { + write!(f, "Unknown") + } else { + write!(f, "Capable of {}", parts.join(", ")) + } + } + } + } +} + +// Family table transcribed from dmidecode 3.7+ (dmi_processor_family in dmidecode.c). +// Spec reference: SMBIOS DSP0134 §7.5.2. +const FAMILY_NAMES: &[(u16, &str)] = &[ + (0x01, "Other"), + (0x02, "Unknown"), + (0x03, "8086"), + (0x04, "80286"), + (0x05, "80386"), + (0x06, "80486"), + (0x07, "8087"), + (0x08, "80287"), + (0x09, "80387"), + (0x0A, "80487"), + (0x0B, "Pentium"), + (0x0C, "Pentium Pro"), + (0x0D, "Pentium II"), + (0x0E, "Pentium MMX"), + (0x0F, "Celeron"), + (0x10, "Pentium II Xeon"), + (0x11, "Pentium III"), + (0x12, "M1"), + (0x13, "M2"), + (0x14, "Celeron M"), + (0x15, "Pentium 4 HT"), + (0x16, "Intel"), + (0x18, "Duron"), + (0x19, "K5"), + (0x1A, "K6"), + (0x1B, "K6-2"), + (0x1C, "K6-3"), + (0x1D, "Athlon"), + (0x1E, "AMD29000"), + (0x1F, "K6-2+"), + (0x20, "Power PC"), + (0x21, "Power PC 601"), + (0x22, "Power PC 603"), + (0x23, "Power PC 603+"), + (0x24, "Power PC 604"), + (0x25, "Power PC 620"), + (0x26, "Power PC x704"), + (0x27, "Power PC 750"), + (0x28, "Core Duo"), + (0x29, "Core Duo Mobile"), + (0x2A, "Core Solo Mobile"), + (0x2B, "Atom"), + (0x2C, "Core M"), + (0x2D, "Core m3"), + (0x2E, "Core m5"), + (0x2F, "Core m7"), + (0x30, "Alpha"), + (0x31, "Alpha 21064"), + (0x32, "Alpha 21066"), + (0x33, "Alpha 21164"), + (0x34, "Alpha 21164PC"), + (0x35, "Alpha 21164a"), + (0x36, "Alpha 21264"), + (0x37, "Alpha 21364"), + (0x38, "Turion II Ultra Dual-Core Mobile M"), + (0x39, "Turion II Dual-Core Mobile M"), + (0x3A, "Athlon II Dual-Core M"), + (0x3B, "Opteron 6100"), + (0x3C, "Opteron 4100"), + (0x3D, "Opteron 6200"), + (0x3E, "Opteron 4200"), + (0x3F, "FX"), + (0x40, "MIPS"), + (0x41, "MIPS R4000"), + (0x42, "MIPS R4200"), + (0x43, "MIPS R4400"), + (0x44, "MIPS R4600"), + (0x45, "MIPS R10000"), + (0x46, "C-Series"), + (0x47, "E-Series"), + (0x48, "A-Series"), + (0x49, "G-Series"), + (0x4A, "Z-Series"), + (0x4B, "R-Series"), + (0x4C, "Opteron 4300"), + (0x4D, "Opteron 6300"), + (0x4E, "Opteron 3300"), + (0x4F, "FirePro"), + (0x50, "SPARC"), + (0x51, "SuperSPARC"), + (0x52, "MicroSPARC II"), + (0x53, "MicroSPARC IIep"), + (0x54, "UltraSPARC"), + (0x55, "UltraSPARC II"), + (0x56, "UltraSPARC IIi"), + (0x57, "UltraSPARC III"), + (0x58, "UltraSPARC IIIi"), + (0x60, "68040"), + (0x61, "68xxx"), + (0x62, "68000"), + (0x63, "68010"), + (0x64, "68020"), + (0x65, "68030"), + (0x66, "Athlon X4"), + (0x67, "Opteron X1000"), + (0x68, "Opteron X2000"), + (0x69, "Opteron A-Series"), + (0x6A, "Opteron X3000"), + (0x6B, "Zen"), + (0x70, "Hobbit"), + (0x78, "Crusoe TM5000"), + (0x79, "Crusoe TM3000"), + (0x7A, "Efficeon TM8000"), + (0x80, "Weitek"), + (0x82, "Itanium"), + (0x83, "Athlon 64"), + (0x84, "Opteron"), + (0x85, "Sempron"), + (0x86, "Turion 64"), + (0x87, "Dual-Core Opteron"), + (0x88, "Athlon 64 X2"), + (0x89, "Turion 64 X2"), + (0x8A, "Quad-Core Opteron"), + (0x8B, "Third-Generation Opteron"), + (0x8C, "Phenom FX"), + (0x8D, "Phenom X4"), + (0x8E, "Phenom X2"), + (0x8F, "Athlon X2"), + (0x90, "PA-RISC"), + (0x91, "PA-RISC 8500"), + (0x92, "PA-RISC 8000"), + (0x93, "PA-RISC 7300LC"), + (0x94, "PA-RISC 7200"), + (0x95, "PA-RISC 7100LC"), + (0x96, "PA-RISC 7100"), + (0xA0, "V30"), + (0xA1, "Quad-Core Xeon 3200"), + (0xA2, "Dual-Core Xeon 3000"), + (0xA3, "Quad-Core Xeon 5300"), + (0xA4, "Dual-Core Xeon 5100"), + (0xA5, "Dual-Core Xeon 5000"), + (0xA6, "Dual-Core Xeon LV"), + (0xA7, "Dual-Core Xeon ULV"), + (0xA8, "Dual-Core Xeon 7100"), + (0xA9, "Quad-Core Xeon 5400"), + (0xAA, "Quad-Core Xeon"), + (0xAB, "Dual-Core Xeon 5200"), + (0xAC, "Dual-Core Xeon 7200"), + (0xAD, "Quad-Core Xeon 7300"), + (0xAE, "Quad-Core Xeon 7400"), + (0xAF, "Multi-Core Xeon 7400"), + (0xB0, "Pentium III Xeon"), + (0xB1, "Pentium III Speedstep"), + (0xB2, "Pentium 4"), + (0xB3, "Xeon"), + (0xB4, "AS400"), + (0xB5, "Xeon MP"), + (0xB6, "Athlon XP"), + (0xB7, "Athlon MP"), + (0xB8, "Itanium 2"), + (0xB9, "Pentium M"), + (0xBA, "Celeron D"), + (0xBB, "Pentium D"), + (0xBC, "Pentium EE"), + (0xBD, "Core Solo"), + // 0xBE handled as special case in family_name + (0xBF, "Core 2 Duo"), + (0xC0, "Core 2 Solo"), + (0xC1, "Core 2 Extreme"), + (0xC2, "Core 2 Quad"), + (0xC3, "Core 2 Extreme Mobile"), + (0xC4, "Core 2 Duo Mobile"), + (0xC5, "Core 2 Solo Mobile"), + (0xC6, "Core i7"), + (0xC7, "Dual-Core Celeron"), + (0xC8, "IBM390"), + (0xC9, "G4"), + (0xCA, "G5"), + (0xCB, "ESA/390 G6"), + (0xCC, "z/Architecture"), + (0xCD, "Core i5"), + (0xCE, "Core i3"), + (0xCF, "Core i9"), + (0xD2, "C7-M"), + (0xD3, "C7-D"), + (0xD4, "C7"), + (0xD5, "Eden"), + (0xD6, "Multi-Core Xeon"), + (0xD7, "Dual-Core Xeon 3xxx"), + (0xD8, "Quad-Core Xeon 3xxx"), + (0xD9, "Nano"), + (0xDA, "Dual-Core Xeon 5xxx"), + (0xDB, "Quad-Core Xeon 5xxx"), + (0xDD, "Dual-Core Xeon 7xxx"), + (0xDE, "Quad-Core Xeon 7xxx"), + (0xDF, "Multi-Core Xeon 7xxx"), + (0xE0, "Multi-Core Xeon 3400"), + (0xE4, "Opteron 3000"), + (0xE5, "Sempron II"), + (0xE6, "Embedded Opteron Quad-Core"), + (0xE7, "Phenom Triple-Core"), + (0xE8, "Turion Ultra Dual-Core Mobile"), + (0xE9, "Turion Dual-Core Mobile"), + (0xEA, "Athlon Dual-Core"), + (0xEB, "Sempron SI"), + (0xEC, "Phenom II"), + (0xED, "Athlon II"), + (0xEE, "Six-Core Opteron"), + (0xEF, "Sempron M"), + (0xFA, "i860"), + (0xFB, "i960"), + (0x100, "ARMv7"), + (0x101, "ARMv8"), + (0x102, "ARMv9"), + (0x103, "ARM"), + (0x104, "SH-3"), + (0x105, "SH-4"), + (0x118, "ARM"), + (0x119, "StrongARM"), + (0x12C, "6x86"), + (0x12D, "MediaGX"), + (0x12E, "MII"), + (0x140, "WinChip"), + (0x15E, "DSP"), + (0x1F4, "Video Processor"), + (0x200, "RV32"), + (0x201, "RV64"), + (0x202, "RV128"), + (0x258, "LoongArch"), + (0x259, "Loongson 1"), + (0x25A, "Loongson 2"), + (0x25B, "Loongson 3"), + (0x25C, "Loongson 2K"), + (0x25D, "Loongson 3A"), + (0x25E, "Loongson 3B"), + (0x25F, "Loongson 3C"), + (0x260, "Loongson 3D"), + (0x261, "Loongson 3E"), + (0x262, "Dual-Core Loongson 2K 2xxx"), + (0x26C, "Quad-Core Loongson 3A 5xxx"), + (0x26D, "Multi-Core Loongson 3A 5xxx"), + (0x26E, "Quad-Core Loongson 3B 5xxx"), + (0x26F, "Multi-Core Loongson 3B 5xxx"), + (0x270, "Multi-Core Loongson 3C 5xxx"), + (0x271, "Multi-Core Loongson 3D 5xxx"), +]; + +fn family_name(family: u16, manufacturer: &str) -> String { + // 0xBE is ambiguous: Intel Core 2 vs AMD K7. Decode using manufacturer. + if family == 0xBE { + if manufacturer.contains("Intel") { + return "Core 2".to_string(); + } + if manufacturer.contains("AMD") { + return "K7".to_string(); + } + return "Core 2 or K7".to_string(); + } + + FAMILY_NAMES + .iter() + .find_map(|(id, name)| (*id == family).then_some((*name).to_string())) + .unwrap_or_else(|| format!("Family {family:#x}")) +} + +// Upgrade array transcribed from dmidecode 3.7+ (dmi_processor_upgrade in dmidecode.c). +// Spec reference: SMBIOS DSP0134 §7.5.5. Indexed by code - 0x01. +const UPGRADE_NAMES: &[&str] = &[ + "Other", // 0x01 + "Unknown", // 0x02 + "Daughter Board", // 0x03 + "ZIF Socket", // 0x04 + "Replaceable Piggy Back",// 0x05 + "None", // 0x06 + "LIF Socket", // 0x07 + "Slot 1", // 0x08 + "Slot 2", // 0x09 + "370-pin Socket", // 0x0A + "Slot A", // 0x0B + "Slot M", // 0x0C + "Socket 423", // 0x0D + "Socket A (Socket 462)", // 0x0E + "Socket 478", // 0x0F + "Socket 754", // 0x10 + "Socket 940", // 0x11 + "Socket 939", // 0x12 + "Socket mPGA604", // 0x13 + "Socket LGA771", // 0x14 + "Socket LGA775", // 0x15 + "Socket S1", // 0x16 + "Socket AM2", // 0x17 + "Socket F (1207)", // 0x18 + "Socket LGA1366", // 0x19 + "Socket G34", // 0x1A + "Socket AM3", // 0x1B + "Socket C32", // 0x1C + "Socket LGA1156", // 0x1D + "Socket LGA1567", // 0x1E + "Socket PGA988A", // 0x1F + "Socket BGA1288", // 0x20 + "Socket rPGA988B", // 0x21 + "Socket BGA1023", // 0x22 + "Socket BGA1224", // 0x23 + "Socket BGA1155", // 0x24 + "Socket LGA1356", // 0x25 + "Socket LGA2011", // 0x26 + "Socket FS1", // 0x27 + "Socket FS2", // 0x28 + "Socket FM1", // 0x29 + "Socket FM2", // 0x2A + "Socket LGA2011-3", // 0x2B + "Socket LGA1356-3", // 0x2C + "Socket LGA1150", // 0x2D + "Socket BGA1168", // 0x2E + "Socket BGA1234", // 0x2F + "Socket BGA1364", // 0x30 + "Socket AM4", // 0x31 + "Socket LGA1151", // 0x32 + "Socket BGA1356", // 0x33 + "Socket BGA1440", // 0x34 + "Socket BGA1515", // 0x35 + "Socket LGA3647-1", // 0x36 + "Socket SP3", // 0x37 + "Socket SP3r2", // 0x38 + "Socket LGA2066", // 0x39 + "Socket BGA1392", // 0x3A + "Socket BGA1510", // 0x3B + "Socket BGA1528", // 0x3C + "Socket LGA4189", // 0x3D + "Socket LGA1200", // 0x3E + "Socket LGA4677", // 0x3F + "Socket LGA1700", // 0x40 + "Socket BGA1744", // 0x41 + "Socket BGA1781", // 0x42 + "Socket BGA1211", // 0x43 + "Socket BGA2422", // 0x44 + "Socket LGA1211", // 0x45 + "Socket LGA2422", // 0x46 + "Socket LGA5773", // 0x47 + "Socket BGA5773", // 0x48 + "Socket AM5", // 0x49 + "Socket SP5", // 0x4A + "Socket SP6", // 0x4B + "Socket BGA883", // 0x4C + "Socket BGA1190", // 0x4D + "Socket BGA4129", // 0x4E + "Socket LGA4710", // 0x4F + "Socket LGA7529", // 0x50 +]; + +fn upgrade_name(upgrade: u8) -> String { + if !(0x01..=0x50).contains(&upgrade) { + return format!("Upgrade {upgrade:#x}"); + } + UPGRADE_NAMES[(upgrade - 1) as usize].to_string() +} From 440f78f88ac69e9d9c62b0822cb7d75a1c5dc1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 09:25:44 +0200 Subject: [PATCH 06/12] cpu cache magic --- src/dmi.rs | 12 ++++- src/dmi/cache.rs | 114 +++++++++++++++++++++++++++++++++++++++++++ src/dmi/processor.rs | 46 +++++++++++++++-- 3 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/dmi/cache.rs diff --git a/src/dmi.rs b/src/dmi.rs index e6ae046..90a9502 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -1,5 +1,6 @@ mod baseboard; mod battery; +mod cache; mod chassis; mod firmware; mod memory; @@ -16,6 +17,7 @@ use anyhow::{Result, bail}; use crate::dmi::baseboard::Baseboard; use crate::dmi::battery::Battery; +use crate::dmi::cache::Cache; use crate::dmi::chassis::Chassis; use crate::dmi::firmware::Firmware; use crate::dmi::memory::{Memory, MemoryDevice, PhysicalMemoryArray}; @@ -70,6 +72,7 @@ impl From<[u8; 4]> for Header { 2 => StructureType::Baseboard, 3 => StructureType::Chassis, 4 => StructureType::Processor, + 7 => StructureType::Cache, 13 => StructureType::FirmwareLanguage, 16 => StructureType::PhysicalMemoryArray, 17 => StructureType::MemoryDevice, @@ -81,7 +84,7 @@ impl From<[u8; 4]> for Header { Self { structure_type, length: value[1], - handle: u16::from_be_bytes([value[2], value[3]]), + handle: u16::from_le_bytes([value[2], value[3]]), } } } @@ -94,6 +97,7 @@ pub enum StructureType { Baseboard = 2, Chassis = 3, Processor = 4, + Cache = 7, FirmwareLanguage = 13, PhysicalMemoryArray = 16, MemoryDevice = 17, @@ -111,6 +115,7 @@ impl DMI { let mut baseboard: Option = None; let mut chassis: Option = None; let mut processor_list: Vec = Vec::new(); + let mut caches: Vec = Vec::new(); let mut physical_memory_array: Option = None; let mut memory_devices: Vec = Vec::new(); let mut battery: Option = None; @@ -182,6 +187,9 @@ impl DMI { StructureType::Processor => { processor_list.push(Processor::from((data, text))); } + StructureType::Cache => { + caches.push(Cache::parse(header.handle, data)); + } StructureType::FirmwareLanguage => { let language_infos = firmware::LanguageInfos::from((data, text)); @@ -203,7 +211,7 @@ impl DMI { } let memory = physical_memory_array.map(|pma| Memory::new(pma, memory_devices)); - let processors = Processors::new(processor_list); + let processors = Processors::new(processor_list, caches); let focused_section = [ (FocusedSection::Firmware, firmware.is_some()), diff --git a/src/dmi/cache.rs b/src/dmi/cache.rs new file mode 100644 index 0000000..e226ef4 --- /dev/null +++ b/src/dmi/cache.rs @@ -0,0 +1,114 @@ +// SMBIOS Type 7 (Cache Information). Spec reference: DSP0134 §7.8. + +#[derive(Debug)] +pub struct Cache { + pub handle: u16, + installed_size: CacheSize, + cache_type: CacheType, +} + +impl Cache { + pub fn parse(handle: u16, data: Vec) -> Self { + let installed_size_field = u16::from_le_bytes(data[5..7].try_into().unwrap()); + let installed_size_2 = if data.len() >= 23 { + Some(u32::from_le_bytes(data[19..23].try_into().unwrap())) + } else { + None + }; + let installed_size = CacheSize::from_fields(installed_size_field, installed_size_2); + + let cache_type = data.get(13).copied().map_or(CacheType::Unknown, CacheType::from); + + Self { + handle, + installed_size, + cache_type, + } + } + + pub fn summary(&self) -> String { + if matches!(self.installed_size, CacheSize::NotInstalled) { + return "Not installed".to_string(); + } + format!("{}, {}", self.installed_size, self.cache_type) + } +} + +#[derive(Debug)] +enum CacheSize { + NotInstalled, + Unknown, + Kilobytes(u64), +} + +impl CacheSize { + fn from_fields(size_field: u16, size2_field: Option) -> Self { + match size_field { + 0 => CacheSize::NotInstalled, + 0xFFFF => match size2_field { + Some(0) => CacheSize::NotInstalled, + Some(v) => decode_size_2(v), + None => CacheSize::Unknown, + }, + v => decode_size(v), + } + } +} + +// 16-bit size field: bit 15 = granularity (0 → 1 KB, 1 → 64 KB), bits 0..14 = count. +fn decode_size(v: u16) -> CacheSize { + let count = (v & 0x7FFF) as u64; + let granularity_kb: u64 = if v & 0x8000 == 0 { 1 } else { 64 }; + CacheSize::Kilobytes(count * granularity_kb) +} + +// 32-bit size field: bit 31 = granularity (0 → 1 KB, 1 → 64 KB), bits 0..30 = count. +fn decode_size_2(v: u32) -> CacheSize { + let count = (v & 0x7FFF_FFFF) as u64; + let granularity_kb: u64 = if v & 0x8000_0000 == 0 { 1 } else { 64 }; + CacheSize::Kilobytes(count * granularity_kb) +} + +impl std::fmt::Display for CacheSize { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CacheSize::NotInstalled => write!(f, "Not installed"), + CacheSize::Unknown => write!(f, "Unknown"), + CacheSize::Kilobytes(kb) => { + if *kb >= 1024 && kb.is_multiple_of(1024) { + write!(f, "{} MB", kb / 1024) + } else if *kb >= 1024 { + write!(f, "{:.1} MB", *kb as f64 / 1024.0) + } else { + write!(f, "{kb} KB") + } + } + } + } +} + +#[derive(Debug, strum::Display)] +enum CacheType { + #[strum(to_string = "Other")] + Other, + #[strum(to_string = "Unknown")] + Unknown, + #[strum(to_string = "Instruction")] + Instruction, + #[strum(to_string = "Data")] + Data, + #[strum(to_string = "Unified")] + Unified, +} + +impl From for CacheType { + fn from(value: u8) -> Self { + match value { + 3 => Self::Instruction, + 4 => Self::Data, + 5 => Self::Unified, + 1 => Self::Other, + _ => Self::Unknown, + } + } +} diff --git a/src/dmi/processor.rs b/src/dmi/processor.rs index e5736fd..c2cf22e 100644 --- a/src/dmi/processor.rs +++ b/src/dmi/processor.rs @@ -6,6 +6,8 @@ use ratatui::{ widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, }; +use crate::dmi::cache::Cache; + fn string_ref(idx: u8, text: &[String]) -> String { if idx == 0 { return "Not Specified".to_string(); @@ -18,15 +20,20 @@ fn string_ref(idx: u8, text: &[String]) -> String { #[derive(Debug)] pub struct Processors { list: Vec, + caches: Vec, selected: usize, } impl Processors { - pub fn new(list: Vec) -> Option { + pub fn new(list: Vec, caches: Vec) -> Option { if list.is_empty() { None } else { - Some(Self { list, selected: 0 }) + Some(Self { + list, + caches, + selected: 0, + }) } } @@ -51,7 +58,7 @@ impl Processors { pub fn render(&mut self, frame: &mut Frame, block: Rect) { if !self.has_multiple() { - self.list[0].render(frame, block); + self.list[0].render(frame, block, &self.caches); return; } @@ -81,7 +88,7 @@ impl Processors { frame.render_stateful_widget(list, body[0], &mut state); if let Some(processor) = self.list.get(self.selected) { - processor.render(frame, body[1]); + processor.render(frame, body[1], &self.caches); } } } @@ -98,6 +105,9 @@ pub struct Processor { current_speed: Option, status: ProcessorStatus, upgrade: u8, + l1_cache: Option, + l2_cache: Option, + l3_cache: Option, core_count: Option, core_enabled: Option, thread_count: Option, @@ -143,6 +153,10 @@ impl From<(Vec, Vec)> for Processor { |b| string_ref(b, &text), ); + let l1_cache = cache_handle(&data, 22); + let l2_cache = cache_handle(&data, 24); + let l3_cache = cache_handle(&data, 26); + Self { socket_designation: string_ref(data[0], &text), processor_type: ProcessorType::from(data[1]), @@ -154,6 +168,9 @@ impl From<(Vec, Vec)> for Processor { current_speed, status: ProcessorStatus::from(data[20]), upgrade: data[21], + l1_cache, + l2_cache, + l3_cache, core_count, core_enabled, thread_count, @@ -164,6 +181,15 @@ impl From<(Vec, Vec)> for Processor { } } +// Read a u16 cache handle at the given offset in the structure's data slice. +// Returns None if the structure is too short or the handle is 0xFFFF +// ("the device does not have any cache of this level"). +fn cache_handle(data: &[u8], offset: usize) -> Option { + let slice = data.get(offset..offset + 2)?; + let handle = u16::from_le_bytes(slice.try_into().ok()?); + (handle != 0xFFFF).then_some(handle) +} + fn read_count(legacy: Option, extended: Option<&[u8]>) -> Option { match legacy? { 0 => None, @@ -175,7 +201,7 @@ fn read_count(legacy: Option, extended: Option<&[u8]>) -> Option { } impl Processor { - fn render(&self, frame: &mut Frame, block: Rect) { + fn render(&self, frame: &mut Frame, block: Rect, caches: &[Cache]) { let speed_cell = |v: Option| match v { Some(s) => format!("{s} MHz"), None => "Unknown".to_string(), @@ -184,6 +210,13 @@ impl Processor { Some(c) => c.to_string(), None => "Unknown".to_string(), }; + let cache_row = |label: &'static str, handle: Option| { + let summary = handle + .and_then(|h| caches.iter().find(|c| c.handle == h)) + .map(Cache::summary) + .unwrap_or_else(|| "Not present".to_string()); + Row::new(vec![Cell::from(label).bold(), Cell::from(summary)]) + }; let rows = vec![ Row::new(vec![ @@ -238,6 +271,9 @@ impl Processor { Cell::from("Upgrade").bold(), Cell::from(upgrade_name(self.upgrade)), ]), + cache_row("L1 Cache", self.l1_cache), + cache_row("L2 Cache", self.l2_cache), + cache_row("L3 Cache", self.l3_cache), Row::new(vec![ Cell::from("Part Number").bold(), Cell::from(self.part_number.clone()), From 65aa06947c225f1595080e5149d4e81142195abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 09:31:15 +0200 Subject: [PATCH 07/12] slots --- src/dmi.rs | 30 +++- src/dmi/slot.rs | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 src/dmi/slot.rs diff --git a/src/dmi.rs b/src/dmi.rs index 90a9502..401b65a 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -5,6 +5,7 @@ mod chassis; mod firmware; mod memory; mod processor; +mod slot; mod system; use std::{ @@ -22,6 +23,7 @@ use crate::dmi::chassis::Chassis; use crate::dmi::firmware::Firmware; use crate::dmi::memory::{Memory, MemoryDevice, PhysicalMemoryArray}; use crate::dmi::processor::{Processor, Processors}; +use crate::dmi::slot::{Slot, Slots}; use crate::dmi::system::System; use crossterm::event::{KeyCode, KeyEvent}; @@ -41,6 +43,7 @@ pub struct DMI { chassis: Option, processors: Option, memory: Option, + slots: Option, battery: Option, pub focused_section: FocusedSection, } @@ -54,6 +57,7 @@ pub enum FocusedSection { Chassis, Processor, Memory, + Slots, Battery, } @@ -73,6 +77,7 @@ impl From<[u8; 4]> for Header { 3 => StructureType::Chassis, 4 => StructureType::Processor, 7 => StructureType::Cache, + 9 => StructureType::SystemSlots, 13 => StructureType::FirmwareLanguage, 16 => StructureType::PhysicalMemoryArray, 17 => StructureType::MemoryDevice, @@ -98,6 +103,7 @@ pub enum StructureType { Chassis = 3, Processor = 4, Cache = 7, + SystemSlots = 9, FirmwareLanguage = 13, PhysicalMemoryArray = 16, MemoryDevice = 17, @@ -118,6 +124,7 @@ impl DMI { let mut caches: Vec = Vec::new(); let mut physical_memory_array: Option = None; let mut memory_devices: Vec = Vec::new(); + let mut slot_list: Vec = Vec::new(); let mut battery: Option = None; let dmi_file_path = Path::new("/sys/firmware/dmi/tables/DMI"); @@ -190,6 +197,9 @@ impl DMI { StructureType::Cache => { caches.push(Cache::parse(header.handle, data)); } + StructureType::SystemSlots => { + slot_list.push(Slot::from((data, text))); + } StructureType::FirmwareLanguage => { let language_infos = firmware::LanguageInfos::from((data, text)); @@ -212,6 +222,7 @@ impl DMI { let memory = physical_memory_array.map(|pma| Memory::new(pma, memory_devices)); let processors = Processors::new(processor_list, caches); + let slots = Slots::new(slot_list); let focused_section = [ (FocusedSection::Firmware, firmware.is_some()), @@ -220,6 +231,7 @@ impl DMI { (FocusedSection::Chassis, chassis.is_some()), (FocusedSection::Processor, processors.is_some()), (FocusedSection::Memory, memory.is_some()), + (FocusedSection::Slots, slots.is_some()), (FocusedSection::Battery, battery.is_some()), ] .into_iter() @@ -233,13 +245,14 @@ impl DMI { chassis, processors, memory, + slots, battery, focused_section, }) } fn available_sections(&self) -> Vec { - let mut sections = Vec::with_capacity(7); + let mut sections = Vec::with_capacity(8); if self.firmware.is_some() { sections.push(FocusedSection::Firmware); } @@ -258,6 +271,9 @@ impl DMI { if self.memory.is_some() { sections.push(FocusedSection::Memory); } + if self.slots.is_some() { + sections.push(FocusedSection::Slots); + } if self.battery.is_some() { sections.push(FocusedSection::Battery); } @@ -288,6 +304,11 @@ impl DMI { processors.handle_key_events(key_event); } } + FocusedSection::Slots => { + if let Some(slots) = &mut self.slots { + slots.handle_key_events(key_event); + } + } _ => {} }, } @@ -301,6 +322,7 @@ impl DMI { FocusedSection::Chassis => " Chassis ", FocusedSection::Processor => " Processor ", FocusedSection::Memory => " Memory ", + FocusedSection::Slots => " Slots ", FocusedSection::Battery => " Battery ", }; @@ -352,6 +374,7 @@ impl DMI { FocusedSection::Processor => { self.processors.as_ref().is_some_and(Processors::has_multiple) } + FocusedSection::Slots => self.slots.as_ref().is_some_and(Slots::has_multiple), _ => false, }; let help_text = if inner_nav { @@ -394,6 +417,11 @@ impl DMI { memory.render(frame, section_block); } } + FocusedSection::Slots => { + if let Some(slots) = &mut self.slots { + slots.render(frame, section_block); + } + } FocusedSection::Battery => { if let Some(battery) = &self.battery { battery.render(frame, section_block); diff --git a/src/dmi/slot.rs b/src/dmi/slot.rs new file mode 100644 index 0000000..0824761 --- /dev/null +++ b/src/dmi/slot.rs @@ -0,0 +1,356 @@ +// SMBIOS Type 9 (System Slots). Spec reference: DSP0134 §7.10. + +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::{ + Frame, + layout::{Constraint, Direction, Layout, Margin, Rect}, + style::{Color, Style, Stylize}, + widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, +}; + +fn string_ref(idx: u8, text: &[String]) -> String { + if idx == 0 { + return "Not Specified".to_string(); + } + text.get((idx - 1) as usize) + .cloned() + .unwrap_or_else(|| "Not Specified".to_string()) +} + +#[derive(Debug)] +pub struct Slots { + list: Vec, + selected: usize, +} + +impl Slots { + pub fn new(list: Vec) -> Option { + if list.is_empty() { + None + } else { + Some(Self { list, selected: 0 }) + } + } + + pub fn has_multiple(&self) -> bool { + self.list.len() >= 2 + } + + pub fn handle_key_events(&mut self, key_event: KeyEvent) { + if !self.has_multiple() { + return; + } + match key_event.code { + KeyCode::Down | KeyCode::Char('j') => { + self.selected = (self.selected + 1) % self.list.len(); + } + KeyCode::Up | KeyCode::Char('k') => { + self.selected = (self.selected + self.list.len() - 1) % self.list.len(); + } + _ => {} + } + } + + pub fn render(&mut self, frame: &mut Frame, block: Rect) { + if !self.has_multiple() { + self.list[0].render(frame, block); + return; + } + + let body = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(28), Constraint::Fill(1)]) + .split(block.inner(Margin::new(2, 1))); + + let items: Vec> = self + .list + .iter() + .map(|s| ListItem::new(s.designation.clone())) + .collect(); + + let list = List::new(items) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .padding(Padding::horizontal(1)), + ) + .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black).bold()) + .highlight_symbol(""); + + let mut state = ListState::default(); + state.select(Some(self.selected)); + frame.render_stateful_widget(list, body[0], &mut state); + + if let Some(slot) = self.list.get(self.selected) { + slot.render(frame, body[1]); + } + } +} + +#[derive(Debug)] +pub struct Slot { + designation: String, + slot_type: u8, + bus_width: u8, + current_usage: u8, + length: u8, + id: u16, + bdf: Option, +} + +#[derive(Debug)] +struct BusDeviceFunction { + segment: u16, + bus: u8, + device: u8, + function: u8, +} + +impl std::fmt::Display for BusDeviceFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:04x}:{:02x}:{:02x}.{}", + self.segment, self.bus, self.device, self.function + ) + } +} + +impl From<(Vec, Vec)> for Slot { + fn from((data, text): (Vec, Vec)) -> Self { + let id = u16::from_le_bytes(data[5..7].try_into().unwrap()); + + // Segment/Bus/Device-Function only present in SMBIOS 2.6+ + let bdf = if data.len() >= 13 { + let segment = u16::from_le_bytes(data[9..11].try_into().unwrap()); + let bus = data[11]; + let devfunc = data[12]; + // Unset values are 0xFFFF/0xFF — skip if all unset. + if segment == 0xFFFF && bus == 0xFF && devfunc == 0xFF { + None + } else { + Some(BusDeviceFunction { + segment, + bus, + device: devfunc >> 3, + function: devfunc & 0x07, + }) + } + } else { + None + }; + + Self { + designation: string_ref(data[0], &text), + slot_type: data[1], + bus_width: data[2], + current_usage: data[3], + length: data[4], + id, + bdf, + } + } +} + +impl Slot { + fn render(&self, frame: &mut Frame, block: Rect) { + let mut rows = vec![ + Row::new(vec![ + Cell::from("Designation").bold(), + Cell::from(self.designation.clone()), + ]), + Row::new(vec![ + Cell::from("Type").bold(), + Cell::from(slot_type_name(self.slot_type)), + ]), + Row::new(vec![ + Cell::from("Bus Width").bold(), + Cell::from(slot_bus_width_name(self.bus_width)), + ]), + Row::new(vec![ + Cell::from("Current Usage").bold(), + Cell::from(slot_current_usage_name(self.current_usage)), + ]), + Row::new(vec![ + Cell::from("Length").bold(), + Cell::from(slot_length_name(self.length)), + ]), + Row::new(vec![Cell::from("ID").bold(), Cell::from(self.id.to_string())]), + ]; + if let Some(bdf) = &self.bdf { + rows.push(Row::new(vec![ + Cell::from("Bus:Device.Function").bold(), + Cell::from(bdf.to_string()), + ])); + } + + let widths = [Constraint::Length(22), Constraint::Fill(1)]; + let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); + frame.render_widget(table, block.inner(Margin::new(2, 0))); + } +} + +// Slot Type table transcribed from dmidecode 3.7+ (dmi_slot_type in dmidecode.c). +// Spec reference: SMBIOS DSP0134 §7.10.1. +const SLOT_TYPE_LOW: &[&str] = &[ + "Other", // 0x01 + "Unknown", // 0x02 + "ISA", // 0x03 + "MCA", // 0x04 + "EISA", // 0x05 + "PCI", // 0x06 + "PC Card (PCMCIA)", // 0x07 + "VLB", // 0x08 + "Proprietary", // 0x09 + "Processor Card", // 0x0A + "Proprietary Memory Card", // 0x0B + "I/O Riser Card", // 0x0C + "NuBus", // 0x0D + "PCI-66", // 0x0E + "AGP", // 0x0F + "AGP 2x", // 0x10 + "AGP 4x", // 0x11 + "PCI-X", // 0x12 + "AGP 8x", // 0x13 + "M.2 Socket 1-DP", // 0x14 + "M.2 Socket 1-SD", // 0x15 + "M.2 Socket 2", // 0x16 + "M.2 Socket 3", // 0x17 + "MXM Type I", // 0x18 + "MXM Type II", // 0x19 + "MXM Type III", // 0x1A + "MXM Type III-HE", // 0x1B + "MXM Type IV", // 0x1C + "MXM 3.0 Type A", // 0x1D + "MXM 3.0 Type B", // 0x1E + "PCI Express 2 SFF-8639 (U.2)", // 0x1F + "PCI Express 3 SFF-8639 (U.2)", // 0x20 + "PCI Express Mini 52-pin with bottom-side keep-outs", // 0x21 + "PCI Express Mini 52-pin without bottom-side keep-outs", // 0x22 + "PCI Express Mini 76-pin", // 0x23 + "PCI Express 4 SFF-8639 (U.2)", // 0x24 + "PCI Express 5 SFF-8639 (U.2)", // 0x25 + "OCP NIC 3.0 Small Form Factor (SFF)", // 0x26 + "OCP NIC 3.0 Large Form Factor (LFF)", // 0x27 + "OCP NIC Prior to 3.0", // 0x28 +]; + +// Mirrors dmidecode's spelling, including "FLexbus". +const SLOT_TYPE_CXL: &str = "CXL FLexbus 1.0"; + +const SLOT_TYPE_HIGH: &[&str] = &[ + "PC-98/C20", // 0xA0 + "PC-98/C24", // 0xA1 + "PC-98/E", // 0xA2 + "PC-98/Local Bus", // 0xA3 + "PC-98/Card", // 0xA4 + "PCI Express", // 0xA5 + "PCI Express x1", // 0xA6 + "PCI Express x2", // 0xA7 + "PCI Express x4", // 0xA8 + "PCI Express x8", // 0xA9 + "PCI Express x16", // 0xAA + "PCI Express 2", // 0xAB + "PCI Express 2 x1", // 0xAC + "PCI Express 2 x2", // 0xAD + "PCI Express 2 x4", // 0xAE + "PCI Express 2 x8", // 0xAF + "PCI Express 2 x16", // 0xB0 + "PCI Express 3", // 0xB1 + "PCI Express 3 x1", // 0xB2 + "PCI Express 3 x2", // 0xB3 + "PCI Express 3 x4", // 0xB4 + "PCI Express 3 x8", // 0xB5 + "PCI Express 3 x16", // 0xB6 + "", // 0xB7 — out of spec gap in dmidecode + "PCI Express 4", // 0xB8 + "PCI Express 4 x1", // 0xB9 + "PCI Express 4 x2", // 0xBA + "PCI Express 4 x4", // 0xBB + "PCI Express 4 x8", // 0xBC + "PCI Express 4 x16", // 0xBD + "PCI Express 5", // 0xBE + "PCI Express 5 x1", // 0xBF + "PCI Express 5 x2", // 0xC0 + "PCI Express 5 x4", // 0xC1 + "PCI Express 5 x8", // 0xC2 + "PCI Express 5 x16", // 0xC3 + "PCI Express 6+", // 0xC4 + "EDSFF E1", // 0xC5 + "EDSFF E3", // 0xC6 +]; + +fn slot_type_name(code: u8) -> String { + if (0x01..=0x28).contains(&code) { + return SLOT_TYPE_LOW[(code - 0x01) as usize].to_string(); + } + if code == 0x30 { + return SLOT_TYPE_CXL.to_string(); + } + if (0xA0..=0xC6).contains(&code) { + let s = SLOT_TYPE_HIGH[(code - 0xA0) as usize]; + if !s.is_empty() { + return s.to_string(); + } + } + format!("Slot type {code:#x}") +} + +// Spec reference: SMBIOS DSP0134 §7.10.2. +const SLOT_BUS_WIDTH: &[&str] = &[ + "Other", // 0x01 + "Unknown", // 0x02 + "8 bit", // 0x03 + "16 bit", // 0x04 + "32 bit", // 0x05 + "64 bit", // 0x06 + "128 bit", // 0x07 + "1x or x1", // 0x08 + "2x or x2", // 0x09 + "4x or x4", // 0x0A + "8x or x8", // 0x0B + "12x or x12", // 0x0C + "16x or x16", // 0x0D + "32x or x32", // 0x0E +]; + +fn slot_bus_width_name(code: u8) -> String { + if (0x01..=0x0E).contains(&code) { + return SLOT_BUS_WIDTH[(code - 0x01) as usize].to_string(); + } + format!("Bus width {code:#x}") +} + +// Spec reference: SMBIOS DSP0134 §7.10.3. +const SLOT_CURRENT_USAGE: &[&str] = &[ + "Other", // 0x01 + "Unknown", // 0x02 + "Available", // 0x03 + "In Use", // 0x04 + "Unavailable", // 0x05 +]; + +fn slot_current_usage_name(code: u8) -> String { + if (0x01..=0x05).contains(&code) { + return SLOT_CURRENT_USAGE[(code - 0x01) as usize].to_string(); + } + format!("Usage {code:#x}") +} + +// Spec reference: SMBIOS DSP0134 §7.10.4. +const SLOT_LENGTH: &[&str] = &[ + "Other", // 0x01 + "Unknown", // 0x02 + "Short", // 0x03 + "Long", // 0x04 + "2.5\" drive form factor", // 0x05 + "3.5\" drive form factor", // 0x06 +]; + +fn slot_length_name(code: u8) -> String { + if (0x01..=0x06).contains(&code) { + return SLOT_LENGTH[(code - 0x01) as usize].to_string(); + } + format!("Length {code:#x}") +} From 9a31ca674eb8a40ac0e11707f48a6d496f257476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 09:35:45 +0200 Subject: [PATCH 08/12] fix dark theme colors --- src/dmi.rs | 15 +++++---------- src/dmi/memory.rs | 4 ++-- src/dmi/processor.rs | 4 ++-- src/dmi/slot.rs | 4 ++-- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/dmi.rs b/src/dmi.rs index 401b65a..e18ddcf 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -30,7 +30,7 @@ use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ Frame, layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Style, Stylize}, + style::{Style, Stylize}, text::{Line, Span}, widgets::{Block, BorderType, Borders, Padding}, }; @@ -327,12 +327,9 @@ impl DMI { }; if self.focused_section == header_section { - Span::styled( - label, - Style::default().bg(Color::Yellow).fg(Color::Black).bold(), - ) + Span::styled(label, Style::new().bold().reversed()) } else { - Span::from(label).fg(Color::DarkGray) + Span::from(label).dim() } } @@ -359,9 +356,7 @@ impl DMI { .title_alignment(Alignment::Left) .padding(Padding::top(1)) .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .style(Style::default()) - .border_style(Style::default().fg(Color::Yellow)), + .border_type(BorderType::Rounded), section_block, ); @@ -382,7 +377,7 @@ impl DMI { } else { "⇆ : Navigation" }; - let message = Line::from(help_text).centered().cyan(); + let message = Line::from(help_text).centered().dim(); frame.render_widget(message, help_block); diff --git a/src/dmi/memory.rs b/src/dmi/memory.rs index a29f4b6..522d8a3 100644 --- a/src/dmi/memory.rs +++ b/src/dmi/memory.rs @@ -2,7 +2,7 @@ use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Margin, Rect}, - style::{Color, Style, Stylize}, + style::{Style, Stylize}, text::{Line, Span}, widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, }; @@ -83,7 +83,7 @@ impl Memory { .border_type(BorderType::Rounded) .padding(Padding::horizontal(1)), ) - .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black).bold()) + .highlight_style(Style::new().bold().reversed()) .highlight_symbol(""); let mut state = ListState::default(); diff --git a/src/dmi/processor.rs b/src/dmi/processor.rs index c2cf22e..6482331 100644 --- a/src/dmi/processor.rs +++ b/src/dmi/processor.rs @@ -2,7 +2,7 @@ use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Margin, Rect}, - style::{Color, Style, Stylize}, + style::{Style, Stylize}, widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, }; @@ -80,7 +80,7 @@ impl Processors { .border_type(BorderType::Rounded) .padding(Padding::horizontal(1)), ) - .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black).bold()) + .highlight_style(Style::new().bold().reversed()) .highlight_symbol(""); let mut state = ListState::default(); diff --git a/src/dmi/slot.rs b/src/dmi/slot.rs index 0824761..f735056 100644 --- a/src/dmi/slot.rs +++ b/src/dmi/slot.rs @@ -4,7 +4,7 @@ use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Margin, Rect}, - style::{Color, Style, Stylize}, + style::{Style, Stylize}, widgets::{Block, BorderType, Borders, Cell, List, ListItem, ListState, Padding, Row, Table}, }; @@ -75,7 +75,7 @@ impl Slots { .border_type(BorderType::Rounded) .padding(Padding::horizontal(1)), ) - .highlight_style(Style::default().bg(Color::Yellow).fg(Color::Black).bold()) + .highlight_style(Style::new().bold().reversed()) .highlight_symbol(""); let mut state = ListState::default(); From 1589f0460eb02aeb6ae8f8b442639ce04a3ba3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 12:35:22 +0200 Subject: [PATCH 09/12] support other memory tech and proper width for ram cols --- src/dmi/memory.rs | 209 ++++++++++++++++++++++++++++++++++++++----- src/dmi/processor.rs | 11 ++- src/dmi/slot.rs | 11 ++- 3 files changed, 208 insertions(+), 23 deletions(-) diff --git a/src/dmi/memory.rs b/src/dmi/memory.rs index 522d8a3..a7245db 100644 --- a/src/dmi/memory.rs +++ b/src/dmi/memory.rs @@ -26,6 +26,23 @@ impl Memory { } } + fn device_layout(&self) -> DeviceLayout { + let mut has_soldered = false; + let mut has_socketed = false; + for d in &self.memory_devices { + match d.form_factor.kind() { + FormFactorKind::Soldered => has_soldered = true, + FormFactorKind::Socketed => has_socketed = true, + FormFactorKind::Unknown => {} + } + } + match (has_soldered, has_socketed) { + (true, false) => DeviceLayout::Soldered, + (false, true) => DeviceLayout::Socketed, + _ => DeviceLayout::Mixed, + } + } + pub fn handle_key_events(&mut self, key_event: KeyEvent) { if self.memory_devices.is_empty() { return; @@ -53,11 +70,17 @@ impl Memory { .constraints([Constraint::Length(3), Constraint::Fill(1)]) .split(block.inner(Margin::new(2, 1))); + let count_label = match self.device_layout() { + DeviceLayout::Soldered => "Chips: ", + DeviceLayout::Socketed => "Slots: ", + DeviceLayout::Mixed => "Devices: ", + }; + let summary = Line::from(vec![ Span::from("Total Capacity: ").bold(), Span::from(self.physical_memory_array.max_capacity.clone()), Span::from(" "), - Span::from("Slots: ").bold(), + Span::from(count_label).bold(), Span::from(self.physical_memory_array.number_memory_devices.to_string()), Span::from(" "), Span::from("ECC: ").bold(), @@ -65,9 +88,18 @@ impl Memory { ]); frame.render_widget(summary, chunks[0]); + let max_label = self + .memory_devices + .iter() + .map(|d| d.device_locator.chars().count()) + .max() + .unwrap_or(0) as u16; + // 4 = 2 borders + 2 horizontal padding + let list_width = max_label.saturating_add(4).max(14); + let body = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Length(22), Constraint::Fill(1)]) + .constraints([Constraint::Length(list_width), Constraint::Fill(1)]) .split(chunks[1]); let items: Vec> = self @@ -96,6 +128,20 @@ impl Memory { } } +#[derive(Debug, Clone, Copy)] +enum DeviceLayout { + Soldered, + Socketed, + Mixed, +} + +#[derive(Debug, Clone, Copy)] +enum FormFactorKind { + Soldered, + Socketed, + Unknown, +} + #[derive(Debug)] pub struct PhysicalMemoryArray { location: Location, @@ -110,21 +156,22 @@ impl From<&[u8]> for PhysicalMemoryArray { fn from(data: &[u8]) -> Self { let max_capacity = { let value = u32::from_le_bytes(data[3..7].try_into().unwrap()); - - if value == 0x80008000 { - format!("{}T", u64::from_le_bytes(data[11..19].try_into().unwrap())) + // Per SMBIOS spec, 0x80000000 in the DWORD field means the actual + // value is in the Extended Maximum Capacity QWORD (in bytes). + let kb: u64 = if value == 0x80000000 && data.len() >= 19 { + u64::from_le_bytes(data[11..19].try_into().unwrap()) / 1024 } else { - match value { - value if value <= 1024 => { - format!("{value}K") - } - value if value <= 1024 * 1024 => { - format!("{}M", value / 1024) - } - _ => { - format!("{}G", value / 1024 / 1024) - } - } + value as u64 + }; + + if kb <= 1024 { + format!("{kb}K") + } else if kb <= 1024 * 1024 { + format!("{}M", kb / 1024) + } else if kb <= 1024 * 1024 * 1024 { + format!("{}G", kb / 1024 / 1024) + } else { + format!("{}T", kb / 1024 / 1024 / 1024) } }; let error_information_handle = { @@ -326,7 +373,11 @@ pub struct MemoryDevice { size: MemorySize, form_factor: FormFactor, memory_type: MemoryType, + memory_technology: MemoryTechnology, speed: Option, + configured_speed: Option, + rank: Option, + configured_voltage_mv: Option, manufacturer: String, serial_number: String, asset_tag: String, @@ -351,6 +402,30 @@ impl From<(Vec, Vec)> for MemoryDevice { if v == 0 { None } else { Some(v) } }; + let rank = data + .get(23) + .copied() + .map(|b| b & 0x0F) + .filter(|r| *r != 0); + + let configured_speed = data + .get(28..30) + .and_then(|s| s.try_into().ok()) + .map(u16::from_le_bytes) + .filter(|v| *v != 0); + + let configured_voltage_mv = data + .get(34..36) + .and_then(|s| s.try_into().ok()) + .map(u16::from_le_bytes) + .filter(|v| *v != 0); + + let memory_technology = data + .get(36) + .copied() + .map(MemoryTechnology::from) + .unwrap_or(MemoryTechnology::Unknown); + let manufacturer = data.get(19).copied().map_or_else( || "Not Specified".to_string(), |b| string_ref(b, &text), @@ -374,7 +449,11 @@ impl From<(Vec, Vec)> for MemoryDevice { size, form_factor, memory_type, + memory_technology, speed, + configured_speed, + rank, + configured_voltage_mv, manufacturer, serial_number, asset_tag, @@ -385,6 +464,23 @@ impl From<(Vec, Vec)> for MemoryDevice { impl MemoryDevice { fn render(&self, frame: &mut Frame, block: Rect) { + let speed_text = match self.speed { + Some(v) => format!("{v} MT/s"), + None => "Unknown".to_string(), + }; + let configured_speed_text = match self.configured_speed { + Some(v) => format!("{v} MT/s"), + None => "Unknown".to_string(), + }; + let rank_text = match self.rank { + Some(v) => v.to_string(), + None => "Unknown".to_string(), + }; + let voltage_text = match self.configured_voltage_mv { + Some(mv) => format_voltage(mv), + None => "Unknown".to_string(), + }; + let rows = vec![ Row::new(vec![ Cell::from("Size").bold(), @@ -394,16 +490,29 @@ impl MemoryDevice { Cell::from("Type").bold(), Cell::from(self.memory_type.to_string()), ]), + Row::new(vec![ + Cell::from("Technology").bold(), + Cell::from(self.memory_technology.to_string()), + ]), Row::new(vec![ Cell::from("Form Factor").bold(), Cell::from(self.form_factor.to_string()), ]), + Row::new(vec![ + Cell::from("Rank").bold(), + Cell::from(rank_text), + ]), Row::new(vec![ Cell::from("Speed").bold(), - Cell::from(match self.speed { - Some(v) => format!("{v} MT/s"), - None => "Unknown".to_string(), - }), + Cell::from(speed_text), + ]), + Row::new(vec![ + Cell::from("Configured Speed").bold(), + Cell::from(configured_speed_text), + ]), + Row::new(vec![ + Cell::from("Voltage").bold(), + Cell::from(voltage_text), ]), Row::new(vec![ Cell::from("Bank Locator").bold(), @@ -427,12 +536,18 @@ impl MemoryDevice { ]), ]; - let widths = [Constraint::Length(16), Constraint::Fill(1)]; + let widths = [Constraint::Length(18), Constraint::Fill(1)]; let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); frame.render_widget(table, block.inner(Margin::new(2, 0))); } } +fn format_voltage(mv: u16) -> String { + let s = format!("{:.3}", mv as f64 / 1000.0); + let trimmed = s.trim_end_matches('0').trim_end_matches('.'); + format!("{trimmed} V") +} + #[derive(Debug)] enum MemorySize { Empty, @@ -536,6 +651,26 @@ impl From for FormFactor { } } +impl FormFactor { + fn kind(&self) -> FormFactorKind { + match self { + Self::Chip | Self::RowOfChips | Self::Die => FormFactorKind::Soldered, + Self::Simm + | Self::Sip + | Self::Dip + | Self::Zip + | Self::ProprietaryCard + | Self::Dimm + | Self::Tsop + | Self::Rimm + | Self::Sodimm + | Self::Srimm + | Self::FbDimm => FormFactorKind::Socketed, + Self::Other | Self::Unknown => FormFactorKind::Unknown, + } + } +} + #[derive(Debug, strum::Display)] enum MemoryType { #[strum(to_string = "Other")] @@ -606,6 +741,38 @@ enum MemoryType { Hbm3, } +#[derive(Debug, strum::Display)] +enum MemoryTechnology { + #[strum(to_string = "Other")] + Other, + #[strum(to_string = "Unknown")] + Unknown, + #[strum(to_string = "DRAM")] + Dram, + #[strum(to_string = "NVDIMM-N")] + NvdimmN, + #[strum(to_string = "NVDIMM-F")] + NvdimmF, + #[strum(to_string = "NVDIMM-P")] + NvdimmP, + #[strum(to_string = "Intel Optane persistent memory")] + IntelOptane, +} + +impl From for MemoryTechnology { + fn from(value: u8) -> Self { + match value { + 1 => Self::Other, + 3 => Self::Dram, + 4 => Self::NvdimmN, + 5 => Self::NvdimmF, + 6 => Self::NvdimmP, + 7 => Self::IntelOptane, + _ => Self::Unknown, + } + } +} + impl From for MemoryType { fn from(value: u8) -> Self { match value { diff --git a/src/dmi/processor.rs b/src/dmi/processor.rs index 6482331..9294249 100644 --- a/src/dmi/processor.rs +++ b/src/dmi/processor.rs @@ -62,9 +62,18 @@ impl Processors { return; } + let max_label = self + .list + .iter() + .map(|p| p.socket_designation.chars().count()) + .max() + .unwrap_or(0) as u16; + // 4 = 2 borders + 2 horizontal padding + let list_width = max_label.saturating_add(4).max(14); + let body = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Length(22), Constraint::Fill(1)]) + .constraints([Constraint::Length(list_width), Constraint::Fill(1)]) .split(block.inner(Margin::new(2, 1))); let items: Vec> = self diff --git a/src/dmi/slot.rs b/src/dmi/slot.rs index f735056..48be443 100644 --- a/src/dmi/slot.rs +++ b/src/dmi/slot.rs @@ -57,9 +57,18 @@ impl Slots { return; } + let max_label = self + .list + .iter() + .map(|s| s.designation.chars().count()) + .max() + .unwrap_or(0) as u16; + // 4 = 2 borders + 2 horizontal padding + let list_width = max_label.saturating_add(4).max(14); + let body = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Length(28), Constraint::Fill(1)]) + .constraints([Constraint::Length(list_width), Constraint::Fill(1)]) .split(block.inner(Margin::new(2, 1))); let items: Vec> = self From 21acf0303b6c9fbeb756ba4932702457adbee8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 13:06:57 +0200 Subject: [PATCH 10/12] fix up padding --- src/dmi/memory.rs | 8 ++++---- src/dmi/processor.rs | 6 +++--- src/dmi/slot.rs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dmi/memory.rs b/src/dmi/memory.rs index a7245db..d51b18e 100644 --- a/src/dmi/memory.rs +++ b/src/dmi/memory.rs @@ -67,8 +67,8 @@ impl Memory { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Length(3), Constraint::Fill(1)]) - .split(block.inner(Margin::new(2, 1))); + .constraints([Constraint::Length(1), Constraint::Fill(1)]) + .split(block.inner(Margin::new(4, 2))); let count_label = match self.device_layout() { DeviceLayout::Soldered => "Chips: ", @@ -113,7 +113,7 @@ impl Memory { Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) - .padding(Padding::horizontal(1)), + .padding(Padding::new(1, 1, 1, 0)), ) .highlight_style(Style::new().bold().reversed()) .highlight_symbol(""); @@ -537,7 +537,7 @@ impl MemoryDevice { ]; let widths = [Constraint::Length(18), Constraint::Fill(1)]; - let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); + let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(2))); frame.render_widget(table, block.inner(Margin::new(2, 0))); } } diff --git a/src/dmi/processor.rs b/src/dmi/processor.rs index 9294249..646be4e 100644 --- a/src/dmi/processor.rs +++ b/src/dmi/processor.rs @@ -74,7 +74,7 @@ impl Processors { let body = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Length(list_width), Constraint::Fill(1)]) - .split(block.inner(Margin::new(2, 1))); + .split(block.inner(Margin::new(4, 1))); let items: Vec> = self .list @@ -87,7 +87,7 @@ impl Processors { Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) - .padding(Padding::horizontal(1)), + .padding(Padding::new(1, 1, 1, 0)), ) .highlight_style(Style::new().bold().reversed()) .highlight_symbol(""); @@ -298,7 +298,7 @@ impl Processor { ]; let widths = [Constraint::Length(18), Constraint::Fill(1)]; - let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); + let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(2))); frame.render_widget(table, block.inner(Margin::new(2, 0))); } } diff --git a/src/dmi/slot.rs b/src/dmi/slot.rs index 48be443..2640543 100644 --- a/src/dmi/slot.rs +++ b/src/dmi/slot.rs @@ -69,7 +69,7 @@ impl Slots { let body = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Length(list_width), Constraint::Fill(1)]) - .split(block.inner(Margin::new(2, 1))); + .split(block.inner(Margin::new(4, 2))); let items: Vec> = self .list @@ -82,7 +82,7 @@ impl Slots { Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) - .padding(Padding::horizontal(1)), + .padding(Padding::new(1, 1, 1, 0)), ) .highlight_style(Style::new().bold().reversed()) .highlight_symbol(""); @@ -195,7 +195,7 @@ impl Slot { } let widths = [Constraint::Length(22), Constraint::Fill(1)]; - let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(1))); + let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(2))); frame.render_widget(table, block.inner(Margin::new(2, 0))); } } From 7de5093ecee5c36a14466f20310801cd5c0e0c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 13:20:45 +0200 Subject: [PATCH 11/12] docs --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cd3b70a..2579b34 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,12 @@ sudo dmitui - [x] System (type 1) - [x] Baseboard (type 2) - [x] Chassis (type 3) (Partially) +- [x] Processor (type 4) +- [x] Cache (type 7) +- [x] System Slots (type 9) (Partially) - [x] Firmware Language Information (type 13) - [x] Physical Memory Array (type 16) +- [x] Memory Device (type 17) - [x] Portable Battery (type 22) ## ⚖️ License From 8585f47593e1657bc22c35dffa9d6cb44e65e872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=C3=B6b?= <57842588+y5@users.noreply.github.com> Date: Sat, 16 May 2026 13:22:28 +0200 Subject: [PATCH 12/12] ok fmt --- src/dmi.rs | 7 +- src/dmi/cache.rs | 5 +- src/dmi/memory.rs | 53 +++++------- src/dmi/processor.rs | 184 +++++++++++++++++++-------------------- src/dmi/slot.rs | 201 ++++++++++++++++++++++--------------------- 5 files changed, 222 insertions(+), 228 deletions(-) diff --git a/src/dmi.rs b/src/dmi.rs index e18ddcf..87835b5 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -366,9 +366,10 @@ impl DMI { .memory .as_ref() .is_some_and(|m| !m.memory_devices.is_empty()), - FocusedSection::Processor => { - self.processors.as_ref().is_some_and(Processors::has_multiple) - } + FocusedSection::Processor => self + .processors + .as_ref() + .is_some_and(Processors::has_multiple), FocusedSection::Slots => self.slots.as_ref().is_some_and(Slots::has_multiple), _ => false, }; diff --git a/src/dmi/cache.rs b/src/dmi/cache.rs index e226ef4..e310df8 100644 --- a/src/dmi/cache.rs +++ b/src/dmi/cache.rs @@ -17,7 +17,10 @@ impl Cache { }; let installed_size = CacheSize::from_fields(installed_size_field, installed_size_2); - let cache_type = data.get(13).copied().map_or(CacheType::Unknown, CacheType::from); + let cache_type = data + .get(13) + .copied() + .map_or(CacheType::Unknown, CacheType::from); Self { handle, diff --git a/src/dmi/memory.rs b/src/dmi/memory.rs index d51b18e..45d58ea 100644 --- a/src/dmi/memory.rs +++ b/src/dmi/memory.rs @@ -402,11 +402,7 @@ impl From<(Vec, Vec)> for MemoryDevice { if v == 0 { None } else { Some(v) } }; - let rank = data - .get(23) - .copied() - .map(|b| b & 0x0F) - .filter(|r| *r != 0); + let rank = data.get(23).copied().map(|b| b & 0x0F).filter(|r| *r != 0); let configured_speed = data .get(28..30) @@ -426,22 +422,22 @@ impl From<(Vec, Vec)> for MemoryDevice { .map(MemoryTechnology::from) .unwrap_or(MemoryTechnology::Unknown); - let manufacturer = data.get(19).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); - let serial_number = data.get(20).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); - let asset_tag = data.get(21).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); - let part_number = data.get(22).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); + let manufacturer = data + .get(19) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); + let serial_number = data + .get(20) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); + let asset_tag = data + .get(21) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); + let part_number = data + .get(22) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); Self { device_locator: string_ref(data[12], &text), @@ -498,22 +494,13 @@ impl MemoryDevice { Cell::from("Form Factor").bold(), Cell::from(self.form_factor.to_string()), ]), - Row::new(vec![ - Cell::from("Rank").bold(), - Cell::from(rank_text), - ]), - Row::new(vec![ - Cell::from("Speed").bold(), - Cell::from(speed_text), - ]), + Row::new(vec![Cell::from("Rank").bold(), Cell::from(rank_text)]), + Row::new(vec![Cell::from("Speed").bold(), Cell::from(speed_text)]), Row::new(vec![ Cell::from("Configured Speed").bold(), Cell::from(configured_speed_text), ]), - Row::new(vec![ - Cell::from("Voltage").bold(), - Cell::from(voltage_text), - ]), + Row::new(vec![Cell::from("Voltage").bold(), Cell::from(voltage_text)]), Row::new(vec![ Cell::from("Bank Locator").bold(), Cell::from(self.bank_locator.clone()), diff --git a/src/dmi/processor.rs b/src/dmi/processor.rs index 646be4e..2b0a05b 100644 --- a/src/dmi/processor.rs +++ b/src/dmi/processor.rs @@ -149,18 +149,18 @@ impl From<(Vec, Vec)> for Processor { let core_enabled = read_count(data.get(32).copied(), data.get(40..42)); let thread_count = read_count(data.get(33).copied(), data.get(42..44)); - let serial_number = data.get(28).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); - let asset_tag = data.get(29).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); - let part_number = data.get(30).copied().map_or_else( - || "Not Specified".to_string(), - |b| string_ref(b, &text), - ); + let serial_number = data + .get(28) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); + let asset_tag = data + .get(29) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); + let part_number = data + .get(30) + .copied() + .map_or_else(|| "Not Specified".to_string(), |b| string_ref(b, &text)); let l1_cache = cache_handle(&data, 22); let l2_cache = cache_handle(&data, 24); @@ -674,86 +674,86 @@ fn family_name(family: u16, manufacturer: &str) -> String { // Upgrade array transcribed from dmidecode 3.7+ (dmi_processor_upgrade in dmidecode.c). // Spec reference: SMBIOS DSP0134 §7.5.5. Indexed by code - 0x01. const UPGRADE_NAMES: &[&str] = &[ - "Other", // 0x01 - "Unknown", // 0x02 - "Daughter Board", // 0x03 - "ZIF Socket", // 0x04 - "Replaceable Piggy Back",// 0x05 - "None", // 0x06 - "LIF Socket", // 0x07 - "Slot 1", // 0x08 - "Slot 2", // 0x09 - "370-pin Socket", // 0x0A - "Slot A", // 0x0B - "Slot M", // 0x0C - "Socket 423", // 0x0D - "Socket A (Socket 462)", // 0x0E - "Socket 478", // 0x0F - "Socket 754", // 0x10 - "Socket 940", // 0x11 - "Socket 939", // 0x12 - "Socket mPGA604", // 0x13 - "Socket LGA771", // 0x14 - "Socket LGA775", // 0x15 - "Socket S1", // 0x16 - "Socket AM2", // 0x17 - "Socket F (1207)", // 0x18 - "Socket LGA1366", // 0x19 - "Socket G34", // 0x1A - "Socket AM3", // 0x1B - "Socket C32", // 0x1C - "Socket LGA1156", // 0x1D - "Socket LGA1567", // 0x1E - "Socket PGA988A", // 0x1F - "Socket BGA1288", // 0x20 - "Socket rPGA988B", // 0x21 - "Socket BGA1023", // 0x22 - "Socket BGA1224", // 0x23 - "Socket BGA1155", // 0x24 - "Socket LGA1356", // 0x25 - "Socket LGA2011", // 0x26 - "Socket FS1", // 0x27 - "Socket FS2", // 0x28 - "Socket FM1", // 0x29 - "Socket FM2", // 0x2A - "Socket LGA2011-3", // 0x2B - "Socket LGA1356-3", // 0x2C - "Socket LGA1150", // 0x2D - "Socket BGA1168", // 0x2E - "Socket BGA1234", // 0x2F - "Socket BGA1364", // 0x30 - "Socket AM4", // 0x31 - "Socket LGA1151", // 0x32 - "Socket BGA1356", // 0x33 - "Socket BGA1440", // 0x34 - "Socket BGA1515", // 0x35 - "Socket LGA3647-1", // 0x36 - "Socket SP3", // 0x37 - "Socket SP3r2", // 0x38 - "Socket LGA2066", // 0x39 - "Socket BGA1392", // 0x3A - "Socket BGA1510", // 0x3B - "Socket BGA1528", // 0x3C - "Socket LGA4189", // 0x3D - "Socket LGA1200", // 0x3E - "Socket LGA4677", // 0x3F - "Socket LGA1700", // 0x40 - "Socket BGA1744", // 0x41 - "Socket BGA1781", // 0x42 - "Socket BGA1211", // 0x43 - "Socket BGA2422", // 0x44 - "Socket LGA1211", // 0x45 - "Socket LGA2422", // 0x46 - "Socket LGA5773", // 0x47 - "Socket BGA5773", // 0x48 - "Socket AM5", // 0x49 - "Socket SP5", // 0x4A - "Socket SP6", // 0x4B - "Socket BGA883", // 0x4C - "Socket BGA1190", // 0x4D - "Socket BGA4129", // 0x4E - "Socket LGA4710", // 0x4F - "Socket LGA7529", // 0x50 + "Other", // 0x01 + "Unknown", // 0x02 + "Daughter Board", // 0x03 + "ZIF Socket", // 0x04 + "Replaceable Piggy Back", // 0x05 + "None", // 0x06 + "LIF Socket", // 0x07 + "Slot 1", // 0x08 + "Slot 2", // 0x09 + "370-pin Socket", // 0x0A + "Slot A", // 0x0B + "Slot M", // 0x0C + "Socket 423", // 0x0D + "Socket A (Socket 462)", // 0x0E + "Socket 478", // 0x0F + "Socket 754", // 0x10 + "Socket 940", // 0x11 + "Socket 939", // 0x12 + "Socket mPGA604", // 0x13 + "Socket LGA771", // 0x14 + "Socket LGA775", // 0x15 + "Socket S1", // 0x16 + "Socket AM2", // 0x17 + "Socket F (1207)", // 0x18 + "Socket LGA1366", // 0x19 + "Socket G34", // 0x1A + "Socket AM3", // 0x1B + "Socket C32", // 0x1C + "Socket LGA1156", // 0x1D + "Socket LGA1567", // 0x1E + "Socket PGA988A", // 0x1F + "Socket BGA1288", // 0x20 + "Socket rPGA988B", // 0x21 + "Socket BGA1023", // 0x22 + "Socket BGA1224", // 0x23 + "Socket BGA1155", // 0x24 + "Socket LGA1356", // 0x25 + "Socket LGA2011", // 0x26 + "Socket FS1", // 0x27 + "Socket FS2", // 0x28 + "Socket FM1", // 0x29 + "Socket FM2", // 0x2A + "Socket LGA2011-3", // 0x2B + "Socket LGA1356-3", // 0x2C + "Socket LGA1150", // 0x2D + "Socket BGA1168", // 0x2E + "Socket BGA1234", // 0x2F + "Socket BGA1364", // 0x30 + "Socket AM4", // 0x31 + "Socket LGA1151", // 0x32 + "Socket BGA1356", // 0x33 + "Socket BGA1440", // 0x34 + "Socket BGA1515", // 0x35 + "Socket LGA3647-1", // 0x36 + "Socket SP3", // 0x37 + "Socket SP3r2", // 0x38 + "Socket LGA2066", // 0x39 + "Socket BGA1392", // 0x3A + "Socket BGA1510", // 0x3B + "Socket BGA1528", // 0x3C + "Socket LGA4189", // 0x3D + "Socket LGA1200", // 0x3E + "Socket LGA4677", // 0x3F + "Socket LGA1700", // 0x40 + "Socket BGA1744", // 0x41 + "Socket BGA1781", // 0x42 + "Socket BGA1211", // 0x43 + "Socket BGA2422", // 0x44 + "Socket LGA1211", // 0x45 + "Socket LGA2422", // 0x46 + "Socket LGA5773", // 0x47 + "Socket BGA5773", // 0x48 + "Socket AM5", // 0x49 + "Socket SP5", // 0x4A + "Socket SP6", // 0x4B + "Socket BGA883", // 0x4C + "Socket BGA1190", // 0x4D + "Socket BGA4129", // 0x4E + "Socket LGA4710", // 0x4F + "Socket LGA7529", // 0x50 ]; fn upgrade_name(upgrade: u8) -> String { diff --git a/src/dmi/slot.rs b/src/dmi/slot.rs index 2640543..1ae5e1d 100644 --- a/src/dmi/slot.rs +++ b/src/dmi/slot.rs @@ -185,7 +185,10 @@ impl Slot { Cell::from("Length").bold(), Cell::from(slot_length_name(self.length)), ]), - Row::new(vec![Cell::from("ID").bold(), Cell::from(self.id.to_string())]), + Row::new(vec![ + Cell::from("ID").bold(), + Cell::from(self.id.to_string()), + ]), ]; if let Some(bdf) = &self.bdf { rows.push(Row::new(vec![ @@ -203,91 +206,91 @@ impl Slot { // Slot Type table transcribed from dmidecode 3.7+ (dmi_slot_type in dmidecode.c). // Spec reference: SMBIOS DSP0134 §7.10.1. const SLOT_TYPE_LOW: &[&str] = &[ - "Other", // 0x01 - "Unknown", // 0x02 - "ISA", // 0x03 - "MCA", // 0x04 - "EISA", // 0x05 - "PCI", // 0x06 - "PC Card (PCMCIA)", // 0x07 - "VLB", // 0x08 - "Proprietary", // 0x09 - "Processor Card", // 0x0A - "Proprietary Memory Card", // 0x0B - "I/O Riser Card", // 0x0C - "NuBus", // 0x0D - "PCI-66", // 0x0E - "AGP", // 0x0F - "AGP 2x", // 0x10 - "AGP 4x", // 0x11 - "PCI-X", // 0x12 - "AGP 8x", // 0x13 - "M.2 Socket 1-DP", // 0x14 - "M.2 Socket 1-SD", // 0x15 - "M.2 Socket 2", // 0x16 - "M.2 Socket 3", // 0x17 - "MXM Type I", // 0x18 - "MXM Type II", // 0x19 - "MXM Type III", // 0x1A - "MXM Type III-HE", // 0x1B - "MXM Type IV", // 0x1C - "MXM 3.0 Type A", // 0x1D - "MXM 3.0 Type B", // 0x1E - "PCI Express 2 SFF-8639 (U.2)", // 0x1F - "PCI Express 3 SFF-8639 (U.2)", // 0x20 - "PCI Express Mini 52-pin with bottom-side keep-outs", // 0x21 + "Other", // 0x01 + "Unknown", // 0x02 + "ISA", // 0x03 + "MCA", // 0x04 + "EISA", // 0x05 + "PCI", // 0x06 + "PC Card (PCMCIA)", // 0x07 + "VLB", // 0x08 + "Proprietary", // 0x09 + "Processor Card", // 0x0A + "Proprietary Memory Card", // 0x0B + "I/O Riser Card", // 0x0C + "NuBus", // 0x0D + "PCI-66", // 0x0E + "AGP", // 0x0F + "AGP 2x", // 0x10 + "AGP 4x", // 0x11 + "PCI-X", // 0x12 + "AGP 8x", // 0x13 + "M.2 Socket 1-DP", // 0x14 + "M.2 Socket 1-SD", // 0x15 + "M.2 Socket 2", // 0x16 + "M.2 Socket 3", // 0x17 + "MXM Type I", // 0x18 + "MXM Type II", // 0x19 + "MXM Type III", // 0x1A + "MXM Type III-HE", // 0x1B + "MXM Type IV", // 0x1C + "MXM 3.0 Type A", // 0x1D + "MXM 3.0 Type B", // 0x1E + "PCI Express 2 SFF-8639 (U.2)", // 0x1F + "PCI Express 3 SFF-8639 (U.2)", // 0x20 + "PCI Express Mini 52-pin with bottom-side keep-outs", // 0x21 "PCI Express Mini 52-pin without bottom-side keep-outs", // 0x22 - "PCI Express Mini 76-pin", // 0x23 - "PCI Express 4 SFF-8639 (U.2)", // 0x24 - "PCI Express 5 SFF-8639 (U.2)", // 0x25 - "OCP NIC 3.0 Small Form Factor (SFF)", // 0x26 - "OCP NIC 3.0 Large Form Factor (LFF)", // 0x27 - "OCP NIC Prior to 3.0", // 0x28 + "PCI Express Mini 76-pin", // 0x23 + "PCI Express 4 SFF-8639 (U.2)", // 0x24 + "PCI Express 5 SFF-8639 (U.2)", // 0x25 + "OCP NIC 3.0 Small Form Factor (SFF)", // 0x26 + "OCP NIC 3.0 Large Form Factor (LFF)", // 0x27 + "OCP NIC Prior to 3.0", // 0x28 ]; // Mirrors dmidecode's spelling, including "FLexbus". const SLOT_TYPE_CXL: &str = "CXL FLexbus 1.0"; const SLOT_TYPE_HIGH: &[&str] = &[ - "PC-98/C20", // 0xA0 - "PC-98/C24", // 0xA1 - "PC-98/E", // 0xA2 - "PC-98/Local Bus", // 0xA3 - "PC-98/Card", // 0xA4 - "PCI Express", // 0xA5 - "PCI Express x1", // 0xA6 - "PCI Express x2", // 0xA7 - "PCI Express x4", // 0xA8 - "PCI Express x8", // 0xA9 - "PCI Express x16", // 0xAA - "PCI Express 2", // 0xAB - "PCI Express 2 x1", // 0xAC - "PCI Express 2 x2", // 0xAD - "PCI Express 2 x4", // 0xAE - "PCI Express 2 x8", // 0xAF - "PCI Express 2 x16", // 0xB0 - "PCI Express 3", // 0xB1 - "PCI Express 3 x1", // 0xB2 - "PCI Express 3 x2", // 0xB3 - "PCI Express 3 x4", // 0xB4 - "PCI Express 3 x8", // 0xB5 - "PCI Express 3 x16", // 0xB6 - "", // 0xB7 — out of spec gap in dmidecode - "PCI Express 4", // 0xB8 - "PCI Express 4 x1", // 0xB9 - "PCI Express 4 x2", // 0xBA - "PCI Express 4 x4", // 0xBB - "PCI Express 4 x8", // 0xBC - "PCI Express 4 x16", // 0xBD - "PCI Express 5", // 0xBE - "PCI Express 5 x1", // 0xBF - "PCI Express 5 x2", // 0xC0 - "PCI Express 5 x4", // 0xC1 - "PCI Express 5 x8", // 0xC2 - "PCI Express 5 x16", // 0xC3 - "PCI Express 6+", // 0xC4 - "EDSFF E1", // 0xC5 - "EDSFF E3", // 0xC6 + "PC-98/C20", // 0xA0 + "PC-98/C24", // 0xA1 + "PC-98/E", // 0xA2 + "PC-98/Local Bus", // 0xA3 + "PC-98/Card", // 0xA4 + "PCI Express", // 0xA5 + "PCI Express x1", // 0xA6 + "PCI Express x2", // 0xA7 + "PCI Express x4", // 0xA8 + "PCI Express x8", // 0xA9 + "PCI Express x16", // 0xAA + "PCI Express 2", // 0xAB + "PCI Express 2 x1", // 0xAC + "PCI Express 2 x2", // 0xAD + "PCI Express 2 x4", // 0xAE + "PCI Express 2 x8", // 0xAF + "PCI Express 2 x16", // 0xB0 + "PCI Express 3", // 0xB1 + "PCI Express 3 x1", // 0xB2 + "PCI Express 3 x2", // 0xB3 + "PCI Express 3 x4", // 0xB4 + "PCI Express 3 x8", // 0xB5 + "PCI Express 3 x16", // 0xB6 + "", // 0xB7 — out of spec gap in dmidecode + "PCI Express 4", // 0xB8 + "PCI Express 4 x1", // 0xB9 + "PCI Express 4 x2", // 0xBA + "PCI Express 4 x4", // 0xBB + "PCI Express 4 x8", // 0xBC + "PCI Express 4 x16", // 0xBD + "PCI Express 5", // 0xBE + "PCI Express 5 x1", // 0xBF + "PCI Express 5 x2", // 0xC0 + "PCI Express 5 x4", // 0xC1 + "PCI Express 5 x8", // 0xC2 + "PCI Express 5 x16", // 0xC3 + "PCI Express 6+", // 0xC4 + "EDSFF E1", // 0xC5 + "EDSFF E3", // 0xC6 ]; fn slot_type_name(code: u8) -> String { @@ -308,20 +311,20 @@ fn slot_type_name(code: u8) -> String { // Spec reference: SMBIOS DSP0134 §7.10.2. const SLOT_BUS_WIDTH: &[&str] = &[ - "Other", // 0x01 - "Unknown", // 0x02 - "8 bit", // 0x03 - "16 bit", // 0x04 - "32 bit", // 0x05 - "64 bit", // 0x06 - "128 bit", // 0x07 - "1x or x1", // 0x08 - "2x or x2", // 0x09 - "4x or x4", // 0x0A - "8x or x8", // 0x0B - "12x or x12", // 0x0C - "16x or x16", // 0x0D - "32x or x32", // 0x0E + "Other", // 0x01 + "Unknown", // 0x02 + "8 bit", // 0x03 + "16 bit", // 0x04 + "32 bit", // 0x05 + "64 bit", // 0x06 + "128 bit", // 0x07 + "1x or x1", // 0x08 + "2x or x2", // 0x09 + "4x or x4", // 0x0A + "8x or x8", // 0x0B + "12x or x12", // 0x0C + "16x or x16", // 0x0D + "32x or x32", // 0x0E ]; fn slot_bus_width_name(code: u8) -> String { @@ -349,12 +352,12 @@ fn slot_current_usage_name(code: u8) -> String { // Spec reference: SMBIOS DSP0134 §7.10.4. const SLOT_LENGTH: &[&str] = &[ - "Other", // 0x01 - "Unknown", // 0x02 - "Short", // 0x03 - "Long", // 0x04 - "2.5\" drive form factor", // 0x05 - "3.5\" drive form factor", // 0x06 + "Other", // 0x01 + "Unknown", // 0x02 + "Short", // 0x03 + "Long", // 0x04 + "2.5\" drive form factor", // 0x05 + "3.5\" drive form factor", // 0x06 ]; fn slot_length_name(code: u8) -> String {