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