1use anyhow::{anyhow, Context as _, Error};
5use std::collections::HashMap;
6use std::str::Lines;
7
8#[derive(Debug)]
9struct Device<'a> {
10 name: &'a str,
11 device: u16,
12 #[allow(unused)]
13 subvendor: Option<u16>,
14 #[allow(unused)]
15 subdevice: Option<u16>,
16}
17
18#[derive(Debug)]
19struct Vendor<'a> {
20 name: &'a str,
21 devices: Vec<Device<'a>>,
22}
23
24#[derive(Debug)]
25struct SubClass<'a> {
26 name: &'a str,
27 subclass: u8,
28 prog_if: Option<u8>,
29}
30
31#[derive(Debug)]
32struct Class<'a> {
33 name: &'a str,
34 subclasses: Vec<SubClass<'a>>,
35}
36
37#[derive(Debug)]
38pub struct PciDb<'a> {
39 device_db: HashMap<u16, Vendor<'a>>,
40 class_db: HashMap<u8, Class<'a>>,
41}
42
43impl<'a> PciDb<'a> {
44 fn count_tabs(line: &str) -> usize {
45 let mut tabs = 0;
46 let mut chars = line.chars();
47 while chars.next() == Some('\t') {
48 tabs += 1;
49 }
50 tabs
51 }
52
53 fn parse_devices(iter: &mut Lines<'a>) -> Result<HashMap<u16, Vendor<'a>>, Error> {
55 let mut map: HashMap<u16, Vendor<'a>> = HashMap::new();
58 let mut cached_device: u16 = 0;
59 let mut cached_vendor: Option<&mut Vendor<'a>> = None;
60 for line in iter {
61 let trimmed = line.trim();
62 let len = trimmed.len();
63 if trimmed.is_empty() || line.starts_with('#') {
65 continue;
66 }
67
68 match PciDb::count_tabs(line) {
70 0 => {
71 let vendor = u16::from_str_radix(&trimmed[0..4], 16)?;
74 let name = &trimmed[6..len];
75 map.insert(vendor, Vendor { name, devices: Vec::new() });
76 if vendor == 0xffff {
77 break;
78 }
79 cached_vendor = map.get_mut(&vendor);
80 }
81 1 => {
82 cached_device = u16::from_str_radix(&trimmed[0..4], 16)?;
85 let name = &trimmed[6..len];
86 let v = cached_vendor.unwrap();
87 v.devices.push(Device {
88 name,
89 device: cached_device,
90 subvendor: None,
91 subdevice: None,
92 });
93 cached_vendor = Some(v);
94 }
95 2 => {
96 let subvendor = u16::from_str_radix(&trimmed[0..4], 16);
99 let subdevice = u16::from_str_radix(&trimmed[5..9], 16);
100 let name = &trimmed[10..len].trim();
103
104 let v = cached_vendor.unwrap();
105 v.devices.push(Device {
106 name,
107 device: cached_device,
108 subvendor: subvendor.ok(),
109 subdevice: subdevice.ok(),
110 });
111 cached_vendor = Some(v);
112 }
113 _ => return Err(anyhow!("Invalid line in db: \"{}\"", line)),
114 }
115 }
116
117 Ok(map)
118 }
119
120 fn parse_classes(iter: &mut Lines<'a>) -> Result<HashMap<u8, Class<'a>>, Error> {
123 let mut map: HashMap<u8, Class<'a>> = HashMap::new();
124 let mut cached_subclass: u8 = 0;
125 let mut cached_class: Option<&mut Class<'a>> = None;
126 for line in iter {
127 if line.is_empty() || line.starts_with('#') {
128 continue;
129 }
130
131 let len = line.len();
132 if line.starts_with('C') {
133 let class =
134 u8::from_str_radix(&line[2..4], 16).context(format!("'{}'", &line[2..4]))?;
135 let name = &line[6..len];
136 map.insert(class, Class { name, subclasses: Vec::new() });
137 cached_class = map.get_mut(&class);
138 } else {
139 match PciDb::count_tabs(line) {
140 1 => {
141 cached_subclass = u8::from_str_radix(&line[1..3], 16)
142 .context(format!("'{}'", &line[1..3]))?;
143 let name = &line[5..len];
144 let c = cached_class.unwrap();
145 c.subclasses.push(SubClass {
146 name,
147 subclass: cached_subclass,
148 prog_if: None,
149 });
150 cached_class = Some(c);
151 }
152 2 => {
153 let prog_if = u8::from_str_radix(&line[2..4], 16)
154 .context(format!("'{}'", &line[2..4]))?;
155 let name = &line[6..len];
156 let c = cached_class.unwrap();
157 c.subclasses.push(SubClass {
158 name,
159 subclass: cached_subclass,
160 prog_if: Some(prog_if),
161 });
162 cached_class = Some(c);
163 }
164
165 _ => return Err(anyhow!("Invalid line in db: \"{}\"", line)),
166 }
167 }
168 }
169 Ok(map)
170 }
171
172 pub fn new(id_buffer: &'a str) -> Result<Self, Error> {
173 let mut iter = id_buffer.lines();
174 let device_db = PciDb::parse_devices(&mut iter)?;
175 let class_db = PciDb::parse_classes(&mut iter)?;
176 Ok(PciDb { device_db, class_db })
177 }
178
179 pub fn find_device(&self, vendor: u16, device: u16) -> Option<String> {
180 if let Some(v_entry) = self.device_db.get(&vendor) {
181 let mut name = v_entry.name.to_string();
182 for dev in &v_entry.devices {
191 if device == dev.device {
192 name.push(' ');
193 name.push_str(&dev.name);
194 break;
195 }
196 }
197 return Some(name);
198 }
199 None
200 }
201
202 pub fn find_class(&self, class: u8, subclass: u8, prog_if: Option<u8>) -> Option<String> {
203 if let Some(c_entry) = self.class_db.get(&class) {
204 let mut name = c_entry.name.to_string();
205 for sub in &c_entry.subclasses {
206 if subclass == sub.subclass {
207 if sub.prog_if == None {
208 name = sub.name.to_string();
212 } else if prog_if.is_some() && prog_if == sub.prog_if {
213 name.push_str(&format!(" ({})", sub.name));
217 }
218 }
219 }
220 return Some(name);
222 }
223 None
224 }
225}
226
227#[cfg(test)]
228mod test {
229 use super::*;
230 use lazy_static::lazy_static;
231 use std::fs;
232
233 lazy_static! {
234 static ref DB_BUF: String = fs::read_to_string("/pkg/data/lspci/pci.ids")
235 .context("Couldn't open PCI IDs file")
236 .unwrap();
237 static ref DB: PciDb<'static> =
238 PciDb::new(&DB_BUF).context("Couldnt parse database buffer").unwrap();
239 }
240
241 #[test]
242 fn vendor_without_devices() -> Result<(), Error> {
243 assert_eq!("SafeNet (wrong ID)", DB.find_device(0x0001, 0).unwrap());
244 assert_eq!("SafeNet (wrong ID)", DB.find_device(0x0001, 0xFFFF).unwrap());
246 Ok(())
247 }
248
249 #[test]
250 fn general_device_and_subdevices() -> Result<(), Error> {
251 assert_eq!("PEAK-System Technik GmbH", DB.find_device(0x001c, 0).unwrap());
252 assert_eq!(
253 "PEAK-System Technik GmbH PCAN-PCI CAN-Bus controller",
254 DB.find_device(0x001c, 0x001).unwrap()
255 );
256 Ok(())
257 }
258
259 #[test]
260 fn device_not_found() -> Result<(), Error> {
261 assert_eq!(None, DB.find_device(0x0000, 0x8139));
263 assert_eq!("LevelOne", DB.find_device(0x018a, 0x1000).unwrap());
265 assert_eq!(
267 "LevelOne FPC-0106TX misprogrammed [RTL81xx]",
268 DB.find_device(0x018a, 0x0106).unwrap(),
269 );
270 Ok(())
271 }
272
273 struct ClassTest<'a> {
274 e: &'a str,
275 c: u8,
276 s: u8,
277 p: Option<u8>,
278 }
279
280 #[test]
281 fn class_lookup() -> Result<(), Error> {
282 let tests = vec![
283 ClassTest { e: "Bridge", c: 0x06, s: 0xFF, p: None },
284 ClassTest { e: "Bridge", c: 0x06, s: 0xFF, p: Some(0xFF) },
285 ClassTest { e: "Bridge", c: 0x06, s: 0x80, p: None },
286 ClassTest { e: "Host bridge", c: 0x06, s: 0x00, p: None },
287 ClassTest { e: "ISA bridge", c: 0x06, s: 0x01, p: None },
288 ClassTest { e: "EISA bridge", c: 0x06, s: 0x02, p: None },
289 ClassTest { e: "PCI bridge", c: 0x06, s: 0x04, p: None },
290 ClassTest { e: "PCI bridge (Normal decode)", c: 0x06, s: 0x04, p: Some(0x00) },
291 ClassTest { e: "PCI bridge (Subtractive decode)", c: 0x06, s: 0x04, p: Some(0x01) },
292 ];
293
294 assert_eq!(None, DB.find_class(0xEF, 0x00, None));
295 for test in &tests {
296 assert_eq!(test.e, DB.find_class(test.c, test.s, test.p).unwrap());
297 }
298 Ok(())
299 }
300}