Files
MontaukOS/kernel/src/Pci/Pci.cpp
T

718 lines
29 KiB
C++

/*
* Pci.cpp
* PCI Express enumeration and configuration space access
* Copyright (c) 2025 Daniel Hammer
*/
#include "Pci.hpp"
#include <Arch/Interrupts.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Io/IoPort.hpp>
#include <CppLib/Spinlock.hpp>
#include <Timekeeping/Timer.hpp>
using namespace Kt;
namespace Pci {
// Legacy PCI config I/O ports
static constexpr uint16_t ConfigAddressPort = 0xCF8;
static constexpr uint16_t ConfigDataPort = 0xCFC;
// CF8/CFC is one machine-wide address/data latch, not a per-CPU interface.
// Keep the address write and matching data access indivisible: otherwise a
// second CPU can replace CF8 between them and make us read or write an
// unrelated device's register. Besides bogus probing, a crossed runtime
// command/MSI write can disable interrupts or bus mastering underneath an
// active GPU, USB controller, NIC, or HDA controller.
static kcp::Spinlock g_legacyConfigLock;
// PCI config space register offsets
static constexpr uint16_t RegVendorId = 0x00;
static constexpr uint16_t RegDeviceId = 0x02;
static constexpr uint16_t RegCommand = 0x04;
static constexpr uint16_t RegStatus = 0x06;
static constexpr uint16_t RegRevisionId = 0x08;
static constexpr uint16_t RegProgIf = 0x09;
static constexpr uint16_t RegSubClass = 0x0A;
static constexpr uint16_t RegClassCode = 0x0B;
static constexpr uint16_t RegHeaderType = 0x0E;
static kcp::vector<PciDevice> g_devices{};
// State: ECAM base (0 if not available, use legacy)
static uint64_t g_ecamBase = 0;
static uint8_t g_ecamStartBus = 0;
static uint8_t g_ecamEndBus = 0;
static bool g_useEcam = false;
// -------------------------------------------------------------------------
// MCFG table discovery (same pattern as MADT)
// -------------------------------------------------------------------------
static bool SignatureMatch(const char* sig, const char* target, int len) {
for (int i = 0; i < len; i++) {
if (sig[i] != target[i]) return false;
}
return true;
}
static Hal::ACPI::CommonSDTHeader* FindMcfgInXsdt(Hal::ACPI::CommonSDTHeader* xsdt) {
uint32_t entryCount = (xsdt->Length - sizeof(Hal::ACPI::CommonSDTHeader)) / 8;
uint64_t* entries = (uint64_t*)((uint64_t)xsdt + sizeof(Hal::ACPI::CommonSDTHeader));
for (uint32_t i = 0; i < entryCount; i++) {
auto* header = (Hal::ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]);
if (SignatureMatch(header->Signature, "MCFG", 4)) {
return header;
}
}
return nullptr;
}
static bool ParseMcfg(Hal::ACPI::CommonSDTHeader* xsdt) {
auto* mcfgHeader = FindMcfgInXsdt(xsdt);
if (mcfgHeader == nullptr) {
KernelLogStream(WARNING, "PCI") << "MCFG table not found, falling back to legacy config access";
return false;
}
if (!Hal::ACPI::TestChecksum(mcfgHeader)) {
KernelLogStream(ERROR, "PCI") << "MCFG checksum failed";
return false;
}
KernelLogStream(OK, "PCI") << "Found MCFG table";
auto* mcfg = (McfgHeader*)mcfgHeader;
uint32_t entriesSize = mcfg->SDTHeader.Length - sizeof(McfgHeader);
uint32_t entryCount = entriesSize / sizeof(McfgEntry);
if (entryCount == 0) {
KernelLogStream(WARNING, "PCI") << "MCFG contains no entries";
return false;
}
auto* entries = (McfgEntry*)((uint64_t)mcfg + sizeof(McfgHeader));
// Use the first MCFG entry (segment group 0)
g_ecamBase = entries[0].BaseAddress;
g_ecamStartBus = entries[0].StartBus;
g_ecamEndBus = entries[0].EndBus;
KernelLogStream(INFO, "PCI") << "ECAM base: " << base::hex << g_ecamBase
<< " buses " << base::dec << (uint64_t)g_ecamStartBus
<< "-" << (uint64_t)g_ecamEndBus;
// Map the ECAM MMIO region
// Size = (endBus - startBus + 1) * 32 devices * 8 functions * 4096 bytes
uint64_t busCount = (uint64_t)(g_ecamEndBus - g_ecamStartBus + 1);
uint64_t ecamSize = busCount * 32 * 8 * 4096;
if (Memory::VMM::g_paging) {
for (uint64_t offset = 0; offset < ecamSize; offset += 0x1000) {
uint64_t phys = g_ecamBase + offset;
Memory::VMM::g_paging->MapMMIO(phys, Memory::HHDM(phys));
}
KernelLogStream(DEBUG, "PCI") << "Mapped ECAM region: " << base::hex << ecamSize << " bytes";
}
g_useEcam = true;
return true;
}
// -------------------------------------------------------------------------
// ECAM (memory-mapped) configuration space access
// -------------------------------------------------------------------------
static volatile uint8_t* EcamAddress(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
uint64_t phys = ecamBase
+ ((uint64_t)bus << 20)
+ ((uint64_t)device << 15)
+ ((uint64_t)function << 12)
+ offset;
return (volatile uint8_t*)Memory::HHDM(phys);
}
uint8_t EcamRead8(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
return *EcamAddress(ecamBase, bus, device, function, offset);
}
uint16_t EcamRead16(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
return *(volatile uint16_t*)EcamAddress(ecamBase, bus, device, function, offset);
}
uint32_t EcamRead32(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
return *(volatile uint32_t*)EcamAddress(ecamBase, bus, device, function, offset);
}
void EcamWrite8(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t value) {
*EcamAddress(ecamBase, bus, device, function, offset) = value;
}
void EcamWrite16(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint16_t value) {
*(volatile uint16_t*)EcamAddress(ecamBase, bus, device, function, offset) = value;
}
void EcamWrite32(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint32_t value) {
*(volatile uint32_t*)EcamAddress(ecamBase, bus, device, function, offset) = value;
}
// -------------------------------------------------------------------------
// Legacy PCI configuration space access (I/O ports 0xCF8/0xCFC)
// -------------------------------------------------------------------------
static uint32_t LegacyBuildAddress(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
return (1u << 31) // Enable bit
| ((uint32_t)bus << 16)
| ((uint32_t)(device & 0x1F) << 11)
| ((uint32_t)(function & 0x07) << 8)
| (offset & 0xFC);
}
static uint32_t LegacyRead32Unlocked(uint8_t bus, uint8_t device,
uint8_t function, uint8_t offset) {
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
return Io::In32(ConfigDataPort);
}
static void LegacyWrite32Unlocked(uint8_t bus, uint8_t device,
uint8_t function, uint8_t offset,
uint32_t value) {
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
Io::Out32(value, ConfigDataPort);
}
// Drivers reach config space through the Legacy* calls below. Without
// an I/O address space (ARM) there is no CF8/CFC mechanism, so they use
// ECAM there, at the access's own width (16/8-bit writes must not
// read-modify-write a neighbouring RW1C register).
static bool EcamCovers(uint8_t bus);
static bool UseEcamForLegacy(uint8_t bus) {
return !Io::HasPortIo && EcamCovers(bus);
}
uint32_t LegacyRead32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
if (UseEcamForLegacy(bus)) {
return EcamRead32(g_ecamBase, bus, device, function, offset & 0xFC);
}
g_legacyConfigLock.Acquire();
uint32_t value = LegacyRead32Unlocked(bus, device, function, offset);
g_legacyConfigLock.Release();
return value;
}
uint16_t LegacyRead16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
uint32_t val = LegacyRead32(bus, device, function, offset & 0xFC);
return (uint16_t)(val >> ((offset & 2) * 8));
}
uint8_t LegacyRead8(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
uint32_t val = LegacyRead32(bus, device, function, offset & 0xFC);
return (uint8_t)(val >> ((offset & 3) * 8));
}
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value) {
if (UseEcamForLegacy(bus)) {
EcamWrite32(g_ecamBase, bus, device, function, offset & 0xFC, value);
return;
}
g_legacyConfigLock.Acquire();
LegacyWrite32Unlocked(bus, device, function, offset, value);
g_legacyConfigLock.Release();
}
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value) {
if (UseEcamForLegacy(bus)) {
EcamWrite16(g_ecamBase, bus, device, function, offset & 0xFE, value);
return;
}
g_legacyConfigLock.Acquire();
uint32_t tmp = LegacyRead32Unlocked(bus, device, function, offset & 0xFC);
int shift = (offset & 2) * 8;
tmp &= ~(0xFFFF << shift);
tmp |= ((uint32_t)value << shift);
LegacyWrite32Unlocked(bus, device, function, offset & 0xFC, tmp);
g_legacyConfigLock.Release();
}
void LegacyWrite8(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint8_t value) {
if (UseEcamForLegacy(bus)) {
EcamWrite8(g_ecamBase, bus, device, function, offset, value);
return;
}
g_legacyConfigLock.Acquire();
uint32_t tmp = LegacyRead32Unlocked(bus, device, function, offset & 0xFC);
int shift = (offset & 3) * 8;
tmp &= ~(0xFF << shift);
tmp |= ((uint32_t)value << shift);
LegacyWrite32Unlocked(bus, device, function, offset & 0xFC, tmp);
g_legacyConfigLock.Release();
}
// -------------------------------------------------------------------------
// Unified read helpers (use ECAM if available, else legacy)
// -------------------------------------------------------------------------
static uint16_t ReadConfig16(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
if (g_useEcam) {
return EcamRead16(g_ecamBase, bus, device, function, offset);
}
return LegacyRead16(bus, device, function, (uint8_t)offset);
}
static uint8_t ReadConfig8(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset) {
if (g_useEcam) {
return EcamRead8(g_ecamBase, bus, device, function, offset);
}
return LegacyRead8(bus, device, function, (uint8_t)offset);
}
static bool EcamCovers(uint8_t bus) {
return g_useEcam && bus >= g_ecamStartBus && bus <= g_ecamEndBus;
}
uint32_t ConfigRead(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width) {
if (device > 31 || function > 7 || offset > 0xFFF || (offset & (width - 1)))
return 0xFFFFFFFF;
if (EcamCovers(bus)) {
switch (width) {
case 1: return EcamRead8(g_ecamBase, bus, device, function, offset);
case 2: return EcamRead16(g_ecamBase, bus, device, function, offset);
case 4: return EcamRead32(g_ecamBase, bus, device, function, offset);
default: return 0xFFFFFFFF;
}
}
if (offset > 0xFF) return 0xFFFFFFFF;
switch (width) {
case 1: return LegacyRead8(bus, device, function, (uint8_t)offset);
case 2: return LegacyRead16(bus, device, function, (uint8_t)offset);
case 4: return LegacyRead32(bus, device, function, (uint8_t)offset);
default: return 0xFFFFFFFF;
}
}
bool ConfigWrite(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width, uint32_t value) {
if (device > 31 || function > 7 || offset > 0xFFF || (offset & (width - 1)))
return false;
if (EcamCovers(bus)) {
switch (width) {
case 1: EcamWrite8(g_ecamBase, bus, device, function, offset, (uint8_t)value); return true;
case 2: EcamWrite16(g_ecamBase, bus, device, function, offset, (uint16_t)value); return true;
case 4: EcamWrite32(g_ecamBase, bus, device, function, offset, value); return true;
default: return false;
}
}
if (offset > 0xFF) return false;
switch (width) {
case 1: LegacyWrite8(bus, device, function, (uint8_t)offset, (uint8_t)value); return true;
case 2: LegacyWrite16(bus, device, function, (uint8_t)offset, (uint16_t)value); return true;
case 4: LegacyWrite32(bus, device, function, (uint8_t)offset, value); return true;
default: return false;
}
}
// -------------------------------------------------------------------------
// PCI capability list traversal
// -------------------------------------------------------------------------
uint8_t FindCapability(uint8_t bus, uint8_t device, uint8_t function, uint8_t capId) {
// Check Status register bit 4 (Capabilities List present)
uint16_t status = ReadConfig16(bus, device, function, RegStatus);
if (!(status & (1 << 4))) {
return 0;
}
// Read Capabilities Pointer (offset 0x34), mask to dword-aligned
uint8_t offset = ReadConfig8(bus, device, function, 0x34) & 0xFC;
// Walk the conventional 256-byte capability list (cap_id @ +0,
// next_ptr @ +1). Firmware owns these pointers, so reject offsets
// below the capability area and cycles instead of allowing a corrupt
// list to spin forever during device initialization or resume.
uint64_t visited = 0;
while (offset != 0) {
if (offset < 0x40) return 0;
uint64_t bit = 1ULL << (offset >> 2);
if (visited & bit) return 0;
visited |= bit;
uint8_t id = ReadConfig8(bus, device, function, offset);
if (id == capId) {
return offset;
}
offset = ReadConfig8(bus, device, function, offset + 1) & 0xFC;
}
return 0;
}
bool WriteMsiMessage(uint8_t bus, uint8_t device, uint8_t function, uint8_t cap, uint8_t irq) {
Arch::MsiMessage msi;
if (!Arch::ComposeMsi(irq, msi)) {
return false;
}
uint16_t msgCtrl = LegacyRead16(bus, device, function, cap + 2);
LegacyWrite32(bus, device, function, cap + 4, (uint32_t)msi.address);
if (msgCtrl & (1 << 7)) {
// 64-bit: Upper Address at cap+8, Data at cap+12
LegacyWrite32(bus, device, function, cap + 8, (uint32_t)(msi.address >> 32));
LegacyWrite16(bus, device, function, cap + 12, (uint16_t)msi.data);
} else {
// 32-bit: Data at cap+8
LegacyWrite16(bus, device, function, cap + 8, (uint16_t)msi.data);
}
return true;
}
bool DisableInterruptDelivery(uint8_t bus, uint8_t device, uint8_t function) {
uint16_t command = LegacyRead16(bus, device, function,
(uint8_t)PCI_REG_COMMAND);
if (!(command & PCI_CMD_INTX_DISABLE)) {
LegacyWrite16(bus, device, function, (uint8_t)PCI_REG_COMMAND,
command | PCI_CMD_INTX_DISABLE);
}
bool disabledMessageDelivery = false;
uint8_t msi = FindCapability(bus, device, function, PCI_CAP_MSI);
if (msi != 0) {
uint16_t control = LegacyRead16(bus, device, function, msi + 2);
if (control & 1) {
LegacyWrite16(bus, device, function, msi + 2, control & ~1u);
disabledMessageDelivery = true;
}
}
uint8_t msix = FindCapability(bus, device, function, PCI_CAP_MSIX);
if (msix != 0) {
uint16_t control = LegacyRead16(bus, device, function, msix + 2);
if (control & (1u << 15)) {
// Function-mask before clearing MSI-X Enable so no vector can
// escape during the ownership transition.
uint16_t disabled = (control | (1u << 14)) & ~(1u << 15);
LegacyWrite16(bus, device, function, msix + 2, disabled);
disabledMessageDelivery = true;
}
}
return disabledMessageDelivery;
}
// -------------------------------------------------------------------------
// PCI class code names
// -------------------------------------------------------------------------
const char* GetClassName(uint8_t classCode, uint8_t subClass) {
switch (classCode) {
case 0x00:
return "Unclassified";
case 0x01:
switch (subClass) {
case 0x00: return "SCSI Bus Controller";
case 0x01: return "IDE Controller";
case 0x02: return "Floppy Disk Controller";
case 0x05: return "ATA Controller";
case 0x06: return "SATA Controller";
case 0x08: return "NVM Controller";
default: return "Mass Storage Controller";
}
case 0x02:
switch (subClass) {
case 0x00: return "Ethernet Controller";
case 0x80: return "Other Network Controller";
default: return "Network Controller";
}
case 0x03:
switch (subClass) {
case 0x00: return "VGA Compatible Controller";
case 0x01: return "XGA Controller";
case 0x02: return "3D Controller";
default: return "Display Controller";
}
case 0x04:
return "Multimedia Controller";
case 0x05:
return "Memory Controller";
case 0x06:
switch (subClass) {
case 0x00: return "Host Bridge";
case 0x01: return "ISA Bridge";
case 0x04: return "PCI-to-PCI Bridge";
case 0x80: return "Other Bridge";
default: return "Bridge Device";
}
case 0x07:
return "Simple Communication Controller";
case 0x08:
return "Base System Peripheral";
case 0x09:
return "Input Device Controller";
case 0x0A:
return "Docking Station";
case 0x0B:
return "Processor";
case 0x0C:
switch (subClass) {
case 0x03: return "USB Controller";
case 0x05: return "SMBus Controller";
default: return "Serial Bus Controller";
}
case 0x0D:
return "Wireless Controller";
case 0x0E:
return "Intelligent Controller";
case 0x0F:
return "Satellite Communication Controller";
case 0x10:
return "Encryption Controller";
case 0x11:
return "Signal Processing Controller";
case 0xFF:
return "Unassigned";
default:
return "Unknown";
}
}
// -------------------------------------------------------------------------
// Device enumeration
// -------------------------------------------------------------------------
static void EnumerateFunction(uint8_t bus, uint8_t device, uint8_t function) {
uint16_t vendorId = ReadConfig16(bus, device, function, RegVendorId);
if (vendorId == 0xFFFF) {
return;
}
PciDevice dev{};
dev.Segment = 0;
dev.Bus = bus;
dev.Device = device;
dev.Function = function;
dev.VendorId = vendorId;
dev.DeviceId = ReadConfig16(bus, device, function, RegDeviceId);
dev.ClassCode = ReadConfig8(bus, device, function, RegClassCode);
dev.SubClass = ReadConfig8(bus, device, function, RegSubClass);
dev.ProgIf = ReadConfig8(bus, device, function, RegProgIf);
dev.RevisionId = ReadConfig8(bus, device, function, RegRevisionId);
dev.HeaderType = ReadConfig8(bus, device, function, RegHeaderType);
g_devices.push_back(dev);
KernelLogStream(DEBUG, "PCI") << base::hex
<< (uint64_t)bus << ":" << (uint64_t)device << "." << (uint64_t)function
<< " " << (uint64_t)vendorId << ":" << (uint64_t)dev.DeviceId
<< " " << GetClassName(dev.ClassCode, dev.SubClass)
<< " (class " << (uint64_t)dev.ClassCode << "." << (uint64_t)dev.SubClass << ")";
}
static void EnumerateDevice(uint8_t bus, uint8_t device) {
uint16_t vendorId = ReadConfig16(bus, device, 0, RegVendorId);
if (vendorId == 0xFFFF) {
return;
}
// Always check function 0
EnumerateFunction(bus, device, 0);
// Check if multi-function device (bit 7 of header type)
uint8_t headerType = ReadConfig8(bus, device, 0, RegHeaderType);
if (headerType & 0x80) {
for (uint8_t func = 1; func < 8; func++) {
EnumerateFunction(bus, device, func);
}
}
}
static void EnumerateBus(uint8_t bus) {
for (uint8_t device = 0; device < 32; device++) {
EnumerateDevice(bus, device);
}
}
static void EnumerateAll() {
uint8_t startBus = g_useEcam ? g_ecamStartBus : 0;
uint8_t endBus = g_useEcam ? g_ecamEndBus : 255;
for (uint32_t bus = startBus; bus <= (uint32_t)endBus; bus++) {
EnumerateBus((uint8_t)bus);
}
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
kcp::vector<PciDevice>& GetDevices() {
return g_devices;
}
// -------------------------------------------------------------------------
// Shared PCI utilities
// -------------------------------------------------------------------------
uint64_t ReadBar0(uint8_t bus, uint8_t device, uint8_t function) {
uint32_t bar0Low = LegacyRead32(bus, device, function, (uint8_t)PCI_REG_BAR0);
// Bit 0 == 1 means this is an I/O-space BAR, not memory. The low bits of
// an I/O BAR are a port number, not a physical address, and must never be
// fed to MapMMIO (it would panic on the non-page-aligned value). Callers
// treat 0 as "no usable MMIO BAR". (e.g. VirtualBox AC'97 8086:2415.)
if (bar0Low & 0x1)
return 0;
uint64_t addr = bar0Low & 0xFFFFFFF0u;
// Check for 64-bit BAR (type field bits 2:1 == 0b10)
if ((bar0Low & 0x06) == 0x04) {
uint32_t bar0High = LegacyRead32(bus, device, function, (uint8_t)(PCI_REG_BAR0 + 4));
addr |= ((uint64_t)bar0High << 32);
}
return addr;
}
void EnableBusMaster(uint8_t bus, uint8_t device, uint8_t function) {
uint16_t cmd = LegacyRead16(bus, device, function, (uint8_t)PCI_REG_COMMAND);
cmd |= PCI_CMD_MEM_SPACE | PCI_CMD_BUS_MASTER;
LegacyWrite16(bus, device, function, (uint8_t)PCI_REG_COMMAND, cmd);
}
bool IsBridge(const PciDevice& dev) {
return (dev.HeaderType & 0x7F) == 0x01;
}
// PMCSR lives at PM capability + 4; its low two bits are the D-state.
static constexpr uint16_t PmcsrOffset = 4;
int GetPowerState(uint8_t bus, uint8_t device, uint8_t function) {
if (ReadConfig16(bus, device, function, RegVendorId) == 0xFFFF) return -1;
uint8_t pm = FindCapability(bus, device, function, PCI_CAP_PM);
if (pm == 0) return -1;
return (int)(ReadConfig16(bus, device, function, pm + PmcsrOffset) & 0x3);
}
bool SetPowerState(uint8_t bus, uint8_t device, uint8_t function, int state) {
if (state < 0 || state > 3) return false;
uint8_t pm = FindCapability(bus, device, function, PCI_CAP_PM);
if (pm == 0) return false;
// D1/D2 are optional; PMC bits 25/26 advertise them.
uint32_t pmc = ConfigRead(bus, device, function, pm, 4);
if (state == 1 && !(pmc & (1u << 25))) return false;
if (state == 2 && !(pmc & (1u << 26))) return false;
uint16_t pmcsr = (uint16_t)ConfigRead(bus, device, function, pm + PmcsrOffset, 2);
int current = pmcsr & 0x3;
if (current == state) return true;
// Keep PME_Status (bit 15, write-1-to-clear) from being cleared by
// accident; only the state field changes.
pmcsr = (uint16_t)((pmcsr & ~0x8003u) | (uint16_t)state);
ConfigWrite(bus, device, function, pm + PmcsrOffset, 2, pmcsr);
// PCI PM 1.2 table 5-6: 10 ms into or out of D3hot, 200 us for D2.
if (state == 3 || current == 3) Timekeeping::BusyWaitMs(10);
else if (state == 2 || current == 2) Timekeeping::BusyWaitUs(200);
return GetPowerState(bus, device, function) == state;
}
// -------------------------------------------------------------------------
// PCI driver matching
// -------------------------------------------------------------------------
static bool MatchDriver(const PciDriverDesc& drv, const PciDevice& dev) {
if (drv.VendorId != 0 && drv.VendorId != dev.VendorId)
return false;
if (drv.ClassCode != 0xFF && drv.ClassCode != dev.ClassCode)
return false;
if (drv.SubClass != 0xFF && drv.SubClass != dev.SubClass)
return false;
if (drv.ProgIf != 0xFF && drv.ProgIf != dev.ProgIf)
return false;
if (drv.DeviceIdCount > 0 && drv.DeviceIds != nullptr) {
bool found = false;
for (uint16_t i = 0; i < drv.DeviceIdCount; i++) {
if (drv.DeviceIds[i] == dev.DeviceId) {
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
void ProbeAll(const PciDriverDesc* table, uint16_t count, ProbePhase phase) {
for (uint64_t d = 0; d < g_devices.size(); d++) {
PciDevice& dev = g_devices[d];
if (dev.Claimed || dev.Reserved)
continue;
for (uint16_t i = 0; i < count; i++) {
if (table[i].Phase != phase)
continue;
if (!MatchDriver(table[i], dev))
continue;
KernelLogStream(INFO, "PCI") << "Probing " << table[i].Name
<< " for device " << base::hex
<< (uint64_t)dev.Bus << ":" << (uint64_t)dev.Device
<< "." << (uint64_t)dev.Function;
if (table[i].Probe(dev)) {
dev.Claimed = true;
KernelLogStream(OK, "PCI") << table[i].Name << " claimed device "
<< base::hex << (uint64_t)dev.Bus << ":"
<< (uint64_t)dev.Device << "." << (uint64_t)dev.Function;
break; // device claimed, move to next device
}
}
}
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
void Initialize(Hal::ACPI::CommonSDTHeader* xsdt) {
KernelLogStream(INFO, "PCI") << "Initializing PCI subsystem";
// A plain ECAM walk of a quirky config space reads (and can fault
// on) host-bridge registers. Its devices are described by ACPI
// instead (Drivers/Init.cpp's platform devices).
char quirk[32];
if (RootBridgeEcamQuirk(quirk, sizeof(quirk))) {
KernelLogStream(WARNING, "PCI") << "Root bridge config space is not ECAM (quirk \""
<< quirk << "\"): not enumerating PCI";
return;
}
// Try to parse MCFG for ECAM access; fall back to legacy if unavailable
ParseMcfg(xsdt);
if (g_useEcam) {
KernelLogStream(OK, "PCI") << "Using ECAM (memory-mapped) config access";
} else {
KernelLogStream(INFO, "PCI") << "Using legacy I/O port config access";
}
// Enumerate all PCI devices
EnumerateAll();
KernelLogStream(OK, "PCI") << "Enumeration complete: " << base::dec << (uint64_t)g_devices.size() << " devices found";
}
};