linsight_plugin_sdk/
pciids.rs1use std::collections::HashMap;
18use std::path::Path;
19use std::sync::OnceLock;
20
21#[derive(Debug, Default)]
22pub struct PciIdDb {
23 vendors: HashMap<u16, String>,
24 devices: HashMap<(u16, u16), String>,
25}
26
27impl PciIdDb {
28 pub fn parse(text: &str) -> Self {
29 let mut db = Self::default();
30 let mut current_vendor: Option<u16> = None;
31 for line in text.lines() {
32 if line.starts_with('#') || line.trim().is_empty() {
33 continue;
34 }
35 if line.starts_with("\t\t") {
36 continue;
37 }
38 if let Some(rest) = line.strip_prefix('\t') {
39 let Some(vendor) = current_vendor else { continue };
40 let mut parts = rest.splitn(2, char::is_whitespace);
41 let Some(dev_hex) = parts.next() else { continue };
42 let name = parts.next().map(str::trim).unwrap_or("");
43 if let Ok(dev) = u16::from_str_radix(dev_hex, 16) {
44 db.devices.insert((vendor, dev), name.to_owned());
45 }
46 continue;
47 }
48 let mut parts = line.splitn(2, char::is_whitespace);
49 let Some(vendor_hex) = parts.next() else { continue };
50 let name = parts.next().map(str::trim).unwrap_or("");
51 if let Ok(v) = u16::from_str_radix(vendor_hex, 16) {
52 current_vendor = Some(v);
53 db.vendors.insert(v, name.to_owned());
54 } else {
55 current_vendor = None;
56 }
57 }
58 db
59 }
60
61 pub fn lookup(&self, vendor: u16, device: u16) -> Option<String> {
62 self.devices.get(&(vendor, device)).cloned()
63 }
64
65 pub fn vendor_name(&self, vendor: u16) -> Option<String> {
66 self.vendors.get(&vendor).cloned()
67 }
68
69 pub fn load_default() -> Self {
72 for p in ["/usr/share/hwdata/pci.ids", "/usr/share/misc/pci.ids"] {
73 if let Ok(text) = std::fs::read_to_string(Path::new(p)) {
74 return Self::parse(&text);
75 }
76 }
77 Self::default()
78 }
79
80 pub fn shared() -> &'static Self {
84 static CELL: OnceLock<PciIdDb> = OnceLock::new();
85 CELL.get_or_init(Self::load_default)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 const FIXTURE: &str = include_str!("../tests/fixtures/mini-pci.ids");
94
95 #[test]
96 fn parses_fixture_into_lookup_table() {
97 let db = PciIdDb::parse(FIXTURE);
98 assert_eq!(db.lookup(0x8086, 0xe223).as_deref(), Some("Battlemage [Arc B-series]"));
99 assert_eq!(db.lookup(0x8086, 0xb0a0).as_deref(), Some("Arrow Lake-S iGPU"));
100 assert_eq!(db.lookup(0x10de, 0x2782).as_deref(), Some("GeForce RTX 4070"));
101 }
102
103 #[test]
104 fn lookup_misses_return_none() {
105 let db = PciIdDb::parse(FIXTURE);
106 assert!(db.lookup(0x8086, 0x0000).is_none());
107 assert!(db.lookup(0xdead, 0xbeef).is_none());
108 }
109
110 #[test]
111 fn vendor_name_lookup() {
112 let db = PciIdDb::parse(FIXTURE);
113 assert_eq!(db.vendor_name(0x8086).as_deref(), Some("Intel Corporation"));
114 assert_eq!(db.vendor_name(0x10de).as_deref(), Some("NVIDIA Corporation"));
115 }
116
117 #[test]
118 fn parse_skips_subdevice_lines() {
119 let s = "8086 Intel\n\te223 Battlemage\n\t\t1234 5678 Some subsystem\n";
120 let db = PciIdDb::parse(s);
121 assert_eq!(db.lookup(0x8086, 0xe223).as_deref(), Some("Battlemage"));
122 }
123}