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/// A mapped ECAM region covering a range of buses.
25struct EcamRegion {
26    start_bus: u8,
27    end_bus: u8,
28    base_virt: usize,
29}
30
31/// Cached ECAM MMIO regions for extended config space access (offsets >= 0x100).
32/// Populated during `scan_ecam_devices()` when an MCFG table is present.
33static ECAM_REGIONS: SpinLock<Vec<EcamRegion>> = SpinLock::new(Vec::new());
34
35// ---------------------------------------------------------------------------
36// ECAM (PCIe Enhanced Configuration Access Mechanism) helpers
37// ---------------------------------------------------------------------------
38
39/// Read a 32-bit register from an ECAM-mapped PCI config space.
40///
41/// `ecam_base` is the MMIO base address of the ECAM region.
42/// `bus`, `device`, `function` identify the device; `offset` is the register.
43#[inline]
44unsafe fn ecam_read32(ecam_base: usize, bus: u8, device: u8, function: u8, offset: u16) -> u32 {
45    let addr = ecam_base
46        + ((bus as usize) << 20)
47        + ((device as usize) << 15)
48        + ((function as usize) << 12)
49        + ((offset as usize) & !0x03);
50    core::ptr::read_volatile(addr as *const u32)
51}
52
53/// Write a 32-bit register to an ECAM-mapped PCI config space.
54#[inline]
55unsafe fn ecam_write32(ecam_base: usize, bus: u8, device: u8, function: u8, offset: u16, val: u32) {
56    let addr = ecam_base
57        + ((bus as usize) << 20)
58        + ((device as usize) << 15)
59        + ((function as usize) << 12)
60        + ((offset as usize) & !0x03);
61    core::ptr::write_volatile(addr as *mut u32, val);
62}
63
64/// Read a single byte from ECAM config space.
65#[inline]
66unsafe fn ecam_read8(ecam_base: usize, bus: u8, device: u8, function: u8, offset: u16) -> u8 {
67    let dword = ecam_read32(ecam_base, bus, device, function, offset & !0x03);
68    ((dword >> ((offset & 0x3) * 8)) & 0xFF) as u8
69}
70
71// ---------------------------------------------------------------------------
72// Known device name lookup (for improved logging)
73// ---------------------------------------------------------------------------
74
75/// Known PCI device names for human-readable logging.
76fn device_name(vendor: u16, device: u16) -> Option<&'static str> {
77    match (vendor, device) {
78        // Intel Ethernet
79        (0x8086, 0x100E) => Some("Intel E1000 (QEMU)"),
80        (0x8086, 0x100F) => Some("Intel E1000 (QEMU)"),
81        (0x8086, 0x10D3) => Some("Intel E1000e (QEMU)"),
82        (0x8086, 0x153A) => Some("Intel I217-LM"),
83        (0x8086, 0x15F9) => Some("Intel I219-LM"),
84        (0x8086, 0x15FA) => Some("Intel I219-V"),
85        (0x8086, 0x15F2) => Some("Intel I225-LM"),
86        (0x8086, 0x15F3) => Some("Intel I225-V"),
87        (0x8086, 0x125B) => Some("Intel I226-LM"),
88        (0x8086, 0x125C) => Some("Intel I226-V"),
89        // VirtIO
90        (0x1AF4, 0x1000) => Some("VirtIO Net"),
91        (0x1AF4, 0x1001) => Some("VirtIO Block"),
92        (0x1AF4, 0x1003) => Some("VirtIO Console"),
93        (0x1AF4, 0x1005) => Some("VirtIO RNG"),
94        (0x1AF4, 0x1050) => Some("VirtIO GPU"),
95        (0x1AF4, 0x1052) => Some("VirtIO Input"),
96        // QEMU
97        (0x1234, 0x11E9) => Some("QEMU VGA"),
98        _ => None,
99    }
100}
101
102/// Human-readable PCI class name.
103fn class_name(class: u8, subclass: u8) -> Option<&'static str> {
104    match (class, subclass) {
105        (0x00, 0x00) => Some("Legacy Device"),
106        (0x00, 0x01) => Some("VGA-Compatible Device"),
107        (0x01, 0x00) => Some("SCSI Controller"),
108        (0x01, 0x01) => Some("IDE Controller"),
109        (0x01, 0x06) => Some("SATA Controller"),
110        (0x01, 0x08) => Some("NVMe Controller"),
111        (0x02, 0x00) => Some("Ethernet Controller"),
112        (0x02, 0x80) => Some("Network Controller"),
113        (0x03, 0x00) => Some("VGA Controller"),
114        (0x03, 0x02) => Some("3D Controller"),
115        (0x04, 0x00) => Some("Multimedia Video"),
116        (0x04, 0x03) => Some("Audio Device"),
117        (0x06, 0x00) => Some("Host Bridge"),
118        (0x06, 0x01) => Some("ISA Bridge"),
119        (0x06, 0x04) => Some("PCI-to-PCI Bridge"),
120        (0x0C, 0x03) => Some("USB Controller"),
121        (0x08, 0x00) => Some("I20"),
122        _ => None,
123    }
124}
125
126#[derive(Clone, Copy)]
127struct PciLineQuirk {
128    vendor_id: u16,
129    device_id: u16,
130}
131
132const PCI_IRQ_LINE_ZERO_IF_FF: &[PciLineQuirk] = &[
133    PciLineQuirk {
134        vendor_id: vendor::INTEL,
135        device_id: intel_eth::E1000_82540EM,
136    },
137    PciLineQuirk {
138        vendor_id: vendor::INTEL,
139        device_id: intel_eth::E1000_82545EM,
140    },
141    PciLineQuirk {
142        vendor_id: vendor::INTEL,
143        device_id: intel_eth::E1000E_82574L,
144    },
145    PciLineQuirk {
146        vendor_id: vendor::VIRTIO,
147        device_id: device::VIRTIO_NET,
148    },
149    PciLineQuirk {
150        vendor_id: vendor::VIRTIO,
151        device_id: device::VIRTIO_BLOCK,
152    },
153];
154
155/// PCI Vendor IDs
156pub mod vendor {
157    pub const VIRTIO: u16 = 0x1AF4;
158    pub const QEMU: u16 = 0x1234;
159    pub const INTEL: u16 = 0x8086;
160    pub const AMD: u16 = 0x1022;
161    /// ATI/AMD display controller vendor ID (GPUs, APUs display)
162    pub const AMD_DISPLAY: u16 = 0x1002;
163}
164
165/// PCI Device IDs (VirtIO legacy)
166pub mod device {
167    pub const VIRTIO_NET: u16 = 0x1000;
168    pub const VIRTIO_BLOCK: u16 = 0x1001;
169    pub const VIRTIO_CONSOLE: u16 = 0x1003;
170    pub const VIRTIO_RNG: u16 = 0x1005;
171    pub const VIRTIO_GPU: u16 = 0x1050;
172    pub const VIRTIO_INPUT: u16 = 0x1052;
173}
174
175/// PCI base class codes
176pub mod class {
177    pub const MASS_STORAGE: u8 = 0x01;
178    pub const NETWORK: u8 = 0x02;
179}
180
181/// PCI subclasses for mass storage controllers
182pub mod storage_subclass {
183    pub const SCSI: u8 = 0x00;
184    pub const IDE: u8 = 0x01;
185    pub const FLOPPY: u8 = 0x02;
186    pub const IPI: u8 = 0x03;
187    pub const RAID: u8 = 0x04;
188    pub const ATA: u8 = 0x05;
189    pub const SATA: u8 = 0x06;
190    pub const SAS: u8 = 0x07;
191    pub const NVM: u8 = 0x08;
192    pub const OTHER: u8 = 0x80;
193}
194
195/// Programming interface codes for mass-storage SATA controllers
196pub mod sata_progif {
197    /// AHCI 1.0 (Advanced Host Controller Interface) : the standard modern mode
198    pub const AHCI: u8 = 0x01;
199    /// Vendor-specific / legacy IDE emulation
200    pub const VENDOR: u8 = 0x00;
201}
202
203/// PCI subclasses for network controllers
204pub mod net_subclass {
205    pub const ETHERNET: u8 = 0x00;
206    pub const OTHER: u8 = 0x80;
207}
208
209/// Intel Ethernet device IDs
210pub mod intel_eth {
211    pub const E1000_82540EM: u16 = 0x100E; // QEMU default e1000
212    pub const E1000_82545EM: u16 = 0x100F;
213    pub const E1000E_82574L: u16 = 0x10D3; // QEMU e1000e
214    pub const I210_AT: u16 = 0x1533;
215    pub const I350_AM2: u16 = 0x1521;
216    pub const I350_AM4: u16 = 0x1523;
217    pub const I217_LM: u16 = 0x153A;
218    pub const I211_AT: u16 = 0x1539;
219    pub const I219_LM: u16 = 0x15F9;
220    pub const I219_V: u16 = 0x15FA;
221    pub const I225_LM: u16 = 0x15F2;
222    pub const I225_V: u16 = 0x15F3;
223    pub const I226_LM: u16 = 0x125B;
224    pub const I226_V: u16 = 0x125C;
225}
226
227/// PCI configuration register offsets
228pub mod config {
229    pub const VENDOR_ID: u8 = 0x00;
230    pub const DEVICE_ID: u8 = 0x02;
231    pub const COMMAND: u8 = 0x04;
232    pub const STATUS: u8 = 0x06;
233    pub const REVISION_ID: u8 = 0x08;
234    pub const PROG_IF: u8 = 0x09;
235    pub const SUBCLASS: u8 = 0x0A;
236    pub const CLASS_CODE: u8 = 0x0B;
237    pub const CACHE_LINE_SIZE: u8 = 0x0C;
238    pub const LATENCY_TIMER: u8 = 0x0D;
239    pub const HEADER_TYPE: u8 = 0x0E;
240    pub const BIST: u8 = 0x0F;
241    pub const BAR0: u8 = 0x10;
242    pub const BAR1: u8 = 0x14;
243    pub const BAR2: u8 = 0x18;
244    pub const BAR3: u8 = 0x1C;
245    pub const BAR4: u8 = 0x20;
246    pub const BAR5: u8 = 0x24;
247    pub const CARDBUS_CIS: u8 = 0x28;
248    pub const SUBSYSTEM_VENDOR_ID: u8 = 0x2C;
249    pub const SUBSYSTEM_ID: u8 = 0x2E;
250    pub const ROM_BAR: u8 = 0x30;
251    pub const CAPABILITIES: u8 = 0x34;
252    pub const INTERRUPT_LINE: u8 = 0x3C;
253    pub const INTERRUPT_PIN: u8 = 0x3D;
254    pub const MIN_GNT: u8 = 0x3E;
255    pub const MAX_LAT: u8 = 0x3F;
256    pub const CAPABILITIES_PTR: u8 = 0x34;
257}
258
259/// PCI capability IDs
260pub mod cap_id {
261    /// MSI capability (PCI 2.2+)
262    pub const MSI: u8 = 0x05;
263    /// MSI-X capability (PCI 3.0+)
264    pub const MSIX: u8 = 0x11;
265}
266
267/// PCIe Extended Capability IDs (offset >= 0x100)
268pub mod ext_cap_id {
269    /// Advanced Error Reporting (AER)
270    pub const AER: u16 = 0x0001;
271    /// Virtual Channel
272    pub const VC: u16 = 0x0002;
273    /// Device Serial Number
274    pub const DSN: u16 = 0x0003;
275    /// Power Budgeting
276    pub const PB: u16 = 0x0004;
277    /// Root Link Declaration
278    pub const RLD: u16 = 0x0005;
279    /// Root Complex Link Declaration
280    pub const RCLD: u16 = 0x0006;
281    /// Multi-Root I/O Virtualization
282    pub const MRIOV: u16 = 0x0008;
283    /// Single-Root I/O Virtualization (SR-IOV)
284    pub const SRIOV: u16 = 0x0010;
285    /// Resizable BAR
286    pub const RBAR: u16 = 0x0015;
287    /// Dynamic Power Allocation
288    pub const DPA: u16 = 0x0016;
289    /// TPH Requester
290    pub const TPH: u16 = 0x0017;
291    /// Latency Tolerance Reporting
292    pub const LTR: u16 = 0x0018;
293    /// Secondary PCIe Capability
294    pub const SEC_PCIE: u16 = 0x0019;
295    /// PMUX
296    pub const PMUX: u16 = 0x001A;
297    /// Process Address Space ID (PASID)
298    pub const PASID: u16 = 0x001B;
299    /// LNR (Layer 3 Routing)
300    pub const LNR: u16 = 0x001C;
301    /// Address Translation Services (ATS)
302    pub const ATS: u16 = 0x001D;
303    /// Page Request Interface (PRI)
304    pub const PRI: u16 = 0x001E;
305    /// Process Address Space ID (PASID) Extended Capability
306    pub const PASID_EXT: u16 = 0x0020;
307    /// Shared Virtual Memory (SVM)
308    pub const SVM: u16 = 0x0021;
309    /// L1 PM Substates
310    pub const L1_PM: u16 = 0x0022;
311    /// Precision Time Measurement (PTM)
312    pub const PTM: u16 = 0x0023;
313    /// PCIe Tunneling
314    pub const TUNNEL: u16 = 0x0024;
315    /// Access Control Services (ACS)
316    pub const ACS: u16 = 0x0025;
317    /// Alternative Routing ID Interpretation (ARI)
318    pub const ARI: u16 = 0x0026;
319    /// Address Translation Services (ATS) Extended Capability
320    pub const ATS_EXT: u16 = 0x0027;
321    /// Single Root I/O Virtualization (SR-IOV) Extended Capability
322    pub const SRIOV_EXT: u16 = 0x0028;
323    /// Physical Function (PF) SR-IOV Extended Capability
324    pub const PF_SRIOV: u16 = 0x0029;
325}
326
327/// SR-IOV capability register offsets (relative to capability base).
328pub mod sriov_cap {
329    /// Capability register (16-bit)
330    pub const CAP: u8 = 0x00;
331    /// SR-IOV Control register (16-bit)
332    pub const CONTROL: u8 = 0x02;
333    /// SR-IOV Status register (16-bit)
334    pub const STATUS: u8 = 0x04;
335    /// SR-IOV PCIe Capability register (32-bit)
336    pub const PCIE_CAP: u8 = 0x08;
337    /// SR-IOV ARI Capability register (32-bit)
338    pub const ARI_CAP: u8 = 0x0C;
339    /// SR-IOV Initial VFs (16-bit)
340    pub const INITIAL_VF: u8 = 0x10;
341    /// SR-IOV Total VFs (16-bit)
342    pub const TOTAL_VF: u8 = 0x12;
343    /// SR-IOV Number of VFs (16-bit)
344    pub const NUM_VF: u8 = 0x14;
345    /// SR-IOV Function Dependency Link (8-bit)
346    pub const FDL: u8 = 0x16;
347    /// SR-IOV System Page Size (32-bit)
348    pub const SYS_PAGE_SIZE: u8 = 0x18;
349    /// SR-IOV BAR0 offset (32-bit)
350    pub const BAR0_OFFSET: u8 = 0x1C;
351    /// SR-IOV BAR1 offset (32-bit)
352    pub const BAR1_OFFSET: u8 = 0x20;
353    /// SR-IOV BAR2 offset (32-bit)
354    pub const BAR2_OFFSET: u8 = 0x24;
355    /// SR-IOV BAR3 offset (32-bit)
356    pub const BAR3_OFFSET: u8 = 0x28;
357    /// SR-IOV BAR4 offset (32-bit)
358    pub const BAR4_OFFSET: u8 = 0x2C;
359    /// SR-IOV BAR5 offset (32-bit)
360    pub const BAR5_OFFSET: u8 = 0x30;
361    /// SR-IOV VF Migration State Array Offset (32-bit)
362    pub const VF_MIGRATION_STATE: u8 = 0x3C;
363}
364
365/// SR-IOV Control register bits
366pub mod sriov_ctrl {
367    /// Enable SR-IOV
368    pub const ENABLE: u16 = 1 << 0;
369    /// Enable MSE (Memory Space Enable) for VFs
370    pub const MSE: u16 = 1 << 1;
371    /// Enable ARI (Alternate Routing ID)
372    pub const ARI_CAP: u16 = 1 << 2;
373    /// Enable IMS (Interrupt Mask Set) for VFs
374    pub const IMS_ENABLE: u16 = 1 << 4;
375    /// Enable PFARI (PF ARI Capability)
376    pub const PFARI_CAP: u16 = 1 << 5;
377    /// Enable VFARI (VF ARI Capability)
378    pub const VFARI_CAP: u16 = 1 << 6;
379    /// Enable VF Migration
380    pub const VF_MIGRATION: u16 = 1 << 7;
381}
382
383/// SR-IOV Status register bits
384pub mod sriov_status {
385    /// VF Memory Space Status
386    pub const VF_MEMORY_SPACE: u16 = 1 << 0;
387    /// VF ARI Capability Status
388    pub const VF_ARI_CAP: u16 = 1 << 1;
389    /// PF ARI Capability Status
390    pub const PF_ARI_CAP: u16 = 1 << 2;
391    /// VF Migration Status
392    pub const VF_MIGRATION_STATUS: u16 = 1 << 3;
393}
394
395/// AER (Advanced Error Reporting) capability register offsets.
396pub mod aer_cap {
397    /// AER Capability register (32-bit)
398    pub const CAP: u8 = 0x00;
399    /// AER UnCorrectable Error Status (32-bit)
400    pub const UNCERR_STATUS: u8 = 0x04;
401    /// AER UnCorrectable Error Mask (32-bit)
402    pub const UNCERR_MASK: u8 = 0x08;
403    /// AER UnCorrectable Error Severity (32-bit)
404    pub const UNCERR_SEVERITY: u8 = 0x0C;
405    /// AER Correctable Error Status (32-bit)
406    pub const CORERR_STATUS: u8 = 0x10;
407    /// AER Correctable Error Mask (32-bit)
408    pub const CORERR_MASK: u8 = 0x14;
409    /// AER Advanced Error Capabilities and Control (32-bit)
410    pub const ERR_CAP: u8 = 0x18;
411    /// AER Header Log (128-bit)
412    pub const HEADER_LOG: u8 = 0x1C;
413}
414
415/// Walk PCIe extended capabilities starting from `cap_ptr` (>= 0x100).
416///
417/// Returns a list of (capability_id, offset) pairs.
418pub fn walk_ext_capabilities(dev: &PciDevice, cap_ptr: u16) -> Vec<(u16, u16)> {
419    let mut caps = Vec::new();
420    let mut offset = cap_ptr;
421
422    while offset >= 0x100 && offset < 0xFFF {
423        let dword = match dev.read_config_u32_ext(offset) {
424            Some(v) => v,
425            None => break,
426        };
427        let cap_id = (dword & 0xFFFF) as u16;
428        let next_ptr = ((dword >> 20) & 0xFFC) as u16; // bits 31:20, aligned to 4
429
430        if cap_id == 0 {
431            break; // Invalid capability
432        }
433
434        caps.push((cap_id, offset));
435
436        if next_ptr == 0 || next_ptr == offset {
437            break; // End of list or self-loop
438        }
439        offset = next_ptr;
440    }
441
442    caps
443}
444
445/// Find the PCIe extended capability offset for a given capability ID.
446///
447/// Extended capabilities are always at offsets >= 0x100 in PCIe config space.
448/// Requires ECAM support (MCFG ACPI table) to read.
449pub fn find_ext_capability(dev: &PciDevice, cap_id: u16) -> Option<u16> {
450    if ECAM_REGIONS.lock().is_empty() {
451        return None; // No ECAM available, cannot read extended config space
452    }
453
454    for (found_id, offset) in walk_ext_capabilities(dev, 0x100) {
455        if found_id == cap_id {
456            return Some(offset);
457        }
458    }
459    None
460}
461
462/// Read SR-IOV capability information from a PCI device.
463///
464/// Returns `None` if the device doesn't have SR-IOV capability.
465pub fn read_sriov_info(dev: &PciDevice) -> Option<SriovInfo> {
466    let cap_offset = find_ext_capability(dev, ext_cap_id::SRIOV)?;
467    let control = dev.read_config_u32_ext(cap_offset + sriov_cap::CONTROL as u16)? as u16;
468    let status = dev.read_config_u32_ext(cap_offset + sriov_cap::STATUS as u16)? as u16;
469    let initial_vf = dev.read_config_u32_ext(cap_offset + sriov_cap::INITIAL_VF as u16)? as u16;
470    let total_vf = dev.read_config_u32_ext(cap_offset + sriov_cap::TOTAL_VF as u16)? as u16;
471    let num_vf = dev.read_config_u32_ext(cap_offset + sriov_cap::NUM_VF as u16)? as u16;
472
473    Some(SriovInfo {
474        cap_offset,
475        control,
476        status,
477        initial_vf,
478        total_vf,
479        num_vf,
480        enabled: (control & sriov_ctrl::ENABLE) != 0,
481    })
482}
483
484/// SR-IOV capability information.
485#[derive(Debug, Clone, Copy)]
486pub struct SriovInfo {
487    /// Offset of the SR-IOV capability in config space.
488    pub cap_offset: u16,
489    /// SR-IOV Control register value.
490    pub control: u16,
491    /// SR-IOV Status register value.
492    pub status: u16,
493    /// Number of initial VFs at power-on.
494    pub initial_vf: u16,
495    /// Total number of VFs supported.
496    pub total_vf: u16,
497    /// Number of VFs currently enabled.
498    pub num_vf: u16,
499    /// Whether SR-IOV is currently enabled.
500    pub enabled: bool,
501}
502
503/// Read AER (Advanced Error Reporting) capability information.
504pub fn read_aer_info(dev: &PciDevice) -> Option<AerInfo> {
505    let cap_offset = find_ext_capability(dev, ext_cap_id::AER)?;
506    let uncerr_status = dev.read_config_u32_ext(cap_offset + aer_cap::UNCERR_STATUS as u16)?;
507    let uncerr_mask = dev.read_config_u32_ext(cap_offset + aer_cap::UNCERR_MASK as u16)?;
508    let corerr_status = dev.read_config_u32_ext(cap_offset + aer_cap::CORERR_STATUS as u16)?;
509    let corerr_mask = dev.read_config_u32_ext(cap_offset + aer_cap::CORERR_MASK as u16)?;
510    let err_cap = dev.read_config_u32_ext(cap_offset + aer_cap::ERR_CAP as u16)?;
511
512    Some(AerInfo {
513        cap_offset,
514        uncerr_status,
515        uncerr_mask,
516        corerr_status,
517        corerr_mask,
518        first_error_pointer: ((err_cap >> 24) & 0x1F) as u8,
519        ecrc_error_capable: (err_cap & 0x01) != 0,
520        ecrc_generate_capable: (err_cap & 0x02) != 0,
521    })
522}
523
524/// AER (Advanced Error Reporting) capability information.
525#[derive(Debug, Clone, Copy)]
526pub struct AerInfo {
527    /// Offset of the AER capability in config space.
528    pub cap_offset: u16,
529    /// Uncorrectable error status bits.
530    pub uncerr_status: u32,
531    /// Uncorrectable error mask bits.
532    pub uncerr_mask: u32,
533    /// Correctable error status bits.
534    pub corerr_status: u32,
535    /// Correctable error mask bits.
536    pub corerr_mask: u32,
537    /// First error pointer (for error logging).
538    pub first_error_pointer: u8,
539    /// Whether ECRC error detection is supported.
540    pub ecrc_error_capable: bool,
541    /// Whether ECRC error generation is supported.
542    pub ecrc_generate_capable: bool,
543}
544
545/// Offsets within an MSI capability block (relative to capability base).
546pub mod msi_cap {
547    /// Capability ID (1 byte) + next ptr (1 byte) = 2 bytes header
548    /// Control register at +2 (16-bit)
549    pub const CONTROL: u8 = 0x02;
550    /// Message Address Register (32-bit) at +4
551    pub const ADDR_LOW: u8 = 0x04;
552    /// Message Upper Address Register (32-bit, 64-bit capable only) at +8
553    pub const ADDR_HIGH: u8 = 0x08;
554    /// Message Data Register (16-bit):
555    ///   - 32-bit capable: at +8
556    ///   - 64-bit capable: at +12
557    pub const DATA: u8 = 0x08;
558}
559
560/// MSI control register bit definitions (16-bit at cap_base + 2).
561pub mod msi_ctrl {
562    /// MSI Enable
563    pub const ENABLE: u16 = 1 << 0;
564    /// Multiple Message Enable (bits 4:6) : number of vectors allocated
565    pub const MME_MASK: u16 = 0b111 << 4;
566    /// Multiple Message Capable (bits 1:3) : number of vectors the device wants
567    pub const MMC_MASK: u16 = 0b111 << 1;
568    /// 64-bit addressing supported
569    pub const ADDR64: u16 = 1 << 7;
570    /// Per-vector masking supported
571    pub const PV_MASK: u16 = 1 << 8;
572}
573
574/// Offsets within an MSI-X capability block (relative to capability base).
575pub mod msix_cap {
576    /// Capability ID (1 byte) + next ptr (1 byte) = 2 bytes header
577    /// Control register at +2 (16-bit)
578    pub const CONTROL: u8 = 0x02;
579    /// Table offset / BAR indicator (32-bit) at +4
580    pub const TABLE: u8 = 0x04;
581    /// Pending Bit Array offset / BAR indicator (32-bit) at +8
582    pub const PBA: u8 = 0x08;
583}
584
585/// MSI-X control register bit definitions (16-bit at cap_base + 2).
586pub mod msix_ctrl {
587    /// MSI-X Enable
588    pub const ENABLE: u16 = 1 << 15;
589    /// Function Mask
590    pub const FUNC_MASK: u16 = 1 << 14;
591    /// Table size (bits 0:10) : number of entries minus 1
592    pub const TABLE_SIZE_MASK: u16 = (1 << 11) - 1;
593}
594
595/// x86 MSI message address (delivers to LAPIC via system bus).
596///
597/// Format:
598///   [31:20] = 0xFEE (fixed)
599///   [19:12] = Destination LAPIC ID
600///   [11:4]  = reserved
601///   [3]     = Redirection Hint (0 = physical destination)
602///   [2]     = Destination Mode (0 = physical)
603///   [1:0]   = 0b00
604pub const MSI_ADDR_BASE: u32 = 0xFEE0_0000;
605pub const MSI_ADDR_DEST_SHIFT: u32 = 12;
606
607/// PCI command register bits
608pub mod command {
609    pub const IO_SPACE: u16 = 1 << 0;
610    pub const MEMORY_SPACE: u16 = 1 << 1;
611    pub const BUS_MASTER: u16 = 1 << 2;
612    pub const SPECIAL_CYCLES: u16 = 1 << 3;
613    pub const MWI_ENABLE: u16 = 1 << 4;
614    pub const VGA_PALETTE_SNOOP: u16 = 1 << 5;
615    pub const PARITY_ERROR_RESPONSE: u16 = 1 << 6;
616    pub const STEPPING_CONTROL: u16 = 1 << 7;
617    pub const SERR_ENABLE: u16 = 1 << 8;
618    pub const FAST_BACK_TO_BACK: u16 = 1 << 9;
619    pub const INTERRUPT_DISABLE: u16 = 1 << 10;
620}
621
622/// Base Address Register (BAR) types
623#[derive(Debug, Clone, Copy, PartialEq, Eq)]
624pub enum Bar {
625    Io { port: u16 },
626    Memory32 { addr: u32, prefetchable: bool },
627    Memory64 { addr: u64, prefetchable: bool },
628}
629
630/// A PCI device location
631#[derive(Clone, Copy, PartialEq, Eq)]
632pub struct PciAddress {
633    pub bus: u8,
634    pub device: u8,
635    pub function: u8,
636}
637
638impl fmt::Debug for PciAddress {
639    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640        write!(f, "{:02x}:{:02x}.{}", self.bus, self.device, self.function)
641    }
642}
643
644impl PciAddress {
645    /// Create a new PCI address
646    pub const fn new(bus: u8, device: u8, function: u8) -> Self {
647        Self {
648            bus,
649            device,
650            function,
651        }
652    }
653
654    /// Convert to configuration address format
655    fn config_address(&self, offset: u8) -> u32 {
656        let bus = self.bus as u32;
657        let device = (self.device as u32) & 0x1F;
658        let function = (self.function as u32) & 0x07;
659        let offset = (offset as u32) & 0xFC;
660
661        0x8000_0000 | (bus << 16) | (device << 11) | (function << 8) | offset
662    }
663}
664
665/// PCI device information
666#[derive(Clone, Copy)]
667pub struct PciDevice {
668    pub address: PciAddress,
669    pub vendor_id: u16,
670    pub device_id: u16,
671    pub class_code: u8,
672    pub subclass: u8,
673    pub prog_if: u8,
674    pub revision: u8,
675    pub header_type: u8,
676    pub interrupt_line: u8,
677    pub interrupt_pin: u8,
678}
679
680impl fmt::Debug for PciDevice {
681    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
682        write!(
683            f,
684            "PciDevice({:?} ID {:04x}:{:04x} Class {:02x}:{:02x})",
685            self.address, self.vendor_id, self.device_id, self.class_code, self.subclass
686        )
687    }
688}
689
690impl PciDevice {
691    /// Read a configuration register (8-bit)
692    pub fn read_config_u8(&self, offset: u8) -> u8 {
693        let addr = self.address.config_address(offset & !0x03);
694        let shift = (offset & 0x03) * 8;
695
696        let _lock = PCI_IO_LOCK.lock();
697        unsafe {
698            outl(CONFIG_ADDRESS, addr);
699            ((inl(CONFIG_DATA) >> shift) & 0xFF) as u8
700        }
701    }
702
703    /// Read a configuration register (16-bit)
704    pub fn read_config_u16(&self, offset: u8) -> u16 {
705        let addr = self.address.config_address(offset & !0x03);
706        let shift = (offset & 0x02) * 8;
707
708        let _lock = PCI_IO_LOCK.lock();
709        unsafe {
710            outl(CONFIG_ADDRESS, addr);
711            ((inl(CONFIG_DATA) >> shift) & 0xFFFF) as u16
712        }
713    }
714
715    /// Read a configuration register (32-bit)
716    pub fn read_config_u32(&self, offset: u8) -> u32 {
717        let addr = self.address.config_address(offset);
718
719        let _lock = PCI_IO_LOCK.lock();
720        unsafe {
721            outl(CONFIG_ADDRESS, addr);
722            inl(CONFIG_DATA)
723        }
724    }
725
726    /// Read a 32-bit register from extended config space (offset >= 0x100).
727    ///
728    /// Uses ECAM MMIO when available. Returns `None` if ECAM is not mapped.
729    pub fn read_config_u32_ext(&self, offset: u16) -> Option<u32> {
730        let regions = ECAM_REGIONS.lock();
731        let ecam = regions.iter().find_map(|r| {
732            if self.address.bus >= r.start_bus && self.address.bus <= r.end_bus {
733                Some(r.base_virt)
734            } else {
735                None
736            }
737        })?;
738        Some(unsafe {
739            ecam_read32(
740                ecam,
741                self.address.bus,
742                self.address.device,
743                self.address.function,
744                offset,
745            )
746        })
747    }
748
749    /// Write to a configuration register (8-bit)
750    pub fn write_config_u8(&self, offset: u8, value: u8) {
751        let addr = self.address.config_address(offset & !0x03);
752        let shift = (offset & 0x03) * 8;
753
754        let _lock = PCI_IO_LOCK.lock();
755        unsafe {
756            outl(CONFIG_ADDRESS, addr);
757            let old = inl(CONFIG_DATA);
758            let mask = !(0xFF << shift);
759            let new = (old & mask) | ((value as u32) << shift);
760            outl(CONFIG_ADDRESS, addr);
761            outl(CONFIG_DATA, new);
762        }
763    }
764
765    /// Write to a configuration register (16-bit)
766    pub fn write_config_u16(&self, offset: u8, value: u16) {
767        let addr = self.address.config_address(offset & !0x03);
768        let shift = (offset & 0x02) * 8;
769
770        let _lock = PCI_IO_LOCK.lock();
771        unsafe {
772            outl(CONFIG_ADDRESS, addr);
773            let old = inl(CONFIG_DATA);
774            let mask = !(0xFFFF << shift);
775            let new = (old & mask) | ((value as u32) << shift);
776            outl(CONFIG_ADDRESS, addr);
777            outl(CONFIG_DATA, new);
778        }
779    }
780
781    /// Write to a configuration register (32-bit)
782    pub fn write_config_u32(&self, offset: u8, value: u32) {
783        let addr = self.address.config_address(offset);
784
785        let _lock = PCI_IO_LOCK.lock();
786        unsafe {
787            outl(CONFIG_ADDRESS, addr);
788            outl(CONFIG_DATA, value);
789        }
790    }
791
792    /// Read a Base Address Register (BAR)
793    pub fn read_bar(&self, bar_index: u8) -> Option<Bar> {
794        if bar_index > 5 {
795            return None;
796        }
797
798        let offset = config::BAR0 + (bar_index * 4);
799        let bar_low = self.read_config_u32(offset);
800
801        if bar_low == 0 {
802            return None;
803        }
804
805        // Check if it's an I/O BAR (bit 0 set)
806        if bar_low & 0x1 != 0 {
807            let port = (bar_low & 0xFFFF_FFFC) as u16;
808            Some(Bar::Io { port })
809        } else {
810            let bar_type = (bar_low >> 1) & 0x3;
811            let prefetchable = (bar_low >> 3) & 0x1 != 0;
812
813            match bar_type {
814                0 => {
815                    let addr = bar_low & 0xFFFF_FFF0;
816                    Some(Bar::Memory32 { addr, prefetchable })
817                }
818                2 => {
819                    if bar_index >= 5 {
820                        return None;
821                    }
822                    let bar_high = self.read_config_u32(offset + 4);
823                    let addr = ((bar_high as u64) << 32) | ((bar_low & 0xFFFF_FFF0) as u64);
824                    Some(Bar::Memory64 { addr, prefetchable })
825                }
826                _ => None,
827            }
828        }
829    }
830
831    /// Get the raw BAR value (for legacy compatibility)
832    pub fn read_bar_raw(&self, bar_index: u8) -> Option<u64> {
833        match self.read_bar(bar_index) {
834            Some(Bar::Io { port }) => Some(port as u64),
835            Some(Bar::Memory32 { addr, .. }) => Some(addr as u64),
836            Some(Bar::Memory64 { addr, .. }) => Some(addr),
837            None => None,
838        }
839    }
840
841    /// Enable bus mastering for this device
842    pub fn enable_bus_master(&self) {
843        let mut cmd = self.read_config_u16(config::COMMAND);
844        cmd |= command::BUS_MASTER;
845        self.write_config_u16(config::COMMAND, cmd);
846    }
847
848    /// Enable memory space access for this device
849    pub fn enable_memory_space(&self) {
850        let mut cmd = self.read_config_u16(config::COMMAND);
851        cmd |= command::MEMORY_SPACE;
852        self.write_config_u16(config::COMMAND, cmd);
853    }
854
855    /// Enable I/O space access for this device
856    pub fn enable_io_space(&self) {
857        let mut cmd = self.read_config_u16(config::COMMAND);
858        cmd |= command::IO_SPACE;
859        self.write_config_u16(config::COMMAND, cmd);
860    }
861}
862
863// ---------------------------------------------------------------------------
864// Fast PCI bus scanner (BFS with early-exit)
865// ---------------------------------------------------------------------------
866//
867// Insipired by asterinas OS's PCI scanner:
868//
869//  1. For each (bus, device), probe function 0 first.
870//     If vendor == 0xFFFF => skip all 8 functions (early exit).
871//
872//  2. Read the Header Type from the function-0 dword at offset 0x0C.
873//     Bit 7 (multi-function flag) tells whether functions 1..7 can exist.
874//     If bit 7 is clear => skip functions 1..7 entirely.
875//
876//  3. If header_type & 0x7F == 0x01 (PCI-to-PCI bridge), read the
877//     secondary bus number and enqueue it.  The `seen_buses` bitmap
878//     prevents re-scanning a bus already visited.
879//
880// This reduces the worst-case probes from 256 × 32 × 8 = 65 536 down to
881// 256 × 32 × 1 = 8 192 for a topology with no multi-function devices
882// (the common case on QEMU / VMware).
883//
884// I/O cost per probe:
885//   Old: 4 dword reads under 4 separate lock acquisitions.
886//   New: 1 dword read (vendor check, early exit) + 3 dword reads only
887//        for devices that actually exist, all under a single lock hold
888//        via `probe_device_full`.
889
890/// Iterator for scanning PCI bus
891pub struct PciScanner {
892    bus_queue: [u8; 256],
893    queue_head: usize,
894    queue_tail: usize,
895    seen_buses: [bool; 256],
896    device: u8,
897    function: u8,
898    /// Cached multi-function flag for the current device (from function 0).
899    /// When `function > 0`, this tells us whether to keep scanning.
900    is_multi_function: bool,
901}
902
903impl PciScanner {
904    pub fn new() -> Self {
905        let mut s = Self {
906            bus_queue: [0u8; 256],
907            queue_head: 0,
908            queue_tail: 1,
909            seen_buses: [false; 256],
910            device: 0,
911            function: 0,
912            is_multi_function: false,
913        };
914        s.seen_buses[0] = true;
915        s
916    }
917
918    fn enqueue_bus(&mut self, bus: u8) {
919        if !self.seen_buses[bus as usize] && self.queue_tail < 256 {
920            self.seen_buses[bus as usize] = true;
921            self.bus_queue[self.queue_tail] = bus;
922            self.queue_tail += 1;
923        }
924    }
925
926    #[inline]
927    fn advance_to_next_device(&mut self) {
928        self.function = 0;
929        self.device += 1;
930        self.is_multi_function = false;
931    }
932}
933
934impl Iterator for PciScanner {
935    type Item = PciDevice;
936
937    fn next(&mut self) -> Option<Self::Item> {
938        loop {
939            // Advance to next bus if we exhausted all 32 devices.
940            if self.queue_head >= self.queue_tail {
941                return None;
942            }
943            let bus = self.bus_queue[self.queue_head];
944
945            if self.device >= 32 {
946                self.queue_head += 1;
947                self.device = 0;
948                self.function = 0;
949                self.is_multi_function = false;
950                continue;
951            }
952
953            let current_function = self.function;
954
955            // --- Function 0: fast vendor check + early exit ---
956            if current_function == 0 {
957                // Single dword read: vendor+device at offset 0x00.
958                // If 0xFFFF => no device at this slot, skip all 8 functions.
959                let word00 = raw_config_read(bus, self.device, 0, 0x00);
960                let vendor_id = (word00 & 0xFFFF) as u16;
961                if is_absent_vendor(vendor_id) {
962                    self.advance_to_next_device();
963                    continue;
964                }
965
966                // Device exists at function 0 : do the full probe.
967                let Some(dev) = probe_from_word00(PciAddress::new(bus, self.device, 0), word00)
968                else {
969                    self.advance_to_next_device();
970                    continue;
971                };
972
973                // Cache multi-function status from header_type bit 7.
974                self.is_multi_function = dev.header_type & 0x80 != 0;
975
976                // If it's a PCI-to-PCI bridge, enqueue the secondary bus.
977                if dev.header_type & 0x7F == 0x01 {
978                    let secondary = raw_config_read_u8(bus, self.device, 0, 0x19);
979                    self.enqueue_bus(secondary);
980                }
981
982                // Advance: if multi-function, move to function 1;
983                // otherwise skip straight to the next device.
984                if self.is_multi_function {
985                    self.function = 1;
986                } else {
987                    self.advance_to_next_device();
988                }
989
990                return Some(dev);
991            }
992
993            // --- Functions 1..7 (only reached if multi-function) ---
994            debug_assert!(self.is_multi_function);
995
996            self.function += 1;
997            if self.function >= 8 {
998                self.advance_to_next_device();
999            }
1000
1001            let address = PciAddress::new(bus, self.device, current_function);
1002            let Some(dev) = probe_device_full(address) else {
1003                continue;
1004            };
1005
1006            if dev.header_type & 0x7F == 0x01 {
1007                let secondary = raw_config_read_u8(bus, self.device, current_function, 0x19);
1008                self.enqueue_bus(secondary);
1009            }
1010
1011            return Some(dev);
1012        }
1013    }
1014}
1015
1016// ---------------------------------------------------------------------------
1017// Low-level config-space helpers
1018// ---------------------------------------------------------------------------
1019
1020fn is_absent_vendor(vendor_id: u16) -> bool {
1021    vendor_id == 0xFFFF || vendor_id == 0x0000
1022}
1023
1024fn quirk_zero_irq_line(vendor_id: u16, device_id: u16, irq_line: u8) -> u8 {
1025    if irq_line != 0xFF {
1026        return irq_line;
1027    }
1028    if PCI_IRQ_LINE_ZERO_IF_FF
1029        .iter()
1030        .any(|q| q.vendor_id == vendor_id && q.device_id == device_id)
1031    {
1032        return 0;
1033    }
1034    0
1035}
1036
1037fn valid_header_type(header_type: u8) -> bool {
1038    matches!(header_type & 0x7F, 0x00..=0x02)
1039}
1040
1041fn is_ghost_device(class_code: u8, subclass: u8, prog_if: u8) -> bool {
1042    class_code == 0xFF && subclass == 0xFF && prog_if == 0xFF
1043}
1044
1045/// Single dword config read without building a PciDevice/PciAddress.
1046/// Acquires PCI_IO_LOCK once.
1047#[inline]
1048fn raw_config_read(bus: u8, device: u8, function: u8, offset: u8) -> u32 {
1049    let addr = 0x8000_0000u32
1050        | ((bus as u32) << 16)
1051        | (((device as u32) & 0x1F) << 11)
1052        | (((function as u32) & 0x07) << 8)
1053        | ((offset as u32) & 0xFC);
1054    let _lock = PCI_IO_LOCK.lock();
1055    unsafe {
1056        outl(CONFIG_ADDRESS, addr);
1057        inl(CONFIG_DATA)
1058    }
1059}
1060
1061/// Read a single byte from config space (derived from a dword read).
1062#[inline]
1063fn raw_config_read_u8(bus: u8, device: u8, function: u8, offset: u8) -> u8 {
1064    let dword = raw_config_read(bus, device, function, offset & !0x03);
1065    let shift = (offset & 0x03) * 8;
1066    ((dword >> shift) & 0xFF) as u8
1067}
1068
1069/// Probe a PCI address using 4 batched dword reads under a single lock hold.
1070///
1071/// Reads: 0x00 (vendor+device), 0x08 (rev+progif+subclass+class),
1072///        0x0C (cacheline+latency+headertype+bist), 0x3C (intline+intpin).
1073fn probe_device_full(address: PciAddress) -> Option<PciDevice> {
1074    let _lock = PCI_IO_LOCK.lock();
1075
1076    let word00 = unsafe {
1077        outl(CONFIG_ADDRESS, address.config_address(0x00));
1078        inl(CONFIG_DATA)
1079    };
1080    let vendor_id = (word00 & 0xFFFF) as u16;
1081    if is_absent_vendor(vendor_id) {
1082        return None;
1083    }
1084    let device_id = (word00 >> 16) as u16;
1085    if device_id == 0xFFFF || device_id == 0x0000 {
1086        return None;
1087    }
1088
1089    let word08 = unsafe {
1090        outl(CONFIG_ADDRESS, address.config_address(0x08));
1091        inl(CONFIG_DATA)
1092    };
1093    let word0c = unsafe {
1094        outl(CONFIG_ADDRESS, address.config_address(0x0C));
1095        inl(CONFIG_DATA)
1096    };
1097
1098    let header_type = ((word0c >> 16) & 0xFF) as u8;
1099    if !valid_header_type(header_type) {
1100        return None;
1101    }
1102
1103    let class_code = ((word08 >> 24) & 0xFF) as u8;
1104    let subclass = ((word08 >> 16) & 0xFF) as u8;
1105    let prog_if = ((word08 >> 8) & 0xFF) as u8;
1106    if is_ghost_device(class_code, subclass, prog_if) {
1107        return None;
1108    }
1109
1110    let word3c = unsafe {
1111        outl(CONFIG_ADDRESS, address.config_address(0x3C));
1112        inl(CONFIG_DATA)
1113    };
1114    let interrupt_line = quirk_zero_irq_line(vendor_id, device_id, (word3c & 0xFF) as u8);
1115
1116    Some(PciDevice {
1117        address,
1118        vendor_id,
1119        device_id,
1120        class_code,
1121        subclass,
1122        prog_if,
1123        revision: (word08 & 0xFF) as u8,
1124        header_type,
1125        interrupt_line,
1126        interrupt_pin: ((word3c >> 8) & 0xFF) as u8,
1127    })
1128}
1129
1130/// Build a PciDevice when `word00` (vendor+device dword) was already read
1131/// by the caller's fast-path vendor check, avoiding a redundant I/O cycle.
1132fn probe_from_word00(address: PciAddress, word00: u32) -> Option<PciDevice> {
1133    let vendor_id = (word00 & 0xFFFF) as u16;
1134    let device_id = (word00 >> 16) as u16;
1135    if device_id == 0xFFFF || device_id == 0x0000 {
1136        return None;
1137    }
1138
1139    let _lock = PCI_IO_LOCK.lock();
1140
1141    let word08 = unsafe {
1142        outl(CONFIG_ADDRESS, address.config_address(0x08));
1143        inl(CONFIG_DATA)
1144    };
1145    let word0c = unsafe {
1146        outl(CONFIG_ADDRESS, address.config_address(0x0C));
1147        inl(CONFIG_DATA)
1148    };
1149
1150    let header_type = ((word0c >> 16) & 0xFF) as u8;
1151    if !valid_header_type(header_type) {
1152        return None;
1153    }
1154
1155    let class_code = ((word08 >> 24) & 0xFF) as u8;
1156    let subclass = ((word08 >> 16) & 0xFF) as u8;
1157    let prog_if = ((word08 >> 8) & 0xFF) as u8;
1158    if is_ghost_device(class_code, subclass, prog_if) {
1159        return None;
1160    }
1161
1162    let word3c = unsafe {
1163        outl(CONFIG_ADDRESS, address.config_address(0x3C));
1164        inl(CONFIG_DATA)
1165    };
1166    let interrupt_line = quirk_zero_irq_line(vendor_id, device_id, (word3c & 0xFF) as u8);
1167
1168    Some(PciDevice {
1169        address,
1170        vendor_id,
1171        device_id,
1172        class_code,
1173        subclass,
1174        prog_if,
1175        revision: (word08 & 0xFF) as u8,
1176        header_type,
1177        interrupt_line,
1178        interrupt_pin: ((word3c >> 8) & 0xFF) as u8,
1179    })
1180}
1181
1182// ---------------------------------------------------------------------------
1183// Cached device inventory
1184// ---------------------------------------------------------------------------
1185
1186/// Cached PCI device inventory.
1187///
1188/// The first lookup performs a full bus scan, then all subsequent lookups reuse
1189/// this snapshot. Every query function borrows the cache through the lock and
1190/// operates on the `&[PciDevice]` directly : no `clone()` of the Vec.
1191///
1192/// # Invariant
1193///
1194/// `PCI_DEVICE_CACHE` is populated once at boot and never invalidated during
1195/// normal operation. The `SpinLock<Option<Vec<PciDevice>>>` is held only for
1196/// the initial scan (which allocates the Vec) and for subsequent reads. No
1197/// code path re-scans the bus or reallocates under the lock. If PCI hotplug
1198/// or re-enumeration is added in the future, this must be changed to either :
1199///   - a `Mutex` (sleepable) to allow re-scanning, or
1200///   - a lock-free double-buffered snapshot model.
1201static PCI_DEVICE_CACHE: SpinLock<Option<Vec<PciDevice>>> = SpinLock::new(None);
1202
1203// ---------------------------------------------------------------------------
1204// ECAM (PCIe Enhanced Configuration Access Mechanism) scanner
1205// ---------------------------------------------------------------------------
1206
1207/// Scan PCI devices via ECAM (Memory-Mapped Configuration).
1208///
1209/// Uses MCFG ACPI table entries to discover MMIO-mapped PCI config spaces.
1210/// For each ECAM region, scans all bus/device/function combinations.
1211/// Returns only devices not already found by the I/O port scanner.
1212fn scan_ecam_devices() -> Vec<PciDevice> {
1213    use crate::{acpi::mcfg, memory};
1214
1215    let mcfg_info = match mcfg::parse_mcfg() {
1216        Some(info) => info,
1217        None => {
1218            log::info!("[PCI-ECAM] No MCFG table found, skipping ECAM scan");
1219            return Vec::new();
1220        }
1221    };
1222
1223    log::info!(
1224        "[PCI-ECAM] Found {} ECAM region(s)",
1225        mcfg_info.entries.len()
1226    );
1227
1228    let mut devices = Vec::new();
1229
1230    for entry in &mcfg_info.entries {
1231        let ecam_phys = entry.base_address;
1232        let start_bus = entry.start_bus;
1233        let end_bus = entry.end_bus;
1234
1235        // Ensure the ECAM region is identity-mapped
1236        let region_size = ((end_bus as usize - start_bus as usize + 1) as u64) * (32 * 8 * 4096);
1237        memory::paging::ensure_identity_map_range(ecam_phys, region_size);
1238        let ecam_virt = crate::memory::phys_to_virt(ecam_phys) as usize;
1239
1240        // Cache ECAM region for extended config space reads
1241        ECAM_REGIONS.lock().push(EcamRegion {
1242            start_bus,
1243            end_bus,
1244            base_virt: ecam_virt,
1245        });
1246
1247        log::info!(
1248            "[PCI-ECAM] Region seg={} ecam={:#x} buses={}..{} virt={:#x}",
1249            entry.segment_group,
1250            ecam_phys,
1251            start_bus,
1252            end_bus,
1253            ecam_virt
1254        );
1255
1256        // Scan each bus in the range
1257        for bus in start_bus..=end_bus {
1258            for dev in 0..32u8 {
1259                // Fast vendor check on function 0
1260                let word00 = unsafe { ecam_read32(ecam_virt, bus, dev, 0, 0x00) };
1261                let vendor_id = (word00 & 0xFFFF) as u16;
1262                if is_absent_vendor(vendor_id) {
1263                    continue;
1264                }
1265
1266                // Probe function 0
1267                if let Some(device) = probe_ecam_device(ecam_virt, bus, dev, 0, word00) {
1268                    let is_multi = device.header_type & 0x80 != 0;
1269                    devices.push(device);
1270
1271                    // Scan additional functions if multi-function
1272                    if is_multi {
1273                        for func in 1..8u8 {
1274                            if let Some(device) = probe_ecam_device(ecam_virt, bus, dev, func, 0) {
1275                                devices.push(device);
1276                            }
1277                        }
1278                    }
1279                }
1280            }
1281        }
1282    }
1283
1284    log::info!("[PCI-ECAM] ECAM scan found {} device(s)", devices.len());
1285    devices
1286}
1287
1288/// Probe a single PCI device via ECAM MMIO.
1289fn probe_ecam_device(
1290    ecam_base: usize,
1291    bus: u8,
1292    device: u8,
1293    function: u8,
1294    word00: u32,
1295) -> Option<PciDevice> {
1296    let vendor_id = (word00 & 0xFFFF) as u16;
1297    let device_id = (word00 >> 16) as u16;
1298    if is_absent_vendor(vendor_id) || device_id == 0xFFFF || device_id == 0x0000 {
1299        return None;
1300    }
1301
1302    let address = PciAddress::new(bus, device, function);
1303
1304    let word08 = unsafe { ecam_read32(ecam_base, bus, device, function, 0x08) };
1305    let word0c = unsafe { ecam_read32(ecam_base, bus, device, function, 0x0C) };
1306
1307    let header_type = ((word0c >> 16) & 0xFF) as u8;
1308    if !valid_header_type(header_type) {
1309        return None;
1310    }
1311
1312    let class_code = ((word08 >> 24) & 0xFF) as u8;
1313    let subclass = ((word08 >> 16) & 0xFF) as u8;
1314    let prog_if = ((word08 >> 8) & 0xFF) as u8;
1315    if is_ghost_device(class_code, subclass, prog_if) {
1316        return None;
1317    }
1318
1319    let word3c = unsafe { ecam_read32(ecam_base, bus, device, function, 0x3C) };
1320    let interrupt_line = quirk_zero_irq_line(vendor_id, device_id, (word3c & 0xFF) as u8);
1321
1322    Some(PciDevice {
1323        address,
1324        vendor_id,
1325        device_id,
1326        class_code,
1327        subclass,
1328        prog_if,
1329        revision: (word08 & 0xFF) as u8,
1330        header_type,
1331        interrupt_line,
1332        interrupt_pin: ((word3c >> 8) & 0xFF) as u8,
1333    })
1334}
1335
1336/// Populate the cache if empty, then run `f` on the device slice.
1337///
1338/// All query functions route through here so that only a single scan ever
1339/// happens, and the lock is held for the duration of the filter : not for
1340/// the entire boot.
1341fn with_cache<R>(f: impl FnOnce(&[PciDevice]) -> R) -> R {
1342    let mut cache = PCI_DEVICE_CACHE.lock();
1343    if cache.is_none() {
1344        let dummy = 0u64;
1345        let rsp = &dummy as *const u64 as u64;
1346        crate::serial_println!("[PCI] Scanning PCI bus (rsp={:#x})...", rsp);
1347
1348        // Phase 1: I/O port scan (0xCF8/0xCFC)
1349        let io_devices: Vec<PciDevice> = PciScanner::new().collect();
1350        crate::serial_println!("[PCI] I/O scan: {} device(s)", io_devices.len());
1351
1352        // Phase 2: ECAM MMIO scan (via MCFG ACPI table)
1353        let ecam_devices: Vec<PciDevice> = scan_ecam_devices();
1354        crate::serial_println!("[PCI] ECAM scan: {} device(s)", ecam_devices.len());
1355
1356        // Merge: ECAM devices that are NOT already in the I/O scan
1357        let mut devices = io_devices;
1358        for ecam_dev in &ecam_devices {
1359            let duplicate = devices.iter().any(|d| {
1360                d.address.bus == ecam_dev.address.bus
1361                    && d.address.device == ecam_dev.address.device
1362                    && d.address.function == ecam_dev.address.function
1363            });
1364            if !duplicate {
1365                devices.push(*ecam_dev);
1366            }
1367        }
1368        crate::serial_println!("[PCI] Total: {} device(s) after merge", devices.len());
1369
1370        // Improved logging: human-readable names, IRQ, class
1371        for dev in &devices {
1372            let name = device_name(dev.vendor_id, dev.device_id).unwrap_or("Unknown");
1373            let class = class_name(dev.class_code, dev.subclass).unwrap_or("???");
1374            crate::serial_println!(
1375                "[PCI]   {:02x}:{:02x}.{:x} {:<24} {} (IRQ={})",
1376                dev.address.bus,
1377                dev.address.device,
1378                dev.address.function,
1379                name,
1380                class,
1381                dev.interrupt_line,
1382            );
1383        }
1384        *cache = Some(devices);
1385    }
1386    f(cache.as_deref().unwrap_or(&[]))
1387}
1388
1389/// Helper to find a device by vendor and device ID
1390pub fn find_device(vendor_id: u16, device_id: u16) -> Option<PciDevice> {
1391    with_cache(|devs| {
1392        devs.iter()
1393            .copied()
1394            .find(|dev| dev.vendor_id == vendor_id && dev.device_id == device_id)
1395    })
1396}
1397
1398/// Find all VirtIO devices on the PCI bus
1399pub fn find_virtio_devices() -> Vec<PciDevice> {
1400    find_devices_by_vendor(vendor::VIRTIO)
1401}
1402
1403/// Find a specific VirtIO device by device ID
1404pub fn find_virtio_device(device_id: u16) -> Option<PciDevice> {
1405    find_device(vendor::VIRTIO, device_id)
1406}
1407
1408/// Return a snapshot of all discovered PCI devices.
1409pub fn all_devices() -> Vec<PciDevice> {
1410    with_cache(|devs| devs.to_vec())
1411}
1412
1413/// Return all devices for a given vendor from the cached PCI inventory.
1414pub fn find_devices_by_vendor(vendor_id: u16) -> Vec<PciDevice> {
1415    with_cache(|devs| {
1416        devs.iter()
1417            .copied()
1418            .filter(|dev| dev.vendor_id == vendor_id)
1419            .collect()
1420    })
1421}
1422
1423/// Return all devices matching a PCI class/subclass pair.
1424pub fn find_devices_by_class(class_code: u8, subclass: u8) -> Vec<PciDevice> {
1425    with_cache(|devs| {
1426        devs.iter()
1427            .copied()
1428            .filter(|dev| dev.class_code == class_code && dev.subclass == subclass)
1429            .collect()
1430    })
1431}
1432
1433/// Full PCI probe criteria.
1434///
1435/// Any field left as `None` is treated as a wildcard.
1436#[derive(Debug, Clone, Copy, Default)]
1437pub struct ProbeCriteria {
1438    pub vendor_id: Option<u16>,
1439    pub device_id: Option<u16>,
1440    pub class_code: Option<u8>,
1441    pub subclass: Option<u8>,
1442    pub prog_if: Option<u8>,
1443}
1444
1445impl ProbeCriteria {
1446    pub const fn any() -> Self {
1447        Self {
1448            vendor_id: None,
1449            device_id: None,
1450            class_code: None,
1451            subclass: None,
1452            prog_if: None,
1453        }
1454    }
1455
1456    fn matches(&self, dev: &PciDevice) -> bool {
1457        if self.vendor_id.is_some_and(|v| dev.vendor_id != v) {
1458            return false;
1459        }
1460        if self.device_id.is_some_and(|d| dev.device_id != d) {
1461            return false;
1462        }
1463        if self.class_code.is_some_and(|c| dev.class_code != c) {
1464            return false;
1465        }
1466        if self.subclass.is_some_and(|s| dev.subclass != s) {
1467            return false;
1468        }
1469        if self.prog_if.is_some_and(|p| dev.prog_if != p) {
1470            return false;
1471        }
1472        true
1473    }
1474}
1475
1476/// Return all devices matching `criteria`.
1477pub fn probe_all(criteria: ProbeCriteria) -> Vec<PciDevice> {
1478    with_cache(|devs| {
1479        devs.iter()
1480            .copied()
1481            .filter(|dev| criteria.matches(dev))
1482            .collect()
1483    })
1484}
1485
1486/// Return the first device matching `criteria`.
1487pub fn probe_first(criteria: ProbeCriteria) -> Option<PciDevice> {
1488    with_cache(|devs| devs.iter().copied().find(|dev| criteria.matches(dev)))
1489}
1490
1491/// Invalidate PCI cache.
1492///
1493/// Useful when hotplug/re-enumeration support is added in the future.
1494pub fn invalidate_cache() {
1495    *PCI_DEVICE_CACHE.lock() = None;
1496}