385 lines
13 KiB
C
385 lines
13 KiB
C
// hal_main.c - HAL initialisation and HalGetApi
|
|
#include <hal_api.h>
|
|
#include <hal_apic.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.h>
|
|
#include <intrin.h>
|
|
#include <printf.h>
|
|
|
|
// Declared in other hal_*.c files:
|
|
void HalScreenInitialize(uintptr_t fb, int w, int h, int stride, HAL_SCREEN* out);
|
|
void HalKeyboardInitialize(HAL_KEYBOARD* out);
|
|
void HalMouseInitialize(HAL_MOUSE* out, int screenW, int screenH);
|
|
KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out);
|
|
void HalPciInitialize(HAL_PCI* out);
|
|
void HalDiskInitialize(HAL_DISK* out);
|
|
extern void HalSetDebugEnabled(int v);
|
|
|
|
// Defined in mm_virt.c - HAL needs it for MMIO window access.
|
|
static uint64_t gPhysBase = 0xFFFF880000000000ULL;
|
|
|
|
HAL_API gApi;
|
|
static int gInitialised = 0;
|
|
extern void (*gTimerCallback)(void);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ACPI table walker - minimal, just enough to find APIC and HPET addresses.
|
|
// ---------------------------------------------------------------------------
|
|
#define ACPI_SIG(a,b,c,d) ((uint32_t)(a)|((uint32_t)(b)<<8)|((uint32_t)(c)<<16)|((uint32_t)(d)<<24))
|
|
|
|
#pragma pack(push, 1)
|
|
#pragma warning(disable : 4200)
|
|
|
|
// ACPI RSDP (Root System Description Pointer)
|
|
typedef struct {
|
|
char Signature[8];
|
|
uint8_t Checksum;
|
|
char OEMID[6];
|
|
uint8_t Revision;
|
|
uint32_t RsdtAddress;
|
|
uint32_t Length;
|
|
uint64_t XsdtAddress;
|
|
uint8_t ExtendedChecksum;
|
|
uint8_t Reserved[3];
|
|
} RSDP;
|
|
|
|
// ACPI SDT Header
|
|
typedef struct {
|
|
char Signature[4];
|
|
uint32_t Length;
|
|
uint8_t Revision;
|
|
uint8_t Checksum;
|
|
char OEMID[6];
|
|
char OEMTableID[8];
|
|
uint32_t OEMRevision;
|
|
uint32_t CreatorID;
|
|
uint32_t CreatorRevision;
|
|
} ACPISDTHeader;
|
|
|
|
// RSDT (Root System Description Table)
|
|
typedef struct {
|
|
ACPISDTHeader Header;
|
|
uint32_t PointerToOtherSDT[];
|
|
} RSDT;
|
|
|
|
// XSDT (Extended System Description Table)
|
|
typedef struct {
|
|
ACPISDTHeader Header;
|
|
uint64_t PointerToOtherSDT[];
|
|
} XSDT;
|
|
|
|
// MADT (Multiple APIC Description Table)
|
|
typedef struct {
|
|
ACPISDTHeader Header;
|
|
uint32_t LocalAPICAddress;
|
|
uint32_t Flags;
|
|
uint8_t Entries[];
|
|
} MADT;
|
|
|
|
#pragma pack(pop)
|
|
|
|
static uintptr_t gLapicPhys = 0xFEE00000;
|
|
static uintptr_t gIoapicPhys = 0xFEC00000;
|
|
static uintptr_t gHpetPhys = 0xFED00000;
|
|
|
|
// ACPI power-off state (filled in by ParseFadt / ScanDsdtForS5)
|
|
static uint16_t gPm1aCntBlk = 0;
|
|
static uint16_t gPm1bCntBlk = 0;
|
|
static uint8_t gSlpTypA = 5; // S5 SLP_TYP for PM1a; 5 is correct for QEMU/most VMs
|
|
static uint8_t gSlpTypB = 5; // S5 SLP_TYP for PM1b
|
|
|
|
int debugEnabled = 0;
|
|
int _fltused; // fuckass
|
|
static void hal_putchar(char c) {
|
|
gApi.Screen.PrintChar(c, 0x00FF00, 0x000000);
|
|
}
|
|
void HalDbgPrint(const char* fmt, ...) {
|
|
if (!debugEnabled) return;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vprintf(fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static void *HalPhysToVirt(uint64_t phys) {
|
|
return (void *)(phys + gPhysBase);
|
|
}
|
|
|
|
// FADT (Fixed ACPI Description Table) - partial layout up to PM1b_CNT_BLK.
|
|
// We only need offsets 40 (DSDT ptr), 64 (PM1a_CNT_BLK), 68 (PM1b_CNT_BLK).
|
|
// The full table is ~276 bytes (ACPI 2.0+); we never access beyond offset 68.
|
|
#pragma pack(push, 1)
|
|
typedef struct {
|
|
ACPISDTHeader Header; // 0 (36 bytes)
|
|
uint32_t FirmwareCtrl; // 36
|
|
uint32_t Dsdt; // 40 - physical addr of DSDT
|
|
uint8_t Reserved1; // 44
|
|
uint8_t PreferredPmProfile; // 45
|
|
uint16_t SciInt; // 46
|
|
uint32_t SmiCmd; // 48
|
|
uint8_t AcpiEnable; // 52
|
|
uint8_t AcpiDisable; // 53
|
|
uint8_t S4BiosReq; // 54
|
|
uint8_t PstateCnt; // 55
|
|
uint32_t Pm1aEvtBlk; // 56
|
|
uint32_t Pm1bEvtBlk; // 60
|
|
uint32_t Pm1aCntBlk; // 64 - I/O port for PM1a control register
|
|
uint32_t Pm1bCntBlk; // 68 - I/O port for PM1b control register
|
|
} FADT;
|
|
#pragma pack(pop)
|
|
|
|
// Scan DSDT AML for the _S5_ Name object and extract SLP_TYP values.
|
|
// The encoding looks like:
|
|
// 0x08 '_' 'S' '5' '_' ; NameOp + "_S5_"
|
|
// 0x12 <PkgLen> ; PackageOp + encoded length
|
|
// <NumElements> ; usually 0x04
|
|
// 0x0A <slp_typ_a> ; BytePrefix + value (or 0x00 for zero)
|
|
// 0x0A <slp_typ_b>
|
|
static void ScanDsdtForS5(const uint8_t* aml, uint32_t len) {
|
|
for (uint32_t i = 0; i + 9 < len; i++) {
|
|
// Match NameOp + "_S5_" + PackageOp
|
|
if (aml[i] != 0x08) continue;
|
|
if (aml[i+1] != 0x5F || aml[i+2] != 0x53 ||
|
|
aml[i+3] != 0x35 || aml[i+4] != 0x5F) continue;
|
|
if (aml[i+5] != 0x12) continue;
|
|
|
|
// PkgLength encoding: low 6 bits = byte count (0 → only this byte);
|
|
// bits [7:6] = number of following bytes in the length field.
|
|
uint8_t pkgByte = aml[i+6];
|
|
uint32_t pkgExtra = pkgByte >> 6; // 0..3 extra bytes
|
|
uint32_t j = i + 7 + pkgExtra; // skip PkgLen field
|
|
if (j + 2 >= len) break;
|
|
j++; // skip NumElements byte
|
|
|
|
// SLP_TYP_A
|
|
if (aml[j] == 0x0A && j + 1 < len) {
|
|
gSlpTypA = aml[j + 1]; j += 2;
|
|
} else if (aml[j] == 0x00) {
|
|
gSlpTypA = 0; j++;
|
|
} else {
|
|
break; // unexpected encoding - keep defaults
|
|
}
|
|
|
|
// SLP_TYP_B
|
|
if (j < len && aml[j] == 0x0A && j + 1 < len)
|
|
gSlpTypB = aml[j + 1];
|
|
else if (j < len && aml[j] == 0x00)
|
|
gSlpTypB = 0;
|
|
|
|
HalDbgPrint("HAL: _S5_ SLP_TYP_A=0x%02x SLP_TYP_B=0x%02x\n",
|
|
gSlpTypA, gSlpTypB);
|
|
return;
|
|
}
|
|
HalDbgPrint("HAL: _S5_ not found in DSDT, using defaults (A=0x%02x B=0x%02x)\n",
|
|
gSlpTypA, gSlpTypB);
|
|
}
|
|
|
|
static int validate_checksum(void* table, uint32_t length) {
|
|
uint8_t sum = 0;
|
|
uint8_t* bytes = (uint8_t*)table;
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
sum += bytes[i];
|
|
}
|
|
return sum == 0;
|
|
}
|
|
|
|
static void ParseFadt(const FADT* fadt) {
|
|
gPm1aCntBlk = (uint16_t)fadt->Pm1aCntBlk;
|
|
gPm1bCntBlk = (uint16_t)fadt->Pm1bCntBlk;
|
|
HalDbgPrint("HAL: FADT PM1a_CNT=0x%04x PM1b_CNT=0x%04x DSDT=0x%08x\n",
|
|
gPm1aCntBlk, gPm1bCntBlk, fadt->Dsdt);
|
|
|
|
if (fadt->Dsdt) {
|
|
const ACPISDTHeader* dsdt =
|
|
(const ACPISDTHeader*)((uintptr_t)fadt->Dsdt + gPhysBase);
|
|
if (memcmp(dsdt->Signature, "DSDT", 4) == 0 &&
|
|
validate_checksum((void*)dsdt, dsdt->Length)) {
|
|
const uint8_t* aml = (const uint8_t*)(dsdt + 1);
|
|
ScanDsdtForS5(aml, dsdt->Length - (uint32_t)sizeof(ACPISDTHeader));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ParseMadt(MADT* madt) {
|
|
gLapicPhys = madt->LocalAPICAddress;
|
|
uint8_t* p = (uint8_t *)(madt + 1);
|
|
uint8_t* end = (uint8_t *)madt + madt->Header.Length;
|
|
while (p < end) {
|
|
uint8_t type = p[0], len = p[1];
|
|
if (type == 1) { // I/O APIC entry
|
|
gIoapicPhys = *(uint32_t *)(p + 4);
|
|
}
|
|
p += len ? len : 1;
|
|
}
|
|
}
|
|
|
|
static void WalkAcpi(uintptr_t rsdpPhys) {
|
|
if (!rsdpPhys) return;
|
|
|
|
// Map RSDP into virtual memory
|
|
RSDP* rsdp = (RSDP*)(rsdpPhys + gPhysBase);
|
|
|
|
if (memcmp(rsdp->Signature, "RSD PTR ", 8) != 0) {
|
|
HalDbgPrint("HAL: Invalid RSDP signature!\n");
|
|
return;
|
|
}
|
|
|
|
ACPISDTHeader* sdtHeader = NULL;
|
|
uint32_t count = 0;
|
|
int isXsdt = 0;
|
|
|
|
// Safely find and validate the XSDT (ACPI 2.0+)
|
|
if (rsdp->Revision >= 2 && rsdp->XsdtAddress != 0) {
|
|
XSDT* xsdt = (XSDT*)(rsdp->XsdtAddress + gPhysBase);
|
|
|
|
if (validate_checksum(xsdt, xsdt->Header.Length) &&
|
|
memcmp(xsdt->Header.Signature, "XSDT", 4) == 0) {
|
|
|
|
sdtHeader = &xsdt->Header;
|
|
count = (xsdt->Header.Length - sizeof(ACPISDTHeader)) / 8;
|
|
isXsdt = 1;
|
|
}
|
|
}
|
|
|
|
// Fallback to safely finding and validating the RSDT (ACPI 1.0)
|
|
if (sdtHeader == NULL && rsdp->RsdtAddress != 0) {
|
|
RSDT* rsdt = (RSDT*)((uintptr_t)rsdp->RsdtAddress + gPhysBase);
|
|
|
|
if (validate_checksum(rsdt, rsdt->Header.Length) &&
|
|
memcmp(rsdt->Header.Signature, "RSDT", 4) == 0) {
|
|
|
|
sdtHeader = &rsdt->Header;
|
|
count = (rsdt->Header.Length - sizeof(ACPISDTHeader)) / 4;
|
|
isXsdt = 0;
|
|
}
|
|
}
|
|
|
|
if (sdtHeader == NULL) {
|
|
HalDbgPrint("HAL: No valid XSDT or RSDT found!\n");
|
|
return;
|
|
}
|
|
|
|
// Walk the tables
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
uintptr_t tablePhys = 0;
|
|
|
|
if (isXsdt) {
|
|
tablePhys = ((XSDT*)sdtHeader)->PointerToOtherSDT[i];
|
|
} else {
|
|
tablePhys = ((RSDT*)sdtHeader)->PointerToOtherSDT[i];
|
|
}
|
|
|
|
// Paranoid alignment check to reject firmware garbage pointers
|
|
if (!tablePhys || (tablePhys & 3) != 0) continue;
|
|
|
|
ACPISDTHeader* header = (ACPISDTHeader*)(tablePhys + gPhysBase);
|
|
|
|
// Ensure the table isn't corrupt before parsing
|
|
if (!validate_checksum(header, header->Length)) continue;
|
|
|
|
// Parse MADT
|
|
if (memcmp(header->Signature, "APIC", 4) == 0) {
|
|
ParseMadt((MADT*)header);
|
|
}
|
|
// Parse FADT (signature "FACP") for PM1 control ports and DSDT pointer
|
|
else if (memcmp(header->Signature, "FACP", 4) == 0) {
|
|
ParseFadt((const FADT*)header);
|
|
}
|
|
// Parse HPET
|
|
else if (memcmp(header->Signature, "HPET", 4) == 0) {
|
|
// HPET base address block is located at offset 44
|
|
// (which is 8 bytes past the 36-byte ACPISDTHeader)
|
|
gHpetPhys = *(uint64_t*)((uint8_t*)header + 44) & ~0xFFFULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// HalShutdown - ACPI S5 soft power-off. Does not return.
|
|
// ---------------------------------------------------------------------------
|
|
static void HalShutdown(void) {
|
|
// Silence! Stop the world!
|
|
_disable();
|
|
|
|
// SLP_EN (bit 13) | SLP_TYPx (bits 12:10)
|
|
uint16_t valA = (uint16_t)((gSlpTypA << 10) | (1 << 13));
|
|
uint16_t valB = (uint16_t)((gSlpTypB << 10) | (1 << 13));
|
|
|
|
if (gPm1aCntBlk) __outword(gPm1aCntBlk, valA);
|
|
if (gPm1bCntBlk) __outword(gPm1bCntBlk, valB);
|
|
|
|
// Die!
|
|
while (true) __halt();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
__declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
|
|
if (gInitialised) return STATUS_ALREADY_EXISTS;
|
|
gPhysBase = initParams->PhysicalWindowBase;
|
|
gApi.PhysToVirt = HalPhysToVirt;
|
|
debugEnabled = initParams->DebugEnabled;
|
|
|
|
// Screen first so DbgPrint works immediately.
|
|
HalScreenInitialize(initParams->FramebufferBase + initParams->PhysicalWindowBase,
|
|
(int)initParams->FramebufferWidth,
|
|
(int)initParams->FramebufferHeight,
|
|
(int)initParams->FramebufferStride,
|
|
&gApi.Screen);
|
|
|
|
printf_set_putchar(hal_putchar);
|
|
|
|
HalDbgPrint("HAL: screen %dx%d\n",
|
|
initParams->FramebufferWidth, initParams->FramebufferHeight);
|
|
|
|
// Parse ACPI to find LAPIC / IOAPIC / HPET addresses.
|
|
if (initParams->RsdpPhysical) WalkAcpi(initParams->RsdpPhysical);
|
|
HalDbgPrint("HAL: LAPIC=%016llx IOAPIC=%016llx HPET=%016llx\n",
|
|
gLapicPhys, gIoapicPhys, gHpetPhys);
|
|
|
|
HalApicInitialize(gLapicPhys, gIoapicPhys);
|
|
HalKeyboardInitialize(&gApi.Keyboard);
|
|
HalMouseInitialize(&gApi.Mouse,
|
|
(int)initParams->FramebufferWidth,
|
|
(int)initParams->FramebufferHeight);
|
|
|
|
KSTATUS ts = HalTimerInitialize(gLapicPhys, gHpetPhys, &gApi.Timer);
|
|
if (!KSUCCESS(ts)) HalDbgPrint("HAL: HPET unavailable, using fallback\n");
|
|
|
|
HalPciInitialize(&gApi.Pci);
|
|
HalDiskInitialize(&gApi.Disk);
|
|
|
|
gApi.Intc.RouteIrq = HalApicRouteIrq;
|
|
gApi.Intc.MaskIrq = HalApicMaskIrq;
|
|
gApi.Intc.UnmaskIrq = HalApicUnmaskIrq;
|
|
gApi.Intc.SendEoi = HalApicSendEoi;
|
|
|
|
gApi.Power.Shutdown = HalShutdown;
|
|
|
|
gApi.Version = HAL_API_VERSION;
|
|
gInitialised = 1;
|
|
|
|
// This shouldn't be here but i have no other way.
|
|
// After i send 0xF4 and move the mouse the controller sends an interrupts that doesn't fire
|
|
// and because the CPU missed the IRQ 12 edge transition (cli) later when we expect it to fire, it doesn't
|
|
// leaving us with no mouse and no keyboard. The dreaded 8042 will hold the CLOCK line low forever.
|
|
// To combat this we just go about our day executing the rest of the init and just before the end
|
|
// of init we just iterate 256 times to grab whatever the controller left for us ("Drain and Pray").
|
|
for (int i = 0; i < 256; i++) {
|
|
if (__inbyte(0x64) & 0x01) {
|
|
__inbyte(0x60); // Consume
|
|
}
|
|
gApi.Timer.Stall(1); // Micro-stalls
|
|
}
|
|
|
|
HalDbgPrint("HAL: initialisation complete.\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
__declspec(dllexport) void HalRegisterScheduler(void (*pKeSchedulerTick)(void)) {
|
|
gTimerCallback = pKeSchedulerTick;
|
|
}
|
|
|
|
__declspec(dllexport) const HAL_API* HalGetApi(void) { return &gApi; }
|