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    pub const CAPABILITIES_PTR: u8 = 0x34;
153}
154
155/// PCI capability IDs
156pub mod cap_id {
157    /// MSI capability (PCI 2.2+)
158    pub const MSI: u8 = 0x05;
159    /// MSI-X capability (PCI 3.0+)
160    pub const MSIX: u8 = 0x11;
161}
162
163/// Offsets within an MSI capability block (relative to capability base).
164pub mod msi_cap {
165    /// Capability ID (1 byte) + next ptr (1 byte) = 2 bytes header
166    /// Control register at +2 (16-bit)
167    pub const CONTROL: u8 = 0x02;
168    /// Message Address Register (32-bit) at +4
169    pub const ADDR_LOW: u8 = 0x04;
170    /// Message Upper Address Register (32-bit, 64-bit capable only) at +8
171    pub const ADDR_HIGH: u8 = 0x08;
172    /// Message Data Register (16-bit):
173    ///   - 32-bit capable: at +8
174    ///   - 64-bit capable: at +12
175    pub const DATA: u8 = 0x08;
176}
177
178/// MSI control register bit definitions (16-bit at cap_base + 2).
179pub mod msi_ctrl {
180    /// MSI Enable
181    pub const ENABLE: u16 = 1 << 0;
182    /// Multiple Message Enable (bits 4:6) : number of vectors allocated
183    pub const MME_MASK: u16 = 0b111 << 4;
184    /// Multiple Message Capable (bits 1:3) : number of vectors the device wants
185    pub const MMC_MASK: u16 = 0b111 << 1;
186    /// 64-bit addressing supported
187    pub const ADDR64: u16 = 1 << 7;
188    /// Per-vector masking supported
189    pub const PV_MASK: u16 = 1 << 8;
190}
191
192/// Offsets within an MSI-X capability block (relative to capability base).
193pub mod msix_cap {
194    /// Capability ID (1 byte) + next ptr (1 byte) = 2 bytes header
195    /// Control register at +2 (16-bit)
196    pub const CONTROL: u8 = 0x02;
197    /// Table offset / BAR indicator (32-bit) at +4
198    pub const TABLE: u8 = 0x04;
199    /// Pending Bit Array offset / BAR indicator (32-bit) at +8
200    pub const PBA: u8 = 0x08;
201}
202
203/// MSI-X control register bit definitions (16-bit at cap_base + 2).
204pub mod msix_ctrl {
205    /// MSI-X Enable
206    pub const ENABLE: u16 = 1 << 15;
207    /// Function Mask
208    pub const FUNC_MASK: u16 = 1 << 14;
209    /// Table size (bits 0:10) : number of entries minus 1
210    pub const TABLE_SIZE_MASK: u16 = (1 << 11) - 1;
211}
212
213/// x86 MSI message address (delivers to LAPIC via system bus).
214///
215/// Format:
216///   [31:20] = 0xFEE (fixed)
217///   [19:12] = Destination LAPIC ID
218///   [11:4]  = reserved
219///   [3]     = Redirection Hint (0 = physical destination)
220///   [2]     = Destination Mode (0 = physical)
221///   [1:0]   = 0b00
222pub const MSI_ADDR_BASE: u32 = 0xFEE0_0000;
223pub const MSI_ADDR_DEST_SHIFT: u32 = 12;
224
225/// PCI command register bits
226pub mod command {
227    pub const IO_SPACE: u16 = 1 << 0;
228    pub const MEMORY_SPACE: u16 = 1 << 1;
229    pub const BUS_MASTER: u16 = 1 << 2;
230    pub const SPECIAL_CYCLES: u16 = 1 << 3;
231    pub const MWI_ENABLE: u16 = 1 << 4;
232    pub const VGA_PALETTE_SNOOP: u16 = 1 << 5;
233    pub const PARITY_ERROR_RESPONSE: u16 = 1 << 6;
234    pub const STEPPING_CONTROL: u16 = 1 << 7;
235    pub const SERR_ENABLE: u16 = 1 << 8;
236    pub const FAST_BACK_TO_BACK: u16 = 1 << 9;
237    pub const INTERRUPT_DISABLE: u16 = 1 << 10;
238}
239
240/// Base Address Register (BAR) types
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum Bar {
243    Io { port: u16 },
244    Memory32 { addr: u32, prefetchable: bool },
245    Memory64 { addr: u64, prefetchable: bool },
246}
247
248/// A PCI device location
249#[derive(Clone, Copy, PartialEq, Eq)]
250pub struct PciAddress {
251    pub bus: u8,
252    pub device: u8,
253    pub function: u8,
254}
255
256impl fmt::Debug for PciAddress {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        write!(f, "{:02x}:{:02x}.{}", self.bus, self.device, self.function)
259    }
260}
261
262impl PciAddress {
263    /// Create a new PCI address
264    pub const fn new(bus: u8, device: u8, function: u8) -> Self {
265        Self {
266            bus,
267            device,
268            function,
269        }
270    }
271
272    /// Convert to configuration address format
273    fn config_address(&self, offset: u8) -> u32 {
274        let bus = self.bus as u32;
275        let device = (self.device as u32) & 0x1F;
276        let function = (self.function as u32) & 0x07;
277        let offset = (offset as u32) & 0xFC;
278
279        0x8000_0000 | (bus << 16) | (device << 11) | (function << 8) | offset
280    }
281}
282
283/// PCI device information
284#[derive(Clone, Copy)]
285pub struct PciDevice {
286    pub address: PciAddress,
287    pub vendor_id: u16,
288    pub device_id: u16,
289    pub class_code: u8,
290    pub subclass: u8,
291    pub prog_if: u8,
292    pub revision: u8,
293    pub header_type: u8,
294    pub interrupt_line: u8,
295    pub interrupt_pin: u8,
296}
297
298impl fmt::Debug for PciDevice {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(
301            f,
302            "PciDevice({:?} ID {:04x}:{:04x} Class {:02x}:{:02x})",
303            self.address, self.vendor_id, self.device_id, self.class_code, self.subclass
304        )
305    }
306}
307
308impl PciDevice {
309    /// Read a configuration register (8-bit)
310    pub fn read_config_u8(&self, offset: u8) -> u8 {
311        let addr = self.address.config_address(offset & !0x03);
312        let shift = (offset & 0x03) * 8;
313
314        let _lock = PCI_IO_LOCK.lock();
315        unsafe {
316            outl(CONFIG_ADDRESS, addr);
317            ((inl(CONFIG_DATA) >> shift) & 0xFF) as u8
318        }
319    }
320
321    /// Read a configuration register (16-bit)
322    pub fn read_config_u16(&self, offset: u8) -> u16 {
323        let addr = self.address.config_address(offset & !0x03);
324        let shift = (offset & 0x02) * 8;
325
326        let _lock = PCI_IO_LOCK.lock();
327        unsafe {
328            outl(CONFIG_ADDRESS, addr);
329            ((inl(CONFIG_DATA) >> shift) & 0xFFFF) as u16
330        }
331    }
332
333    /// Read a configuration register (32-bit)
334    pub fn read_config_u32(&self, offset: u8) -> u32 {
335        let addr = self.address.config_address(offset);
336
337        let _lock = PCI_IO_LOCK.lock();
338        unsafe {
339            outl(CONFIG_ADDRESS, addr);
340            inl(CONFIG_DATA)
341        }
342    }
343
344    /// Write to a configuration register (8-bit)
345    pub fn write_config_u8(&self, offset: u8, value: u8) {
346        let addr = self.address.config_address(offset & !0x03);
347        let shift = (offset & 0x03) * 8;
348
349        let _lock = PCI_IO_LOCK.lock();
350        unsafe {
351            outl(CONFIG_ADDRESS, addr);
352            let old = inl(CONFIG_DATA);
353            let mask = !(0xFF << shift);
354            let new = (old & mask) | ((value as u32) << shift);
355            outl(CONFIG_ADDRESS, addr);
356            outl(CONFIG_DATA, new);
357        }
358    }
359
360    /// Write to a configuration register (16-bit)
361    pub fn write_config_u16(&self, offset: u8, value: u16) {
362        let addr = self.address.config_address(offset & !0x03);
363        let shift = (offset & 0x02) * 8;
364
365        let _lock = PCI_IO_LOCK.lock();
366        unsafe {
367            outl(CONFIG_ADDRESS, addr);
368            let old = inl(CONFIG_DATA);
369            let mask = !(0xFFFF << shift);
370            let new = (old & mask) | ((value as u32) << shift);
371            outl(CONFIG_ADDRESS, addr);
372            outl(CONFIG_DATA, new);
373        }
374    }
375
376    /// Write to a configuration register (32-bit)
377    pub fn write_config_u32(&self, offset: u8, value: u32) {
378        let addr = self.address.config_address(offset);
379
380        let _lock = PCI_IO_LOCK.lock();
381        unsafe {
382            outl(CONFIG_ADDRESS, addr);
383            outl(CONFIG_DATA, value);
384        }
385    }
386
387    /// Read a Base Address Register (BAR)
388    pub fn read_bar(&self, bar_index: u8) -> Option<Bar> {
389        if bar_index > 5 {
390            return None;
391        }
392
393        let offset = config::BAR0 + (bar_index * 4);
394        let bar_low = self.read_config_u32(offset);
395
396        if bar_low == 0 {
397            return None;
398        }
399
400        // Check if it's an I/O BAR (bit 0 set)
401        if bar_low & 0x1 != 0 {
402            let port = (bar_low & 0xFFFF_FFFC) as u16;
403            Some(Bar::Io { port })
404        } else {
405            let bar_type = (bar_low >> 1) & 0x3;
406            let prefetchable = (bar_low >> 3) & 0x1 != 0;
407
408            match bar_type {
409                0 => {
410                    let addr = bar_low & 0xFFFF_FFF0;
411                    Some(Bar::Memory32 { addr, prefetchable })
412                }
413                2 => {
414                    if bar_index >= 5 {
415                        return None;
416                    }
417                    let bar_high = self.read_config_u32(offset + 4);
418                    let addr = ((bar_high as u64) << 32) | ((bar_low & 0xFFFF_FFF0) as u64);
419                    Some(Bar::Memory64 { addr, prefetchable })
420                }
421                _ => None,
422            }
423        }
424    }
425
426    /// Get the raw BAR value (for legacy compatibility)
427    pub fn read_bar_raw(&self, bar_index: u8) -> Option<u64> {
428        match self.read_bar(bar_index) {
429            Some(Bar::Io { port }) => Some(port as u64),
430            Some(Bar::Memory32 { addr, .. }) => Some(addr as u64),
431            Some(Bar::Memory64 { addr, .. }) => Some(addr),
432            None => None,
433        }
434    }
435
436    /// Enable bus mastering for this device
437    pub fn enable_bus_master(&self) {
438        let mut cmd = self.read_config_u16(config::COMMAND);
439        cmd |= command::BUS_MASTER;
440        self.write_config_u16(config::COMMAND, cmd);
441    }
442
443    /// Enable memory space access for this device
444    pub fn enable_memory_space(&self) {
445        let mut cmd = self.read_config_u16(config::COMMAND);
446        cmd |= command::MEMORY_SPACE;
447        self.write_config_u16(config::COMMAND, cmd);
448    }
449
450    /// Enable I/O space access for this device
451    pub fn enable_io_space(&self) {
452        let mut cmd = self.read_config_u16(config::COMMAND);
453        cmd |= command::IO_SPACE;
454        self.write_config_u16(config::COMMAND, cmd);
455    }
456}
457
458// ---------------------------------------------------------------------------
459// Fast PCI bus scanner (BFS with early-exit)
460// ---------------------------------------------------------------------------
461//
462// Insipired by asterinas OS's PCI scanner:
463//
464//  1. For each (bus, device), probe function 0 first.
465//     If vendor == 0xFFFF → skip all 8 functions (early exit).
466//
467//  2. Read the Header Type from the function-0 dword at offset 0x0C.
468//     Bit 7 (multi-function flag) tells whether functions 1..7 can exist.
469//     If bit 7 is clear → skip functions 1..7 entirely.
470//
471//  3. If header_type & 0x7F == 0x01 (PCI-to-PCI bridge), read the
472//     secondary bus number and enqueue it.  The `seen_buses` bitmap
473//     prevents re-scanning a bus already visited.
474//
475// This reduces the worst-case probes from 256 × 32 × 8 = 65 536 down to
476// 256 × 32 × 1 = 8 192 for a topology with no multi-function devices
477// (the common case on QEMU / VMware).
478//
479// I/O cost per probe:
480//   Old: 4 dword reads under 4 separate lock acquisitions.
481//   New: 1 dword read (vendor check, early exit) + 3 dword reads only
482//        for devices that actually exist, all under a single lock hold
483//        via `probe_device_full`.
484
485/// Iterator for scanning PCI bus
486pub struct PciScanner {
487    bus_queue: [u8; 256],
488    queue_head: usize,
489    queue_tail: usize,
490    seen_buses: [bool; 256],
491    device: u8,
492    function: u8,
493    /// Cached multi-function flag for the current device (from function 0).
494    /// When `function > 0`, this tells us whether to keep scanning.
495    is_multi_function: bool,
496}
497
498impl PciScanner {
499    pub fn new() -> Self {
500        let mut s = Self {
501            bus_queue: [0u8; 256],
502            queue_head: 0,
503            queue_tail: 1,
504            seen_buses: [false; 256],
505            device: 0,
506            function: 0,
507            is_multi_function: false,
508        };
509        s.seen_buses[0] = true;
510        s
511    }
512
513    fn enqueue_bus(&mut self, bus: u8) {
514        if !self.seen_buses[bus as usize] && self.queue_tail < 256 {
515            self.seen_buses[bus as usize] = true;
516            self.bus_queue[self.queue_tail] = bus;
517            self.queue_tail += 1;
518        }
519    }
520
521    #[inline]
522    fn advance_to_next_device(&mut self) {
523        self.function = 0;
524        self.device += 1;
525        self.is_multi_function = false;
526    }
527}
528
529impl Iterator for PciScanner {
530    type Item = PciDevice;
531
532    fn next(&mut self) -> Option<Self::Item> {
533        loop {
534            // Advance to next bus if we exhausted all 32 devices.
535            if self.queue_head >= self.queue_tail {
536                return None;
537            }
538            let bus = self.bus_queue[self.queue_head];
539
540            if self.device >= 32 {
541                self.queue_head += 1;
542                self.device = 0;
543                self.function = 0;
544                self.is_multi_function = false;
545                continue;
546            }
547
548            let current_function = self.function;
549
550            // --- Function 0: fast vendor check + early exit ---
551            if current_function == 0 {
552                // Single dword read: vendor+device at offset 0x00.
553                // If 0xFFFF → no device at this slot, skip all 8 functions.
554                let word00 = raw_config_read(bus, self.device, 0, 0x00);
555                let vendor_id = (word00 & 0xFFFF) as u16;
556                if is_absent_vendor(vendor_id) {
557                    self.advance_to_next_device();
558                    continue;
559                }
560
561                // Device exists at function 0 : do the full probe.
562                let Some(dev) = probe_from_word00(PciAddress::new(bus, self.device, 0), word00)
563                else {
564                    self.advance_to_next_device();
565                    continue;
566                };
567
568                // Cache multi-function status from header_type bit 7.
569                self.is_multi_function = dev.header_type & 0x80 != 0;
570
571                // If it's a PCI-to-PCI bridge, enqueue the secondary bus.
572                if dev.header_type & 0x7F == 0x01 {
573                    let secondary = raw_config_read_u8(bus, self.device, 0, 0x19);
574                    self.enqueue_bus(secondary);
575                }
576
577                // Advance: if multi-function, move to function 1;
578                // otherwise skip straight to the next device.
579                if self.is_multi_function {
580                    self.function = 1;
581                } else {
582                    self.advance_to_next_device();
583                }
584
585                return Some(dev);
586            }
587
588            // --- Functions 1..7 (only reached if multi-function) ---
589            debug_assert!(self.is_multi_function);
590
591            self.function += 1;
592            if self.function >= 8 {
593                self.advance_to_next_device();
594            }
595
596            let address = PciAddress::new(bus, self.device, current_function);
597            let Some(dev) = probe_device_full(address) else {
598                continue;
599            };
600
601            if dev.header_type & 0x7F == 0x01 {
602                let secondary = raw_config_read_u8(bus, self.device, current_function, 0x19);
603                self.enqueue_bus(secondary);
604            }
605
606            return Some(dev);
607        }
608    }
609}
610
611// ---------------------------------------------------------------------------
612// Low-level config-space helpers
613// ---------------------------------------------------------------------------
614
615fn is_absent_vendor(vendor_id: u16) -> bool {
616    vendor_id == 0xFFFF || vendor_id == 0x0000
617}
618
619fn quirk_zero_irq_line(vendor_id: u16, device_id: u16, irq_line: u8) -> u8 {
620    if irq_line != 0xFF {
621        return irq_line;
622    }
623    if PCI_IRQ_LINE_ZERO_IF_FF
624        .iter()
625        .any(|q| q.vendor_id == vendor_id && q.device_id == device_id)
626    {
627        return 0;
628    }
629    0
630}
631
632fn valid_header_type(header_type: u8) -> bool {
633    matches!(header_type & 0x7F, 0x00..=0x02)
634}
635
636fn is_ghost_device(class_code: u8, subclass: u8, prog_if: u8) -> bool {
637    class_code == 0xFF && subclass == 0xFF && prog_if == 0xFF
638}
639
640/// Single dword config read without building a PciDevice/PciAddress.
641/// Acquires PCI_IO_LOCK once.
642#[inline]
643fn raw_config_read(bus: u8, device: u8, function: u8, offset: u8) -> u32 {
644    let addr = 0x8000_0000u32
645        | ((bus as u32) << 16)
646        | (((device as u32) & 0x1F) << 11)
647        | (((function as u32) & 0x07) << 8)
648        | ((offset as u32) & 0xFC);
649    let _lock = PCI_IO_LOCK.lock();
650    unsafe {
651        outl(CONFIG_ADDRESS, addr);
652        inl(CONFIG_DATA)
653    }
654}
655
656/// Read a single byte from config space (derived from a dword read).
657#[inline]
658fn raw_config_read_u8(bus: u8, device: u8, function: u8, offset: u8) -> u8 {
659    let dword = raw_config_read(bus, device, function, offset & !0x03);
660    let shift = (offset & 0x03) * 8;
661    ((dword >> shift) & 0xFF) as u8
662}
663
664/// Probe a PCI address using 4 batched dword reads under a single lock hold.
665///
666/// Reads: 0x00 (vendor+device), 0x08 (rev+progif+subclass+class),
667///        0x0C (cacheline+latency+headertype+bist), 0x3C (intline+intpin).
668fn probe_device_full(address: PciAddress) -> Option<PciDevice> {
669    let _lock = PCI_IO_LOCK.lock();
670
671    let word00 = unsafe {
672        outl(CONFIG_ADDRESS, address.config_address(0x00));
673        inl(CONFIG_DATA)
674    };
675    let vendor_id = (word00 & 0xFFFF) as u16;
676    if is_absent_vendor(vendor_id) {
677        return None;
678    }
679    let device_id = (word00 >> 16) as u16;
680    if device_id == 0xFFFF || device_id == 0x0000 {
681        return None;
682    }
683
684    let word08 = unsafe {
685        outl(CONFIG_ADDRESS, address.config_address(0x08));
686        inl(CONFIG_DATA)
687    };
688    let word0c = unsafe {
689        outl(CONFIG_ADDRESS, address.config_address(0x0C));
690        inl(CONFIG_DATA)
691    };
692
693    let header_type = ((word0c >> 16) & 0xFF) as u8;
694    if !valid_header_type(header_type) {
695        return None;
696    }
697
698    let class_code = ((word08 >> 24) & 0xFF) as u8;
699    let subclass = ((word08 >> 16) & 0xFF) as u8;
700    let prog_if = ((word08 >> 8) & 0xFF) as u8;
701    if is_ghost_device(class_code, subclass, prog_if) {
702        return None;
703    }
704
705    let word3c = unsafe {
706        outl(CONFIG_ADDRESS, address.config_address(0x3C));
707        inl(CONFIG_DATA)
708    };
709    let interrupt_line = quirk_zero_irq_line(vendor_id, device_id, (word3c & 0xFF) as u8);
710
711    Some(PciDevice {
712        address,
713        vendor_id,
714        device_id,
715        class_code,
716        subclass,
717        prog_if,
718        revision: (word08 & 0xFF) as u8,
719        header_type,
720        interrupt_line,
721        interrupt_pin: ((word3c >> 8) & 0xFF) as u8,
722    })
723}
724
725/// Build a PciDevice when `word00` (vendor+device dword) was already read
726/// by the caller's fast-path vendor check, avoiding a redundant I/O cycle.
727fn probe_from_word00(address: PciAddress, word00: u32) -> Option<PciDevice> {
728    let vendor_id = (word00 & 0xFFFF) as u16;
729    let device_id = (word00 >> 16) as u16;
730    if device_id == 0xFFFF || device_id == 0x0000 {
731        return None;
732    }
733
734    let _lock = PCI_IO_LOCK.lock();
735
736    let word08 = unsafe {
737        outl(CONFIG_ADDRESS, address.config_address(0x08));
738        inl(CONFIG_DATA)
739    };
740    let word0c = unsafe {
741        outl(CONFIG_ADDRESS, address.config_address(0x0C));
742        inl(CONFIG_DATA)
743    };
744
745    let header_type = ((word0c >> 16) & 0xFF) as u8;
746    if !valid_header_type(header_type) {
747        return None;
748    }
749
750    let class_code = ((word08 >> 24) & 0xFF) as u8;
751    let subclass = ((word08 >> 16) & 0xFF) as u8;
752    let prog_if = ((word08 >> 8) & 0xFF) as u8;
753    if is_ghost_device(class_code, subclass, prog_if) {
754        return None;
755    }
756
757    let word3c = unsafe {
758        outl(CONFIG_ADDRESS, address.config_address(0x3C));
759        inl(CONFIG_DATA)
760    };
761    let interrupt_line = quirk_zero_irq_line(vendor_id, device_id, (word3c & 0xFF) as u8);
762
763    Some(PciDevice {
764        address,
765        vendor_id,
766        device_id,
767        class_code,
768        subclass,
769        prog_if,
770        revision: (word08 & 0xFF) as u8,
771        header_type,
772        interrupt_line,
773        interrupt_pin: ((word3c >> 8) & 0xFF) as u8,
774    })
775}
776
777// ---------------------------------------------------------------------------
778// Cached device inventory
779// ---------------------------------------------------------------------------
780
781/// Cached PCI device inventory.
782///
783/// The first lookup performs a full bus scan, then all subsequent lookups reuse
784/// this snapshot. Every query function borrows the cache through the lock and
785/// operates on the `&[PciDevice]` directly : no `clone()` of the Vec.
786///
787/// # Invariant
788///
789/// `PCI_DEVICE_CACHE` is populated once at boot and never invalidated during
790/// normal operation. The `SpinLock<Option<Vec<PciDevice>>>` is held only for
791/// the initial scan (which allocates the Vec) and for subsequent reads. No
792/// code path re-scans the bus or reallocates under the lock. If PCI hotplug
793/// or re-enumeration is added in the future, this must be changed to either :
794///   - a `Mutex` (sleepable) to allow re-scanning, or
795///   - a lock-free double-buffered snapshot model.
796static PCI_DEVICE_CACHE: SpinLock<Option<Vec<PciDevice>>> = SpinLock::new(None);
797
798/// Populate the cache if empty, then run `f` on the device slice.
799///
800/// All query functions route through here so that only a single scan ever
801/// happens, and the lock is held for the duration of the filter : not for
802/// the entire boot.
803fn with_cache<R>(f: impl FnOnce(&[PciDevice]) -> R) -> R {
804    let mut cache = PCI_DEVICE_CACHE.lock();
805    if cache.is_none() {
806        // Debug: check stack before PCI scan
807        let dummy = 0u64;
808        let rsp = &dummy as *const u64 as u64;
809        crate::serial_println!("[PCI] Scanning PCI bus, rsp={:#x}", rsp);
810        let devices: Vec<PciDevice> = PciScanner::new().collect();
811        crate::serial_println!("[PCI] PCI scan complete, found {} devices", devices.len());
812        for dev in &devices {
813            crate::serial_println!(
814                "[PCI]   {:02x}:{:02x}.{:x} vendor={:04x} device={:04x} class={:02x}:{:02x}",
815                dev.address.bus,
816                dev.address.device,
817                dev.address.function,
818                dev.vendor_id,
819                dev.device_id,
820                dev.class_code,
821                dev.subclass
822            );
823        }
824        *cache = Some(devices);
825    }
826    f(cache.as_deref().unwrap_or(&[]))
827}
828
829/// Helper to find a device by vendor and device ID
830pub fn find_device(vendor_id: u16, device_id: u16) -> Option<PciDevice> {
831    with_cache(|devs| {
832        devs.iter()
833            .copied()
834            .find(|dev| dev.vendor_id == vendor_id && dev.device_id == device_id)
835    })
836}
837
838/// Find all VirtIO devices on the PCI bus
839pub fn find_virtio_devices() -> Vec<PciDevice> {
840    find_devices_by_vendor(vendor::VIRTIO)
841}
842
843/// Find a specific VirtIO device by device ID
844pub fn find_virtio_device(device_id: u16) -> Option<PciDevice> {
845    find_device(vendor::VIRTIO, device_id)
846}
847
848/// Return a snapshot of all discovered PCI devices.
849pub fn all_devices() -> Vec<PciDevice> {
850    with_cache(|devs| devs.to_vec())
851}
852
853/// Return all devices for a given vendor from the cached PCI inventory.
854pub fn find_devices_by_vendor(vendor_id: u16) -> Vec<PciDevice> {
855    with_cache(|devs| {
856        devs.iter()
857            .copied()
858            .filter(|dev| dev.vendor_id == vendor_id)
859            .collect()
860    })
861}
862
863/// Return all devices matching a PCI class/subclass pair.
864pub fn find_devices_by_class(class_code: u8, subclass: u8) -> Vec<PciDevice> {
865    with_cache(|devs| {
866        devs.iter()
867            .copied()
868            .filter(|dev| dev.class_code == class_code && dev.subclass == subclass)
869            .collect()
870    })
871}
872
873/// Full PCI probe criteria.
874///
875/// Any field left as `None` is treated as a wildcard.
876#[derive(Debug, Clone, Copy, Default)]
877pub struct ProbeCriteria {
878    pub vendor_id: Option<u16>,
879    pub device_id: Option<u16>,
880    pub class_code: Option<u8>,
881    pub subclass: Option<u8>,
882    pub prog_if: Option<u8>,
883}
884
885impl ProbeCriteria {
886    pub const fn any() -> Self {
887        Self {
888            vendor_id: None,
889            device_id: None,
890            class_code: None,
891            subclass: None,
892            prog_if: None,
893        }
894    }
895
896    fn matches(&self, dev: &PciDevice) -> bool {
897        if self.vendor_id.is_some_and(|v| dev.vendor_id != v) {
898            return false;
899        }
900        if self.device_id.is_some_and(|d| dev.device_id != d) {
901            return false;
902        }
903        if self.class_code.is_some_and(|c| dev.class_code != c) {
904            return false;
905        }
906        if self.subclass.is_some_and(|s| dev.subclass != s) {
907            return false;
908        }
909        if self.prog_if.is_some_and(|p| dev.prog_if != p) {
910            return false;
911        }
912        true
913    }
914}
915
916/// Return all devices matching `criteria`.
917pub fn probe_all(criteria: ProbeCriteria) -> Vec<PciDevice> {
918    with_cache(|devs| {
919        devs.iter()
920            .copied()
921            .filter(|dev| criteria.matches(dev))
922            .collect()
923    })
924}
925
926/// Return the first device matching `criteria`.
927pub fn probe_first(criteria: ProbeCriteria) -> Option<PciDevice> {
928    with_cache(|devs| devs.iter().copied().find(|dev| criteria.matches(dev)))
929}
930
931/// Invalidate PCI cache.
932///
933/// Useful when hotplug/re-enumeration support is added in the future.
934pub fn invalidate_cache() {
935    *PCI_DEVICE_CACHE.lock() = None;
936}