Skip to main content

strat9_bus_drivers/
scheme.rs

1//! Multi-driver VFS scheme served at `/bus/`.
2//!
3//! Each successfully-initialised bus driver appears as a sub-directory:
4//!
5//! ```text
6//! /bus/                     -> list of registered driver names + pci/
7//! /bus/pci/inventory        -> PCI device table
8//! /bus/pci/count            -> number of PCI devices
9//! /bus/pci/rescan           -> (write-only) refresh PCI cache
10//! /bus/pci/find/<vid>/<did> -> find devices by vendor/device
11//! /bus/pci/cfg/<b:d.f>/<off>/<w>  -> raw PCI config read
12//! /bus/<driver>/            -> driver info (compatible, errors, …)
13//! /bus/<driver>/status      -> driver status
14//! /bus/<driver>/error_count -> driver error count
15//! /bus/<driver>/reg/<hex>   -> read/write a driver register
16//! /bus/<driver>/<child>     -> child-device info (if the driver reports any)
17//! ```
18
19use alloc::{boxed::Box, collections::BTreeMap, format, string::String, vec::Vec};
20use strat9_syscall::{
21    call,
22    data::{
23        DT_DIR, DT_REG, IpcMessage, PCI_MATCH_DEVICE_ID, PCI_MATCH_VENDOR_ID, PciAddress,
24        PciDeviceInfo, PciProbeCriteria,
25    },
26    error::{EBADF, EINVAL, EIO, ENOENT, ENOSYS, ENOTDIR},
27};
28
29use crate::BusDriver;
30
31const OPCODE_OPEN: u32 = 0x01;
32const OPCODE_READ: u32 = 0x02;
33const OPCODE_WRITE: u32 = 0x03;
34const OPCODE_CLOSE: u32 = 0x04;
35const OPCODE_READDIR: u32 = 0x08;
36const REPLY_MSG_TYPE: u32 = 0x80;
37const STATUS_OK: u32 = 0;
38const FILEFLAG_DIRECTORY: u32 = 1;
39
40// === Path constants ========================================================
41
42/// Driver-specific paths (relative to the driver prefix).
43const DRV_STATUS: &str = "status";
44const DRV_ERROR_COUNT: &str = "error_count";
45const DRV_SUSPEND: &str = "suspend";
46const DRV_RESUME: &str = "resume";
47const DRV_REG_PREFIX: &str = "reg/";
48
49/// Top-level paths.
50const PCI_PREFIX: &str = "pci";
51
52// === Handle ================================================================
53
54enum HandleKind {
55    /// Root : listing drivers + pci.
56    Root,
57    /// PCI sub-tree.
58    Pci(String),
59    /// A specific driver, with an optional sub-path.
60    Driver { driver_idx: usize, sub_path: String },
61}
62
63struct OpenHandle {
64    kind: HandleKind,
65}
66
67// === Server ================================================================
68
69pub struct BusSchemeServer {
70    drivers: Vec<(String, Box<dyn BusDriver>)>,
71    port_handle: u64,
72    handles: BTreeMap<u64, OpenHandle>,
73    next_id: u64,
74    pci_cache: Vec<PciDeviceInfo>,
75}
76
77impl BusSchemeServer {
78    /// Creates a new instance.
79    pub fn new(drivers: Vec<(String, Box<dyn BusDriver>)>, port_handle: u64) -> Self {
80        Self {
81            drivers,
82            port_handle,
83            handles: BTreeMap::new(),
84            next_id: 1,
85            pci_cache: Vec::new(),
86        }
87    }
88
89    // === PCI cache (shared) =================================================
90
91    /// Performs the refresh pci cache operation.
92    ///
93    /// Returns `Ok(count)` with the number of devices found, or `Err(())` if the
94    /// underlying `pci_enum` syscall failed (cache remains unchanged).
95    pub fn refresh_pci_cache(&mut self) -> Result<usize, ()> {
96        let criteria = PciProbeCriteria {
97            match_flags: 0,
98            vendor_id: 0,
99            device_id: 0,
100            class_code: 0,
101            subclass: 0,
102            prog_if: 0,
103            _reserved: 0,
104        };
105        let mut buf = alloc::vec![PciDeviceInfo {
106            address: PciAddress {
107                bus: 0,
108                device: 0,
109                function: 0,
110                _reserved: 0,
111            },
112            vendor_id: 0,
113            device_id: 0,
114            class_code: 0,
115            subclass: 0,
116            prog_if: 0,
117            revision: 0,
118            header_type: 0,
119            interrupt_line: 0,
120            interrupt_pin: 0,
121            _reserved: 0,
122        }; 256];
123        match call::pci_enum(&criteria, &mut buf) {
124            Ok(n) => {
125                let count = n.min(buf.len());
126                self.pci_cache.clear();
127                self.pci_cache.extend_from_slice(&buf[..count]);
128                Ok(self.pci_cache.len())
129            }
130            Err(_) => Err(()),
131        }
132    }
133
134    // === Reply helpers =====================================================
135
136    fn ok_reply(sender: u64) -> IpcMessage {
137        let mut reply = IpcMessage::new(REPLY_MSG_TYPE);
138        reply.sender = sender;
139        reply.payload[0..4].copy_from_slice(&STATUS_OK.to_le_bytes());
140        reply
141    }
142
143    fn err_reply(sender: u64, code: usize) -> IpcMessage {
144        let mut reply = IpcMessage::new(REPLY_MSG_TYPE);
145        reply.sender = sender;
146        reply.payload[0..4].copy_from_slice(&(code as u32).to_le_bytes());
147        reply
148    }
149
150    fn alloc_id(&mut self) -> u64 {
151        let id = self.next_id;
152        self.next_id = self.next_id.wrapping_add(1).max(1);
153        id
154    }
155
156    // === Path resolution ===================================================
157
158    /// Split a normalised path into a driver index + sub-path, or detect PCI / root.
159    fn resolve_driver_path<'a>(&self, path: &'a str) -> Option<(usize, &'a str)> {
160        let (first, rest) = path.split_once('/').unwrap_or((path, ""));
161        for (i, (name, _)) in self.drivers.iter().enumerate() {
162            if first == name.as_str() {
163                return Some((i, rest));
164            }
165        }
166        None
167    }
168
169    fn is_pci_path(path: &str) -> bool {
170        path == PCI_PREFIX || path.starts_with("pci/")
171    }
172
173    // === Path existence ===================================================
174
175    fn path_exists(&self, path: &str) -> bool {
176        // Root is always valid
177        if path.is_empty() {
178            return true;
179        }
180        // PCI paths
181        if Self::is_pci_path(path) {
182            return true;
183        }
184        // Driver paths
185        if let Some((idx, sub)) = self.resolve_driver_path(path) {
186            if sub.is_empty()
187                || sub == DRV_STATUS
188                || sub == DRV_ERROR_COUNT
189                || sub == DRV_SUSPEND
190                || sub == DRV_RESUME
191            {
192                return true;
193            }
194            if sub.starts_with(DRV_REG_PREFIX) {
195                return Self::parse_reg_offset(sub).is_some();
196            }
197            // Child device check
198            if self.drivers[idx].1.children().iter().any(|c| c.name == sub) {
199                return true;
200            }
201            return false;
202        }
203        false
204    }
205
206    fn parse_reg_offset(path: &str) -> Option<usize> {
207        let reg_str = path.strip_prefix(DRV_REG_PREFIX)?;
208        if reg_str.is_empty() {
209            return None;
210        }
211        usize::from_str_radix(reg_str.trim_start_matches("0x"), 16).ok()
212    }
213
214    // === Open ================================================================
215
216    fn handle_open(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
217        let path_len = u16::from_le_bytes([payload[4], payload[5]]) as usize;
218        if path_len > 42 {
219            return Self::err_reply(sender, EINVAL);
220        }
221        let path_bytes = &payload[6..6 + path_len];
222        let raw_path = match core::str::from_utf8(path_bytes) {
223            Ok(s) => s,
224            Err(_) => return Self::err_reply(sender, EINVAL),
225        };
226        let path = Self::normalize_path(raw_path);
227        if !self.path_exists(&path) {
228            return Self::err_reply(sender, ENOENT);
229        }
230
231        let file_id = self.alloc_id();
232        let is_dir;
233        let kind = if path.is_empty() {
234            is_dir = true;
235            HandleKind::Root
236        } else if Self::is_pci_path(&path) {
237            is_dir = path.is_empty() || path == "pci" || path == "pci/find" || path == "pci/cfg";
238            HandleKind::Pci(path)
239        } else if let Some((idx, sub)) = self.resolve_driver_path(&path) {
240            is_dir = sub.is_empty();
241            HandleKind::Driver {
242                driver_idx: idx,
243                sub_path: String::from(sub),
244            }
245        } else {
246            return Self::err_reply(sender, ENOENT);
247        };
248
249        self.handles.insert(file_id, OpenHandle { kind });
250
251        let mut reply = Self::ok_reply(sender);
252        reply.payload[4..12].copy_from_slice(&file_id.to_le_bytes());
253        reply.payload[12..20].copy_from_slice(&0u64.to_le_bytes());
254        reply.payload[20..24]
255            .copy_from_slice(&(if is_dir { FILEFLAG_DIRECTORY } else { 0 }).to_le_bytes());
256        reply
257    }
258
259    // === Read ================================================================
260
261    fn handle_read(&self, sender: u64, payload: &[u8]) -> IpcMessage {
262        let file_id = u64::from_le_bytes(payload[0..8].try_into().unwrap());
263        let offset = u64::from_le_bytes(payload[8..16].try_into().unwrap());
264
265        let handle = match self.handles.get(&file_id) {
266            Some(h) => h,
267            None => return Self::err_reply(sender, EBADF),
268        };
269
270        let content = self.generate_read_content(&handle.kind, offset as usize);
271        let n = content.len().min(40);
272
273        let mut reply = Self::ok_reply(sender);
274        reply.payload[4..8].copy_from_slice(&(n as u32).to_le_bytes());
275        reply.payload[8..8 + n].copy_from_slice(&content[..n]);
276        reply
277    }
278
279    fn generate_read_content(&self, kind: &HandleKind, offset: usize) -> Vec<u8> {
280        let data = match kind {
281            HandleKind::Root => {
282                let mut s = format!("drivers registered: {}\n", self.drivers.len());
283                for (name, d) in &self.drivers {
284                    s.push_str(&format!("  {} (compat: {:?})\n", name, d.compatible()));
285                }
286                s.into_bytes()
287            }
288            HandleKind::Pci(path) => self.read_pci_content(path),
289            HandleKind::Driver {
290                driver_idx,
291                sub_path,
292            } => {
293                let driver = &self.drivers[*driver_idx].1;
294                let name = &self.drivers[*driver_idx].0;
295                self.read_driver_content(driver, name, sub_path)
296            }
297        };
298
299        if offset >= data.len() {
300            Vec::new()
301        } else {
302            data[offset..].to_vec()
303        }
304    }
305
306    fn read_pci_content(&self, path: &str) -> Vec<u8> {
307        match path {
308            "" | PCI_PREFIX => b"inventory\ncount\nrescan\nfind\ncfg\n".to_vec(),
309            "pci/find" => b"usage: /bus/pci/find/<vendor>/<device>\n".to_vec(),
310            "pci/cfg" => b"usage: /bus/pci/cfg/<bb:dd.f>/<offset>/<width>\n".to_vec(),
311            "pci/inventory" => self.render_inventory(),
312            "pci/count" => format!("{}\n", self.pci_cache.len()).into_bytes(),
313            path if path.starts_with("pci/find/") => {
314                let Some((vendor_id, device_id)) = Self::parse_find_path(path) else {
315                    return b"invalid path\n".to_vec();
316                };
317                let criteria = PciProbeCriteria {
318                    match_flags: PCI_MATCH_VENDOR_ID | PCI_MATCH_DEVICE_ID,
319                    vendor_id,
320                    device_id,
321                    class_code: 0,
322                    subclass: 0,
323                    prog_if: 0,
324                    _reserved: 0,
325                };
326                let mut matches = alloc::vec![PciDeviceInfo {
327                    address: PciAddress {
328                        bus: 0,
329                        device: 0,
330                        function: 0,
331                        _reserved: 0,
332                    },
333                    vendor_id: 0,
334                    device_id: 0,
335                    class_code: 0,
336                    subclass: 0,
337                    prog_if: 0,
338                    revision: 0,
339                    header_type: 0,
340                    interrupt_line: 0,
341                    interrupt_pin: 0,
342                    _reserved: 0,
343                }; 64];
344                match call::pci_enum(&criteria, &mut matches) {
345                    Ok(n) => {
346                        let mut out = alloc::vec::Vec::new();
347                        for d in matches.into_iter().take(n) {
348                            let line = format!(
349                                "{:02x}:{:02x}.{} {:04x}:{:04x}\n",
350                                d.address.bus,
351                                d.address.device,
352                                d.address.function,
353                                d.vendor_id,
354                                d.device_id
355                            );
356                            out.extend_from_slice(line.as_bytes());
357                        }
358                        if out.is_empty() {
359                            b"none\n".to_vec()
360                        } else {
361                            out
362                        }
363                    }
364                    Err(_) => b"error\n".to_vec(),
365                }
366            }
367            path if path.starts_with("pci/cfg/") => {
368                let Some((addr, reg, width)) = Self::parse_cfg_path(path) else {
369                    return b"invalid path\n".to_vec();
370                };
371                match call::pci_cfg_read(&addr, reg, width) {
372                    Ok(v) => format!("0x{:08x}\n", v as u32).into_bytes(),
373                    Err(_) => b"error\n".to_vec(),
374                }
375            }
376            _ => b"unknown\n".to_vec(),
377        }
378    }
379
380    fn read_driver_content(
381        &self,
382        driver: &Box<dyn BusDriver>,
383        name: &str,
384        sub_path: &str,
385    ) -> Vec<u8> {
386        match sub_path {
387            "" => {
388                let mut s = format!("driver: {}\n", name);
389                for c in driver.compatible() {
390                    s.push_str(&format!("compatible: {}\n", c));
391                }
392                s.push_str(&format!("errors: {}\n", driver.error_count()));
393                s.into_bytes()
394            }
395            DRV_STATUS => {
396                format!("driver: {}\nerrors: {}\n", name, driver.error_count()).into_bytes()
397            }
398            DRV_ERROR_COUNT => format!("{}\n", driver.error_count()).into_bytes(),
399            s if s.starts_with(DRV_REG_PREFIX) => {
400                if let Some(reg_offset) = Self::parse_reg_offset(s) {
401                    match driver.read_reg(reg_offset) {
402                        Ok(val) => format!("0x{:08x}\n", val).into_bytes(),
403                        Err(_) => b"error\n".to_vec(),
404                    }
405                } else {
406                    b"invalid register\n".to_vec()
407                }
408            }
409            child_name => {
410                // Child device info
411                if let Some(child) = driver.children().iter().find(|c| c.name == child_name) {
412                    format!(
413                        "name: {}\nbase: 0x{:x}\nsize: {}\n",
414                        child.name, child.base_addr, child.size
415                    )
416                    .into_bytes()
417                } else {
418                    b"unknown\n".to_vec()
419                }
420            }
421        }
422    }
423
424    // === Write ================================================================
425
426    fn handle_write(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
427        let file_id = u64::from_le_bytes(payload[0..8].try_into().unwrap());
428        let len = u16::from_le_bytes([payload[16], payload[17]]) as usize;
429
430        let kind = match self.handles.get(&file_id) {
431            Some(h) => &h.kind,
432            None => return Self::err_reply(sender, EBADF),
433        };
434
435        if len > 30 {
436            return Self::err_reply(sender, EINVAL);
437        }
438
439        match kind {
440            HandleKind::Pci(path) if *path == "pci/rescan" => {
441                if self.refresh_pci_cache().is_err() {
442                    return Self::err_reply(sender, EIO);
443                }
444            }
445            HandleKind::Pci(path) if path.starts_with("pci/cfg/") => {
446                let Some((addr, reg, width)) = Self::parse_cfg_path(path) else {
447                    return Self::err_reply(sender, EINVAL);
448                };
449                if len < 4 {
450                    return Self::err_reply(sender, EINVAL);
451                }
452                let val = u32::from_le_bytes([payload[18], payload[19], payload[20], payload[21]]);
453                if call::pci_cfg_write(&addr, reg, width, val).is_err() {
454                    return Self::err_reply(sender, EINVAL);
455                }
456            }
457            HandleKind::Driver {
458                driver_idx,
459                sub_path,
460            } if sub_path == DRV_SUSPEND => {
461                if self.drivers[*driver_idx].1.suspend().is_err() {
462                    return Self::err_reply(sender, EIO);
463                }
464            }
465            HandleKind::Driver {
466                driver_idx,
467                sub_path,
468            } if sub_path == DRV_RESUME => {
469                if self.drivers[*driver_idx].1.resume().is_err() {
470                    return Self::err_reply(sender, EIO);
471                }
472            }
473            HandleKind::Driver {
474                driver_idx,
475                sub_path,
476            } if sub_path.starts_with(DRV_REG_PREFIX) => {
477                let Some(reg_offset) = Self::parse_reg_offset(sub_path) else {
478                    return Self::err_reply(sender, EINVAL);
479                };
480                if len < 4 {
481                    return Self::err_reply(sender, EINVAL);
482                }
483                let val = u32::from_le_bytes([payload[18], payload[19], payload[20], payload[21]]);
484                if self.drivers[*driver_idx]
485                    .1
486                    .write_reg(reg_offset, val)
487                    .is_err()
488                {
489                    return Self::err_reply(sender, EINVAL);
490                }
491            }
492            _ => return Self::err_reply(sender, ENOSYS),
493        }
494
495        let mut reply = Self::ok_reply(sender);
496        reply.payload[4..8].copy_from_slice(&(len as u32).to_le_bytes());
497        reply
498    }
499
500    // === Close ================================================================
501
502    fn handle_close(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
503        let file_id = u64::from_le_bytes(payload[0..8].try_into().unwrap());
504        if self.handles.remove(&file_id).is_some() {
505            Self::ok_reply(sender)
506        } else {
507            Self::err_reply(sender, EBADF)
508        }
509    }
510
511    // === Read dir ================================================================
512
513    fn handle_readdir(&self, sender: u64, payload: &[u8]) -> IpcMessage {
514        let file_id = u64::from_le_bytes(payload[0..8].try_into().unwrap());
515        let handle = match self.handles.get(&file_id) {
516            Some(h) => h,
517            None => return Self::err_reply(sender, EBADF),
518        };
519
520        let entries: Vec<(u64, u8, String)> = match &handle.kind {
521            HandleKind::Root => {
522                let mut e = alloc::vec![(1u64, DT_DIR, String::from(PCI_PREFIX))];
523                for (i, (name, _)) in self.drivers.iter().enumerate() {
524                    e.push(((i + 2) as u64, DT_DIR, name.clone()));
525                }
526                e
527            }
528            HandleKind::Pci(path) => match path.as_str() {
529                "" | PCI_PREFIX => alloc::vec![
530                    (4u64, DT_REG, String::from("inventory")),
531                    (5u64, DT_REG, String::from("count")),
532                    (6u64, DT_REG, String::from("rescan")),
533                    (7u64, DT_DIR, String::from("find")),
534                    (8u64, DT_DIR, String::from("cfg")),
535                ],
536                "pci/find" | "pci/cfg" => alloc::vec![],
537                _ => return Self::err_reply(sender, ENOTDIR),
538            },
539            HandleKind::Driver {
540                driver_idx,
541                sub_path,
542            } if sub_path.is_empty() => {
543                let driver = &self.drivers[*driver_idx].1;
544                let mut e = alloc::vec![
545                    (1u64, DT_REG, String::from(DRV_STATUS)),
546                    (2u64, DT_REG, String::from(DRV_ERROR_COUNT)),
547                    (3u64, DT_REG, String::from(DRV_SUSPEND)),
548                    (4u64, DT_REG, String::from(DRV_RESUME)),
549                ];
550                for (i, child) in driver.children().iter().enumerate() {
551                    e.push(((i + 5) as u64, DT_REG, child.name.clone()));
552                }
553                e
554            }
555            _ => return Self::err_reply(sender, ENOTDIR),
556        };
557
558        let mut reply = Self::ok_reply(sender);
559        let cursor = u16::from_le_bytes([payload[8], payload[9]]) as usize;
560        if cursor >= entries.len() && !entries.is_empty() {
561            reply.payload[4..6].copy_from_slice(&u16::MAX.to_le_bytes());
562            reply.payload[6] = 0;
563            reply.payload[7] = 0;
564            return reply;
565        }
566
567        let mut offset = 8usize;
568        let mut count = 0u8;
569        let mut next_cursor = u16::MAX;
570        let mut index = cursor;
571
572        for (ino, file_type, name) in &entries[cursor..] {
573            let name_bytes = name.as_bytes();
574            let entry_size = 10 + name_bytes.len();
575            if offset + entry_size > 48 {
576                next_cursor = index.min(u16::MAX as usize) as u16;
577                break;
578            }
579            reply.payload[offset..offset + 8].copy_from_slice(&ino.to_le_bytes());
580            reply.payload[offset + 8] = *file_type;
581            reply.payload[offset + 9] = name_bytes.len() as u8;
582            let end = offset + 10 + name_bytes.len();
583            reply.payload[offset + 10..end].copy_from_slice(name_bytes);
584            offset = end;
585            count += 1;
586            index += 1;
587        }
588
589        reply.payload[4..6].copy_from_slice(&next_cursor.to_le_bytes());
590        reply.payload[6] = count;
591        reply.payload[7] = (offset - 8) as u8;
592        reply
593    }
594
595    // === Serve ================================================================
596
597    /// Performs the serve operation.
598    pub fn serve(&mut self) -> ! {
599        loop {
600            let mut msg = IpcMessage::new(0);
601            if call::ipc_recv(self.port_handle as usize, &mut msg).is_err() {
602                let _ = call::sched_yield();
603                continue;
604            }
605
606            let reply = match msg.msg_type {
607                OPCODE_OPEN => self.handle_open(msg.sender, &msg.payload),
608                OPCODE_READ => self.handle_read(msg.sender, &msg.payload),
609                OPCODE_WRITE => self.handle_write(msg.sender, &msg.payload),
610                OPCODE_CLOSE => self.handle_close(msg.sender, &msg.payload),
611                OPCODE_READDIR => self.handle_readdir(msg.sender, &msg.payload),
612                _ => Self::err_reply(msg.sender, ENOSYS),
613            };
614            let _ = call::ipc_reply(&reply);
615        }
616    }
617
618    // === Static helpers ========================================================
619
620    fn normalize_path(path: &str) -> String {
621        if path.is_empty() || path == "/" {
622            return String::new();
623        }
624        let trimmed = path.trim_matches('/');
625        String::from(trimmed)
626    }
627
628    fn parse_hex_u8(s: &str) -> Option<u8> {
629        u8::from_str_radix(s.trim_start_matches("0x"), 16).ok()
630    }
631
632    fn parse_hex_u16(s: &str) -> Option<u16> {
633        u16::from_str_radix(s.trim_start_matches("0x"), 16).ok()
634    }
635
636    fn parse_pci_bdf(s: &str) -> Option<PciAddress> {
637        let (bus_s, rest) = s.split_once(':')?;
638        let (dev_s, fun_s) = rest.split_once('.')?;
639        let bus = Self::parse_hex_u8(bus_s)?;
640        let device = Self::parse_hex_u8(dev_s)?;
641        let function = Self::parse_hex_u8(fun_s)?;
642        if device > 31 || function > 7 {
643            return None;
644        }
645        Some(PciAddress {
646            bus,
647            device,
648            function,
649            _reserved: 0,
650        })
651    }
652
653    fn parse_cfg_path(path: &str) -> Option<(PciAddress, u8, u8)> {
654        let mut parts = path.strip_prefix("pci/cfg/")?.split('/');
655        let bdf = parts.next()?;
656        let off = parts.next()?;
657        let width = parts.next()?;
658        if parts.next().is_some() {
659            return None;
660        }
661        let addr = Self::parse_pci_bdf(bdf)?;
662        let offset = Self::parse_hex_u8(off)?;
663        let width = width.parse::<u8>().ok()?;
664        if !matches!(width, 1 | 2 | 4) {
665            return None;
666        }
667        Some((addr, offset, width))
668    }
669
670    fn parse_find_path(path: &str) -> Option<(u16, u16)> {
671        let mut parts = path.strip_prefix("pci/find/")?.split('/');
672        let ven = Self::parse_hex_u16(parts.next()?)?;
673        let dev = Self::parse_hex_u16(parts.next()?)?;
674        if parts.next().is_some() {
675            return None;
676        }
677        Some((ven, dev))
678    }
679
680    fn render_inventory(&self) -> Vec<u8> {
681        let mut out = alloc::vec::Vec::new();
682        out.extend_from_slice(b"bus:dev.fn vendor:device class:sub prog_if rev irq\n");
683        for d in &self.pci_cache {
684            let line = format!(
685                "{:02x}:{:02x}.{} {:04x}:{:04x} {:02x}:{:02x} {:02x} {:02x} {}\n",
686                d.address.bus,
687                d.address.device,
688                d.address.function,
689                d.vendor_id,
690                d.device_id,
691                d.class_code,
692                d.subclass,
693                d.prog_if,
694                d.revision,
695                d.interrupt_line
696            );
697            out.extend_from_slice(line.as_bytes());
698        }
699        out
700    }
701}