Skip to main content

strat9_kernel/arch/x86_64/
pci.rs

1//! PCI Configuration Space Access
2//!
3//! Provides functions to scan the PCI bus and read/write configuration registers.
4//! Used to discover VirtIO and other PCI devices.
5//!
6//! Reference: PCI Local Bus Specification 3.0
7
8use super::io::{inl, outl};
9use crate::sync::SpinLock;
10use alloc::vec::Vec;
11use core::fmt;
12
13/// PCI Configuration Address Port
14const CONFIG_ADDRESS: u16 = 0xCF8;
15/// PCI Configuration Data Port
16const CONFIG_DATA: u16 = 0xCFC;
17
18/// Global lock for PCI configuration space I/O.
19///
20/// CONFIG_ADDRESS and CONFIG_DATA form a two-step transaction that must be
21/// atomic w.r.t. other CPUs. Every config read/write must hold this lock.
22static PCI_IO_LOCK: SpinLock<()> = SpinLock::new(());
23
24#[derive(Clone, Copy)]
25struct PciLineQuirk {
26    vendor_id: u16,
27    device_id: u16,
28}
29
30const PCI_IRQ_LINE_ZERO_IF_FF: &[PciLineQuirk] = &[
31    PciLineQuirk {
32        vendor_id: vendor::INTEL,
33        device_id: intel_eth::E1000_82540EM,
34    },
35    PciLineQuirk {
36        vendor_id: vendor::INTEL,
37        device_id: intel_eth::E1000_82545EM,
38    },
39    PciLineQuirk {
40        vendor_id: vendor::INTEL,
41        device_id: intel_eth::E1000E_82574L,
42    },
43    PciLineQuirk {
44        vendor_id: vendor::VIRTIO,
45        device_id: device::VIRTIO_NET,
46    },
47    PciLineQuirk {
48        vendor_id: vendor::VIRTIO,
49        device_id: device::VIRTIO_BLOCK,
50    },
51];
52
53/// PCI Vendor IDs
54pub mod vendor {
55    pub const VIRTIO: u16 = 0x1AF4;
56    pub const QEMU: u16 = 0x1234;
57    pub const INTEL: u16 = 0x8086;
58    pub const AMD: u16 = 0x1022;
59}
60
61/// PCI Device IDs (VirtIO legacy)
62pub mod device {
63    pub const VIRTIO_NET: u16 = 0x1000;
64    pub const VIRTIO_BLOCK: u16 = 0x1001;
65    pub const VIRTIO_CONSOLE: u16 = 0x1003;
66    pub const VIRTIO_RNG: u16 = 0x1005;
67    pub const VIRTIO_GPU: u16 = 0x1050;
68    pub const VIRTIO_INPUT: u16 = 0x1052;
69}
70
71/// PCI base class codes
72pub mod class {
73    pub const MASS_STORAGE: u8 = 0x01;
74    pub const NETWORK: u8 = 0x02;
75}
76
77/// PCI subclasses for mass storage controllers
78pub mod storage_subclass {
79    pub const SCSI: u8 = 0x00;
80    pub const IDE: u8 = 0x01;
81    pub const FLOPPY: u8 = 0x02;
82    pub const IPI: u8 = 0x03;
83    pub const RAID: u8 = 0x04;
84    pub const ATA: u8 = 0x05;
85    pub const SATA: u8 = 0x06;
86    pub const SAS: u8 = 0x07;
87    pub const NVM: u8 = 0x08;
88    pub const OTHER: u8 = 0x80;
89}
90
91/// Programming interface codes for mass-storage SATA controllers
92pub mod sata_progif {
93    /// AHCI 1.0 (Advanced Host Controller Interface) — the standard modern mode
94    pub const AHCI: u8 = 0x01;
95    /// Vendor-specific / legacy IDE emulation
96    pub const VENDOR: u8 = 0x00;
97}
98
99/// PCI subclasses for network controllers
100pub mod net_subclass {
101    pub const ETHERNET: u8 = 0x00;
102    pub const OTHER: u8 = 0x80;
103}
104
105/// Intel Ethernet device IDs
106pub mod intel_eth {
107    pub const E1000_82540EM: u16 = 0x100E; // QEMU default e1000
108    pub const E1000_82545EM: u16 = 0x100F;
109    pub const E1000E_82574L: u16 = 0x10D3; // QEMU e1000e
110    pub const I210_AT: u16 = 0x1533;
111    pub const I350_AM2: u16 = 0x1521;
112    pub const I350_AM4: u16 = 0x1523;
113    pub const I217_LM: u16 = 0x153A;
114    pub const I211_AT: u16 = 0x1539;
115    pub const I219_LM: u16 = 0x15F9;
116    pub const I219_V: u16 = 0x15FA;
117    pub const I225_LM: u16 = 0x15F2;
118    pub const I225_V: u16 = 0x15F3;
119    pub const I226_LM: u16 = 0x125B;
120    pub const I226_V: u16 = 0x125C;
121}
122
123/// PCI configuration register offsets
124pub mod config {
125    pub const VENDOR_ID: u8 = 0x00;
126    pub const DEVICE_ID: u8 = 0x02;
127    pub const COMMAND: u8 = 0x04;
128    pub const STATUS: u8 = 0x06;
129    pub const REVISION_ID: u8 = 0x08;
130    pub const PROG_IF: u8 = 0x09;
131    pub const SUBCLASS: u8 = 0x0A;
132    pub const CLASS_CODE: u8 = 0x0B;
133    pub const CACHE_LINE_SIZE: u8 = 0x0C;
134    pub const LATENCY_TIMER: u8 = 0x0D;
135    pub const HEADER_TYPE: u8 = 0x0E;
136    pub const BIST: u8 = 0x0F;
137    pub const BAR0: u8 = 0x10;
138    pub const BAR1: u8 = 0x14;
139    pub const BAR2: u8 = 0x18;
140    pub const BAR3: u8 = 0x1C;
141    pub const BAR4: u8 = 0x20;
142    pub const BAR5: u8 = 0x24;
143    pub const CARDBUS_CIS: u8 = 0x28;
144    pub const SUBSYSTEM_VENDOR_ID: u8 = 0x2C;
145    pub const SUBSYSTEM_ID: u8 = 0x2E;
146    pub const ROM_BAR: u8 = 0x30;
147    pub const CAPABILITIES: u8 = 0x34;
148    pub const INTERRUPT_LINE: u8 = 0x3C;
149    pub const INTERRUPT_PIN: u8 = 0x3D;
150    pub const MIN_GNT: u8 = 0x3E;
151    pub const MAX_LAT: u8 = 0x3F;
152}
153
154/// PCI command register bits
155pub mod command {
156    pub const IO_SPACE: u16 = 1 << 0;
157    pub const MEMORY_SPACE: u16 = 1 << 1;
158    pub const BUS_MASTER: u16 = 1 << 2;
159    pub const SPECIAL_CYCLES: u16 = 1 << 3;
160    pub const MWI_ENABLE: u16 = 1 << 4;
161    pub const VGA_PALETTE_SNOOP: u16 = 1 << 5;
162    pub const PARITY_ERROR_RESPONSE: u16 = 1 << 6;
163    pub const STEPPING_CONTROL: u16 = 1 << 7;
164    pub const SERR_ENABLE: u16 = 1 << 8;
165    pub const FAST_BACK_TO_BACK: u16 = 1 << 9;
166    pub const INTERRUPT_DISABLE: u16 = 1 << 10;
167}
168
169/// Base Address Register (BAR) types
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum Bar {
172    Io { port: u16 },
173    Memory32 { addr: u32, prefetchable: bool },
174    Memory64 { addr: u64, prefetchable: bool },
175}
176
177/// A PCI device location
178#[derive(Clone, Copy, PartialEq, Eq)]
179pub struct PciAddress {
180    pub bus: u8,
181    pub device: u8,
182    pub function: u8,
183}
184
185impl fmt::Debug for PciAddress {
186    /// Performs the fmt operation.
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        write!(f, "{:02x}:{:02x}.{}", self.bus, self.device, self.function)
189    }
190}
191
192impl PciAddress {
193    /// Create a new PCI address
194    pub const fn new(bus: u8, device: u8, function: u8) -> Self {
195        Self {
196            bus,
197            device,
198            function,
199        }
200    }
201
202    /// Convert to configuration address format
203    fn config_address(&self, offset: u8) -> u32 {
204        let bus = self.bus as u32;
205        let device = (self.device as u32) & 0x1F;
206        let function = (self.function as u32) & 0x07;
207        let offset = (offset as u32) & 0xFC;
208
209        0x8000_0000 | (bus << 16) | (device << 11) | (function << 8) | offset
210    }
211}
212
213/// PCI device information
214#[derive(Clone, Copy)]
215pub struct PciDevice {
216    pub address: PciAddress,
217    pub vendor_id: u16,
218    pub device_id: u16,
219    pub class_code: u8,
220    pub subclass: u8,
221    pub prog_if: u8,
222    pub revision: u8,
223    pub header_type: u8,
224    pub interrupt_line: u8,
225    pub interrupt_pin: u8,
226}
227
228impl fmt::Debug for PciDevice {
229    /// Performs the fmt operation.
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        write!(
232            f,
233            "PciDevice({:?} ID {:04x}:{:04x} Class {:02x}:{:02x})",
234            self.address, self.vendor_id, self.device_id, self.class_code, self.subclass
235        )
236    }
237}
238
239impl PciDevice {
240    /// Read a configuration register (8-bit)
241    pub fn read_config_u8(&self, offset: u8) -> u8 {
242        let addr = self.address.config_address(offset & !0x03);
243        let shift = (offset & 0x03) * 8;
244
245        let _lock = PCI_IO_LOCK.lock();
246        unsafe {
247            outl(CONFIG_ADDRESS, addr);
248            ((inl(CONFIG_DATA) >> shift) & 0xFF) as u8
249        }
250    }
251
252    /// Read a configuration register (16-bit)
253    pub fn read_config_u16(&self, offset: u8) -> u16 {
254        let addr = self.address.config_address(offset & !0x03);
255        let shift = (offset & 0x02) * 8;
256
257        let _lock = PCI_IO_LOCK.lock();
258        unsafe {
259            outl(CONFIG_ADDRESS, addr);
260            ((inl(CONFIG_DATA) >> shift) & 0xFFFF) as u16
261        }
262    }
263
264    /// Read a configuration register (32-bit)
265    pub fn read_config_u32(&self, offset: u8) -> u32 {
266        let addr = self.address.config_address(offset);
267
268        let _lock = PCI_IO_LOCK.lock();
269        unsafe {
270            outl(CONFIG_ADDRESS, addr);
271            inl(CONFIG_DATA)
272        }
273    }
274
275    /// Write to a configuration register (8-bit)
276    pub fn write_config_u8(&self, offset: u8, value: u8) {
277        let addr = self.address.config_address(offset & !0x03);
278        let shift = (offset & 0x03) * 8;
279
280        let _lock = PCI_IO_LOCK.lock();
281        unsafe {
282            outl(CONFIG_ADDRESS, addr);
283            let old = inl(CONFIG_DATA);
284            let mask = !(0xFF << shift);
285            let new = (old & mask) | ((value as u32) << shift);
286            outl(CONFIG_ADDRESS, addr);
287            outl(CONFIG_DATA, new);
288        }
289    }
290
291    /// Write to a configuration register (16-bit)
292    pub fn write_config_u16(&self, offset: u8, value: u16) {
293        let addr = self.address.config_address(offset & !0x03);
294        let shift = (offset & 0x02) * 8;
295
296        let _lock = PCI_IO_LOCK.lock();
297        unsafe {
298            outl(CONFIG_ADDRESS, addr);
299            let old = inl(CONFIG_DATA);
300            let mask = !(0xFFFF << shift);
301            let new = (old & mask) | ((value as u32) << shift);
302            outl(CONFIG_ADDRESS, addr);
303            outl(CONFIG_DATA, new);
304        }
305    }
306
307    /// Write to a configuration register (32-bit)
308    pub fn write_config_u32(&self, offset: u8, value: u32) {
309        let addr = self.address.config_address(offset);
310
311        let _lock = PCI_IO_LOCK.lock();
312        unsafe {
313            outl(CONFIG_ADDRESS, addr);
314            outl(CONFIG_DATA, value);
315        }
316    }
317
318    /// Read a Base Address Register (BAR)
319    pub fn read_bar(&self, bar_index: u8) -> Option<Bar> {
320        if bar_index > 5 {
321            return None;
322        }
323
324        let offset = config::BAR0 + (bar_index * 4);
325        let bar_low = self.read_config_u32(offset);
326
327        if bar_low == 0 {
328            return None;
329        }
330
331        // Check if it's an I/O BAR (bit 0 set)
332        if bar_low & 0x1 != 0 {
333            // I/O space BAR
334            // Bits 0-1 are reserved/type, address starts at bit 2
335            let port = (bar_low & 0xFFFF_FFFC) as u16;
336            Some(Bar::Io { port })
337        } else {
338            // Memory space BAR
339            let bar_type = (bar_low >> 1) & 0x3;
340            let prefetchable = (bar_low >> 3) & 0x1 != 0;
341
342            match bar_type {
343                0 => {
344                    // 32-bit memory space
345                    let addr = bar_low & 0xFFFF_FFF0;
346                    Some(Bar::Memory32 { addr, prefetchable })
347                }
348                2 => {
349                    // 64-bit memory space
350                    if bar_index >= 5 {
351                        return None; // Can't read next BAR as high part
352                    }
353                    let bar_high = self.read_config_u32(offset + 4);
354                    let addr = ((bar_high as u64) << 32) | ((bar_low & 0xFFFF_FFF0) as u64);
355                    Some(Bar::Memory64 { addr, prefetchable })
356                }
357                _ => None, // Reserved types
358            }
359        }
360    }
361
362    /// Get the raw BAR value (for legacy compatibility)
363    pub fn read_bar_raw(&self, bar_index: u8) -> Option<u64> {
364        match self.read_bar(bar_index) {
365            Some(Bar::Io { port }) => Some(port as u64),
366            Some(Bar::Memory32 { addr, .. }) => Some(addr as u64),
367            Some(Bar::Memory64 { addr, .. }) => Some(addr),
368            None => None,
369        }
370    }
371
372    /// Enable bus mastering for this device
373    pub fn enable_bus_master(&self) {
374        let mut cmd = self.read_config_u16(config::COMMAND);
375        cmd |= command::BUS_MASTER;
376        self.write_config_u16(config::COMMAND, cmd);
377    }
378
379    /// Enable memory space access for this device
380    pub fn enable_memory_space(&self) {
381        let mut cmd = self.read_config_u16(config::COMMAND);
382        cmd |= command::MEMORY_SPACE;
383        self.write_config_u16(config::COMMAND, cmd);
384    }
385
386    /// Enable I/O space access for this device
387    pub fn enable_io_space(&self) {
388        let mut cmd = self.read_config_u16(config::COMMAND);
389        cmd |= command::IO_SPACE;
390        self.write_config_u16(config::COMMAND, cmd);
391    }
392}
393
394/// Iterator for scanning PCI bus
395pub struct PciScanner {
396    bus_queue: [u8; 256],
397    queue_head: usize,
398    queue_tail: usize,
399    seen_buses: [bool; 256],
400    device: u8,
401    function: u8,
402}
403
404impl PciScanner {
405    /// Creates a new instance.
406    pub fn new() -> Self {
407        let mut s = Self {
408            bus_queue: [0u8; 256],
409            queue_head: 0,
410            queue_tail: 1,
411            seen_buses: [false; 256],
412            device: 0,
413            function: 0,
414        };
415        s.seen_buses[0] = true;
416        s
417    }
418
419    fn enqueue_bus(&mut self, bus: u8) {
420        if !self.seen_buses[bus as usize] && self.queue_tail < 256 {
421            self.seen_buses[bus as usize] = true;
422            self.bus_queue[self.queue_tail] = bus;
423            self.queue_tail += 1;
424        }
425    }
426}
427
428impl Iterator for PciScanner {
429    type Item = PciDevice;
430
431    fn next(&mut self) -> Option<Self::Item> {
432        loop {
433            if self.queue_head >= self.queue_tail {
434                return None;
435            }
436            let bus = self.bus_queue[self.queue_head];
437
438            if self.device >= 32 {
439                self.queue_head += 1;
440                self.device = 0;
441                self.function = 0;
442                continue;
443            }
444
445            let address = PciAddress::new(bus, self.device, self.function);
446            let current_function = self.function;
447
448            self.function += 1;
449            if self.function >= 8 {
450                self.function = 0;
451                self.device += 1;
452            }
453
454            let Some(dev) = probe_device_full(address) else {
455                if current_function == 0 {
456                    self.function = 0;
457                    self.device += 1;
458                }
459                continue;
460            };
461
462            if dev.header_type & 0x7F == 0x01 {
463                let secondary = {
464                    let _lock = PCI_IO_LOCK.lock();
465                    unsafe {
466                        outl(CONFIG_ADDRESS, address.config_address(0x18));
467                        ((inl(CONFIG_DATA) >> 8) & 0xFF) as u8
468                    }
469                };
470                self.enqueue_bus(secondary);
471            }
472
473            if current_function == 0 && (dev.header_type & 0x80 == 0) {
474                self.function = 0;
475                self.device += 1;
476            }
477
478            return Some(dev);
479        }
480    }
481}
482
483/// Returns whether absent vendor.
484fn is_absent_vendor(vendor_id: u16) -> bool {
485    vendor_id == 0xFFFF || vendor_id == 0x0000
486}
487
488/// Performs the quirk zero irq line operation.
489fn quirk_zero_irq_line(vendor_id: u16, device_id: u16, irq_line: u8) -> u8 {
490    if irq_line != 0xFF {
491        return irq_line;
492    }
493    if PCI_IRQ_LINE_ZERO_IF_FF
494        .iter()
495        .any(|q| q.vendor_id == vendor_id && q.device_id == device_id)
496    {
497        return 0;
498    }
499    0
500}
501
502/// Performs the valid header type operation.
503fn valid_header_type(header_type: u8) -> bool {
504    matches!(header_type & 0x7F, 0x00..=0x02)
505}
506
507/// Returns whether ghost device.
508fn is_ghost_device(class_code: u8, subclass: u8, prog_if: u8) -> bool {
509    class_code == 0xFF && subclass == 0xFF && prog_if == 0xFF
510}
511
512/// Probe a PCI address using 4 batched dword reads under a single lock hold.
513///
514/// Reads: 0x00 (vendor+device), 0x08 (rev+progif+subclass+class),
515///        0x0C (cacheline+latency+headertype+bist), 0x3C (intline+intpin+mingnt+maxlat).
516fn probe_device_full(address: PciAddress) -> Option<PciDevice> {
517    let _lock = PCI_IO_LOCK.lock();
518
519    let word00 = unsafe {
520        outl(CONFIG_ADDRESS, address.config_address(0x00));
521        inl(CONFIG_DATA)
522    };
523    let vendor_id = (word00 & 0xFFFF) as u16;
524    if is_absent_vendor(vendor_id) {
525        return None;
526    }
527    let device_id = (word00 >> 16) as u16;
528    if device_id == 0xFFFF || device_id == 0x0000 {
529        return None;
530    }
531
532    let word08 = unsafe {
533        outl(CONFIG_ADDRESS, address.config_address(0x08));
534        inl(CONFIG_DATA)
535    };
536    let word0c = unsafe {
537        outl(CONFIG_ADDRESS, address.config_address(0x0C));
538        inl(CONFIG_DATA)
539    };
540
541    let header_type = ((word0c >> 16) & 0xFF) as u8;
542    if !valid_header_type(header_type) {
543        return None;
544    }
545
546    let class_code = ((word08 >> 24) & 0xFF) as u8;
547    let subclass = ((word08 >> 16) & 0xFF) as u8;
548    let prog_if = ((word08 >> 8) & 0xFF) as u8;
549    if is_ghost_device(class_code, subclass, prog_if) {
550        return None;
551    }
552
553    let word3c = unsafe {
554        outl(CONFIG_ADDRESS, address.config_address(0x3C));
555        inl(CONFIG_DATA)
556    };
557    let interrupt_line = quirk_zero_irq_line(vendor_id, device_id, (word3c & 0xFF) as u8);
558
559    Some(PciDevice {
560        address,
561        vendor_id,
562        device_id,
563        class_code,
564        subclass,
565        prog_if,
566        revision: (word08 & 0xFF) as u8,
567        header_type,
568        interrupt_line,
569        interrupt_pin: ((word3c >> 8) & 0xFF) as u8,
570    })
571}
572
573/// Helper to find a device by vendor and device ID
574pub fn find_device(vendor_id: u16, device_id: u16) -> Option<PciDevice> {
575    let mut cache = PCI_DEVICE_CACHE.lock();
576    if cache.is_none() {
577        *cache = Some(PciScanner::new().collect());
578    }
579    cache.as_ref().and_then(|devs| {
580        devs.iter()
581            .copied()
582            .find(|dev| dev.vendor_id == vendor_id && dev.device_id == device_id)
583    })
584}
585
586/// Find all VirtIO devices on the PCI bus
587pub fn find_virtio_devices() -> Vec<PciDevice> {
588    find_devices_by_vendor(vendor::VIRTIO)
589}
590
591/// Find a specific VirtIO device by device ID
592pub fn find_virtio_device(device_id: u16) -> Option<PciDevice> {
593    find_device(vendor::VIRTIO, device_id)
594}
595
596/// Cached PCI device inventory.
597///
598/// The first lookup performs a full bus scan, then all subsequent lookups reuse
599/// this snapshot. This is ideal during boot when multiple controllers probe PCI.
600static PCI_DEVICE_CACHE: SpinLock<Option<Vec<PciDevice>>> = SpinLock::new(None);
601
602/// Return a snapshot of all discovered PCI devices.
603///
604/// This uses a cached bus scan to avoid repeated O(bus*device*function) probes.
605pub fn all_devices() -> Vec<PciDevice> {
606    let mut cache = PCI_DEVICE_CACHE.lock();
607    if cache.is_none() {
608        *cache = Some(PciScanner::new().collect());
609    }
610    cache.as_ref().cloned().unwrap_or_default()
611}
612
613/// Return all devices for a given vendor from the cached PCI inventory.
614pub fn find_devices_by_vendor(vendor_id: u16) -> Vec<PciDevice> {
615    all_devices()
616        .into_iter()
617        .filter(|dev| dev.vendor_id == vendor_id)
618        .collect()
619}
620
621/// Return all devices matching a PCI class/subclass pair.
622///
623/// This is useful for future controller families (storage, USB, display, etc.)
624/// where matching by class is more stable than matching specific device IDs.
625pub fn find_devices_by_class(class_code: u8, subclass: u8) -> Vec<PciDevice> {
626    all_devices()
627        .into_iter()
628        .filter(|dev| dev.class_code == class_code && dev.subclass == subclass)
629        .collect()
630}
631
632/// Full PCI probe criteria.
633///
634/// Any field left as `None` is treated as a wildcard.
635#[derive(Debug, Clone, Copy, Default)]
636pub struct ProbeCriteria {
637    pub vendor_id: Option<u16>,
638    pub device_id: Option<u16>,
639    pub class_code: Option<u8>,
640    pub subclass: Option<u8>,
641    pub prog_if: Option<u8>,
642}
643
644impl ProbeCriteria {
645    /// Performs the any operation.
646    pub const fn any() -> Self {
647        Self {
648            vendor_id: None,
649            device_id: None,
650            class_code: None,
651            subclass: None,
652            prog_if: None,
653        }
654    }
655
656    /// Performs the matches operation.
657    fn matches(&self, dev: &PciDevice) -> bool {
658        if self.vendor_id.is_some_and(|v| dev.vendor_id != v) {
659            return false;
660        }
661        if self.device_id.is_some_and(|d| dev.device_id != d) {
662            return false;
663        }
664        if self.class_code.is_some_and(|c| dev.class_code != c) {
665            return false;
666        }
667        if self.subclass.is_some_and(|s| dev.subclass != s) {
668            return false;
669        }
670        if self.prog_if.is_some_and(|p| dev.prog_if != p) {
671            return false;
672        }
673        true
674    }
675}
676
677/// Return all devices matching `criteria`.
678pub fn probe_all(criteria: ProbeCriteria) -> Vec<PciDevice> {
679    all_devices()
680        .into_iter()
681        .filter(|dev| criteria.matches(dev))
682        .collect()
683}
684
685/// Return the first device matching `criteria`.
686pub fn probe_first(criteria: ProbeCriteria) -> Option<PciDevice> {
687    all_devices().into_iter().find(|dev| criteria.matches(dev))
688}
689
690/// Invalidate PCI cache.
691///
692/// Useful when hotplug/re-enumeration support is added in the future.
693pub fn invalidate_cache() {
694    *PCI_DEVICE_CACHE.lock() = None;
695}