feat: PCI-e, APIC timer
This commit is contained in:
@@ -28,6 +28,8 @@
|
|||||||
#include <Memory/Paging.hpp>
|
#include <Memory/Paging.hpp>
|
||||||
#include <ACPI/ACPI.hpp>
|
#include <ACPI/ACPI.hpp>
|
||||||
#include <Hal/Apic/ApicInit.hpp>
|
#include <Hal/Apic/ApicInit.hpp>
|
||||||
|
#include <Pci/Pci.hpp>
|
||||||
|
#include <Timekeeping/ApicTimer.hpp>
|
||||||
#include <Drivers/PS2/PS2Controller.hpp>
|
#include <Drivers/PS2/PS2Controller.hpp>
|
||||||
#include <Drivers/PS2/Keyboard.hpp>
|
#include <Drivers/PS2/Keyboard.hpp>
|
||||||
#include <Drivers/PS2/Mouse.hpp>
|
#include <Drivers/PS2/Mouse.hpp>
|
||||||
@@ -122,6 +124,10 @@ extern "C" void kmain() {
|
|||||||
if (g_acpi.GetXSDT() != nullptr) {
|
if (g_acpi.GetXSDT() != nullptr) {
|
||||||
Hal::ApicInitialize(g_acpi.GetXSDT());
|
Hal::ApicInitialize(g_acpi.GetXSDT());
|
||||||
|
|
||||||
|
Pci::Initialize(g_acpi.GetXSDT());
|
||||||
|
|
||||||
|
Timekeeping::ApicTimerInitialize();
|
||||||
|
|
||||||
Drivers::PS2::Initialize();
|
Drivers::PS2::Initialize();
|
||||||
Drivers::PS2::Keyboard::Initialize();
|
Drivers::PS2::Keyboard::Initialize();
|
||||||
Drivers::PS2::Mouse::Initialize();
|
Drivers::PS2::Mouse::Initialize();
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
/*
|
||||||
|
* Pci.cpp
|
||||||
|
* PCI Express enumeration and configuration space access
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Pci.hpp"
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
#include <Memory/HHDM.hpp>
|
||||||
|
#include <Memory/Paging.hpp>
|
||||||
|
#include <Io/IoPort.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Pci {
|
||||||
|
|
||||||
|
// Legacy PCI config I/O ports
|
||||||
|
static constexpr uint16_t ConfigAddressPort = 0xCF8;
|
||||||
|
static constexpr uint16_t ConfigDataPort = 0xCFC;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t LegacyRead32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset) {
|
||||||
|
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
|
||||||
|
return Io::In32(ConfigDataPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Io::Out32(LegacyBuildAddress(bus, device, function, offset), ConfigAddressPort);
|
||||||
|
Io::Out32(value, ConfigDataPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value) {
|
||||||
|
uint32_t addr = LegacyBuildAddress(bus, device, function, offset & 0xFC);
|
||||||
|
Io::Out32(addr, ConfigAddressPort);
|
||||||
|
uint32_t tmp = Io::In32(ConfigDataPort);
|
||||||
|
int shift = (offset & 2) * 8;
|
||||||
|
tmp &= ~(0xFFFF << shift);
|
||||||
|
tmp |= ((uint32_t)value << shift);
|
||||||
|
Io::Out32(addr, ConfigAddressPort);
|
||||||
|
Io::Out32(tmp, ConfigDataPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LegacyWrite8(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint8_t value) {
|
||||||
|
uint32_t addr = LegacyBuildAddress(bus, device, function, offset & 0xFC);
|
||||||
|
Io::Out32(addr, ConfigAddressPort);
|
||||||
|
uint32_t tmp = Io::In32(ConfigDataPort);
|
||||||
|
int shift = (offset & 3) * 8;
|
||||||
|
tmp &= ~(0xFF << shift);
|
||||||
|
tmp |= ((uint32_t)value << shift);
|
||||||
|
Io::Out32(addr, ConfigAddressPort);
|
||||||
|
Io::Out32(tmp, ConfigDataPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize(Hal::ACPI::CommonSDTHeader* xsdt) {
|
||||||
|
KernelLogStream(INFO, "PCI") << "Initializing PCI subsystem";
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Pci.hpp
|
||||||
|
* PCI Express enumeration and configuration space access
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ACPI/ACPI.hpp>
|
||||||
|
#include <CppLib/Vector.hpp>
|
||||||
|
|
||||||
|
namespace Pci {
|
||||||
|
|
||||||
|
// MCFG table structures
|
||||||
|
struct McfgEntry {
|
||||||
|
uint64_t BaseAddress;
|
||||||
|
uint16_t SegmentGroup;
|
||||||
|
uint8_t StartBus;
|
||||||
|
uint8_t EndBus;
|
||||||
|
uint32_t Reserved;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
struct McfgHeader {
|
||||||
|
Hal::ACPI::CommonSDTHeader SDTHeader;
|
||||||
|
uint64_t Reserved;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
// PCI device information
|
||||||
|
struct PciDevice {
|
||||||
|
uint16_t Segment;
|
||||||
|
uint8_t Bus;
|
||||||
|
uint8_t Device;
|
||||||
|
uint8_t Function;
|
||||||
|
|
||||||
|
uint16_t VendorId;
|
||||||
|
uint16_t DeviceId;
|
||||||
|
|
||||||
|
uint8_t ClassCode;
|
||||||
|
uint8_t SubClass;
|
||||||
|
uint8_t ProgIf;
|
||||||
|
uint8_t RevisionId;
|
||||||
|
uint8_t HeaderType;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configuration space access (ECAM / memory-mapped)
|
||||||
|
uint8_t EcamRead8(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset);
|
||||||
|
uint16_t EcamRead16(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset);
|
||||||
|
uint32_t EcamRead32(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset);
|
||||||
|
|
||||||
|
void EcamWrite8(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t value);
|
||||||
|
void EcamWrite16(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint16_t value);
|
||||||
|
void EcamWrite32(uint64_t ecamBase, uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint32_t value);
|
||||||
|
|
||||||
|
// Configuration space access (legacy I/O ports 0xCF8/0xCFC)
|
||||||
|
uint8_t LegacyRead8(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset);
|
||||||
|
uint16_t LegacyRead16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset);
|
||||||
|
uint32_t LegacyRead32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset);
|
||||||
|
|
||||||
|
void LegacyWrite8(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint8_t value);
|
||||||
|
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value);
|
||||||
|
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
|
||||||
|
|
||||||
|
// Class code name lookup
|
||||||
|
const char* GetClassName(uint8_t classCode, uint8_t subClass);
|
||||||
|
|
||||||
|
// Get the list of discovered devices
|
||||||
|
kcp::vector<PciDevice>& GetDevices();
|
||||||
|
|
||||||
|
// Initialize PCI subsystem: parse MCFG, enumerate devices
|
||||||
|
// xsdt: pointer to the XSDT (already HHDM-mapped)
|
||||||
|
void Initialize(Hal::ACPI::CommonSDTHeader* xsdt);
|
||||||
|
};
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* ApicTimer.cpp
|
||||||
|
* Local APIC timer: PIT-calibrated periodic tick for timekeeping
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ApicTimer.hpp"
|
||||||
|
#include <Hal/Apic/Apic.hpp>
|
||||||
|
#include <Hal/Apic/Interrupts.hpp>
|
||||||
|
#include <Io/IoPort.hpp>
|
||||||
|
#include <Terminal/Terminal.hpp>
|
||||||
|
#include <CppLib/Stream.hpp>
|
||||||
|
|
||||||
|
using namespace Kt;
|
||||||
|
|
||||||
|
namespace Timekeeping {
|
||||||
|
// PIT constants
|
||||||
|
static constexpr uint32_t PIT_FREQUENCY = 1193182; // PIT oscillator frequency in Hz
|
||||||
|
static constexpr uint16_t PIT_CHANNEL2_DATA = 0x42;
|
||||||
|
static constexpr uint16_t PIT_COMMAND = 0x43;
|
||||||
|
static constexpr uint16_t PIT_GATE_PORT = 0x61;
|
||||||
|
|
||||||
|
// APIC timer LVT mode bits
|
||||||
|
static constexpr uint32_t LVT_MASKED = (1 << 16);
|
||||||
|
static constexpr uint32_t LVT_PERIODIC = (1 << 17);
|
||||||
|
|
||||||
|
// APIC timer divide configuration values
|
||||||
|
static constexpr uint32_t DIVIDE_BY_16 = 0x03;
|
||||||
|
|
||||||
|
// Timer tick rate: 1000 Hz (1 ms per tick)
|
||||||
|
static constexpr uint32_t TIMER_HZ = 1000;
|
||||||
|
|
||||||
|
// Global state
|
||||||
|
static volatile uint64_t g_tickCount = 0;
|
||||||
|
static uint32_t g_ticksPerMs = 0;
|
||||||
|
|
||||||
|
// Timer IRQ handler: increment tick count
|
||||||
|
static void TimerHandler(uint8_t) {
|
||||||
|
g_tickCount = g_tickCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use PIT channel 2 to create a precise delay for calibration.
|
||||||
|
// Returns the number of APIC timer ticks that elapsed during ~10ms.
|
||||||
|
static uint32_t CalibratePit() {
|
||||||
|
// We want to measure ~10ms worth of APIC timer ticks.
|
||||||
|
// PIT count for 10ms: PIT_FREQUENCY / 100 = 11931
|
||||||
|
uint16_t pitCount = PIT_FREQUENCY / 100;
|
||||||
|
|
||||||
|
// Set up PIT channel 2 in one-shot mode (mode 0), lobyte/hibyte
|
||||||
|
// Command: channel 2 (bits 7-6 = 10), lobyte/hibyte (bits 5-4 = 11),
|
||||||
|
// mode 0 (bits 3-1 = 000), binary (bit 0 = 0) => 0xB0
|
||||||
|
Io::Out8(0xB0, PIT_COMMAND);
|
||||||
|
|
||||||
|
// Disable PIT channel 2 gate to prepare
|
||||||
|
uint8_t gate = Io::In8(PIT_GATE_PORT);
|
||||||
|
gate &= ~0x01; // Disable gate (bit 0 = 0)
|
||||||
|
gate &= ~0x02; // Disable speaker (bit 1 = 0)
|
||||||
|
Io::Out8(gate, PIT_GATE_PORT);
|
||||||
|
|
||||||
|
// Load the count value (low byte first, then high byte)
|
||||||
|
Io::Out8((uint8_t)(pitCount & 0xFF), PIT_CHANNEL2_DATA);
|
||||||
|
Io::IoPortWait();
|
||||||
|
Io::Out8((uint8_t)(pitCount >> 8), PIT_CHANNEL2_DATA);
|
||||||
|
|
||||||
|
// Set APIC timer to max count with divide-by-16, one-shot (masked so no IRQ)
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, LVT_MASKED);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0xFFFFFFFF);
|
||||||
|
|
||||||
|
// Enable PIT channel 2 gate to start counting
|
||||||
|
gate = Io::In8(PIT_GATE_PORT);
|
||||||
|
gate |= 0x01; // Enable gate (bit 0 = 1)
|
||||||
|
Io::Out8(gate, PIT_GATE_PORT);
|
||||||
|
|
||||||
|
// Wait for PIT channel 2 output to go high (bit 5 of port 0x61)
|
||||||
|
while (!(Io::In8(PIT_GATE_PORT) & 0x20)) {
|
||||||
|
asm volatile("pause");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read how many APIC timer ticks elapsed
|
||||||
|
uint32_t currentCount = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_CURRENT);
|
||||||
|
uint32_t elapsed = 0xFFFFFFFF - currentCount;
|
||||||
|
|
||||||
|
// Stop the APIC timer
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0);
|
||||||
|
|
||||||
|
return elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApicTimerInitialize() {
|
||||||
|
KernelLogStream(INFO, "Timer") << "Calibrating APIC timer using PIT channel 2";
|
||||||
|
|
||||||
|
// Calibrate: measure APIC ticks over ~10ms
|
||||||
|
uint32_t ticksIn10ms = CalibratePit();
|
||||||
|
g_ticksPerMs = ticksIn10ms / 10;
|
||||||
|
|
||||||
|
if (g_ticksPerMs == 0) {
|
||||||
|
KernelLogStream(ERROR, "Timer") << "APIC timer calibration failed (0 ticks/ms)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t timerFreqHz = (uint64_t)g_ticksPerMs * 1000;
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Timer") << "APIC timer: " << base::dec << (uint64_t)g_ticksPerMs
|
||||||
|
<< " ticks/ms (" << timerFreqHz << " Hz, divide-by-16)";
|
||||||
|
|
||||||
|
// Register IRQ handler for timer (IRQ 0 = vector 32)
|
||||||
|
Hal::RegisterIrqHandler(Hal::IRQ_TIMER, TimerHandler);
|
||||||
|
|
||||||
|
// Configure APIC timer: periodic mode, vector 32
|
||||||
|
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||||
|
|
||||||
|
// Set initial count for 1ms intervals (1000 Hz tick rate)
|
||||||
|
uint32_t initialCount = g_ticksPerMs;
|
||||||
|
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, initialCount);
|
||||||
|
|
||||||
|
KernelLogStream(OK, "Timer") << "APIC timer started: " << base::dec << (uint64_t)TIMER_HZ
|
||||||
|
<< " Hz periodic, initial count=" << (uint64_t)initialCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GetTicks() {
|
||||||
|
return g_tickCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GetMilliseconds() {
|
||||||
|
return g_tickCount; // 1 tick = 1 ms at 1000 Hz
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sleep(uint64_t ms) {
|
||||||
|
uint64_t target = g_tickCount + ms;
|
||||||
|
while (g_tickCount < target) {
|
||||||
|
asm volatile("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* ApicTimer.hpp
|
||||||
|
* Local APIC timer for periodic tick interrupts and timekeeping
|
||||||
|
* Copyright (c) 2025 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Timekeeping {
|
||||||
|
// Initialize the APIC timer: calibrate against PIT, start periodic interrupts
|
||||||
|
void ApicTimerInitialize();
|
||||||
|
|
||||||
|
// Get the monotonic tick count (increments on each timer interrupt)
|
||||||
|
uint64_t GetTicks();
|
||||||
|
|
||||||
|
// Get elapsed milliseconds since timer initialization
|
||||||
|
uint64_t GetMilliseconds();
|
||||||
|
|
||||||
|
// Busy-wait sleep for the given number of milliseconds
|
||||||
|
void Sleep(uint64_t ms);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user