Files
nt-lite/code/kernel/hal/hal_main.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

898 lines
31 KiB
C

// HAL initialization entry point. This file discovers platform tables and publishes the HAL API to the kernel.
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_apic.h>
#include <kernel/hal/hal_output.h>
#include <kernel/hal/hal_processor.h>
#include <kernel/hal/hal_internal.h>
#include <kernel/hal/hal_timer.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <intrin.h>
#include <common/printf.h>
void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride,
HAL_SCREEN* out);
void HalPciInitialize(HAL_PCI* out);
void HalRtcInitialize(HAL_RTC* out);
void HalRtcSetCenturyRegister(uint8_t centuryRegister);
extern void HalSetDebugEnabled(int v);
// Defined in mm_virt.c - HAL needs it for MMIO window access.
static uint64_t gPhysBase = 0xFFFF880000000000ULL;
static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out);
static HAL_API gApi = {
.Size = sizeof(HAL_API),
.Version = HAL_API_VERSION,
.Processor = {
.DisableInterrupts = HalDisableInterrupts,
.RestoreInterrupts = HalRestoreInterrupts,
.EnableInterrupts = HalEnableInterrupts,
.WaitForInterrupt = HalWaitForInterrupt,
.Halt = HalHalt,
.Stop = HalStopProcessor,
.QueryTopology = HalQueryProcessorTopology,
.Start = HalApicStartProcessor,
},
.InterruptController = {
.RouteIrq = HalApicRouteIrq,
.MaskIrq = HalApicMaskIrq,
.UnmaskIrq = HalApicUnmaskIrq,
.SendEoi = HalApicSendEoi,
.InitializeCurrentProcessor = HalApicInitializeCurrentProcessor,
.SendProcessorInterrupt = HalApicSendProcessorInterrupt,
.SendProcessorNmi = HalApicSendProcessorNmi,
.BroadcastNmi = HalApicBroadcastNmi,
.ComposeMsi = HalApicComposeMsi,
},
};
static int gInitialised = 0;
// 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 Padding[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;
static HAL_PROCESSOR_TOPOLOGY gCpuTopology;
// ACPI power-off state (filled in by ParseFadt / ScanDsdtForS5).
// Do not guess SLP_TYP on real hardware: these values are firmware-specific.
typedef struct ACPI_FIXED_REGISTER {
uint8_t AddressSpaceId;
uint8_t WidthBytes;
uint16_t Reserved;
uint64_t Address;
} ACPI_FIXED_REGISTER;
static ACPI_FIXED_REGISTER gPm1aCnt;
static ACPI_FIXED_REGISTER gPm1bCnt;
static ACPI_FIXED_REGISTER gSleepControl;
static uint32_t gLegacyPm1aPort = 0;
static uint32_t gLegacyPm1bPort = 0;
static uint32_t gFadtFlags = 0;
static uint32_t gSmiCommandPort = 0;
static uint8_t gAcpiEnableValue = 0;
static uint8_t gSlpTypA = 0;
static uint8_t gSlpTypB = 0;
static int gS5Found = 0;
static int gHwReducedAcpi = 0;
static int gIntelPchPmBase = 0;
int debugEnabled = 0;
int _fltused; // MSVC floating-point helper anchor.
// Writes a formatted early HAL diagnostic through the output router.
void HalDbgPrint(const char* fmt, ...) {
if (!debugEnabled || !fmt || !gApi.Output.Write)
return;
char line[1024];
va_list args;
va_start(args, fmt);
int count = vsnprintf_(line, sizeof(line), fmt, args);
va_end(args);
size_t length = 0;
if (count > 0) {
length = (size_t)count;
if (length >= sizeof(line))
length = sizeof(line) - 1u;
}
HAL_OUTPUT_RECORD record;
record.Stream = HAL_OUTPUT_STREAM_DEBUG;
record.Flags = 0;
record.Foreground = 0x00CCCCCCu;
record.Background = 0x00012456u;
record.Buffer = line;
record.Length = length;
gApi.Output.Write(&record);
}
void* HalPhysicalToVirtual(uint64_t phys) {
return (void*)(phys + gPhysBase);
}
// FADT (Fixed ACPI Description Table) offsets used here. ACPI 2.0+
// systems may put the real DSDT and PM1 control registers in the extended
// X_* fields, so do not rely only on the legacy 32-bit fields.
#define FADT_DSDT_OFFSET 40u
#define FADT_SMI_CMD_OFFSET 48u
#define FADT_ACPI_ENABLE_OFFSET 52u
#define FADT_PM1A_CNT_BLK_OFFSET 64u
#define FADT_PM1B_CNT_BLK_OFFSET 68u
#define FADT_PM1_CNT_LEN_OFFSET 89u
#define FADT_CENTURY_OFFSET 108u
#define FADT_FLAGS_OFFSET 112u
#define FADT_X_DSDT_OFFSET 140u
#define FADT_X_PM1A_CNT_BLK_OFFSET 172u
#define FADT_X_PM1B_CNT_BLK_OFFSET 184u
#define FADT_SLEEP_CONTROL_OFFSET 244u
#define FADT_FLAG_HW_REDUCED_ACPI (1u << 20)
#define INTEL_PCH_SMI_EN_OFFSET 0x30u
#define INTEL_PCH_SMI_ON_SLP_EN (1u << 4)
#pragma pack(push, 1)
typedef struct {
uint8_t AddressSpaceId;
uint8_t RegisterBitWidth;
uint8_t RegisterBitOffset;
uint8_t AccessSize;
uint64_t Address;
} ACPI_GAS;
#pragma pack(pop)
// Implements the acpi Read U32 operation.
static uint32_t AcpiReadU32(const void* table, uint32_t tableLen, uint32_t off) {
uint32_t value = 0;
if (off + sizeof(value) <= tableLen)
memcpy(&value, (const uint8_t*)table + off, sizeof(value));
return value;
}
// Implements the acpi Read U64 operation.
static uint64_t AcpiReadU64(const void* table, uint32_t tableLen, uint32_t off) {
uint64_t value = 0;
if (off + sizeof(value) <= tableLen)
memcpy(&value, (const uint8_t*)table + off, sizeof(value));
return value;
}
// Decodes one ACPI Generic Address Structure used for fixed power registers.
static int AcpiReadGasRegister(const void* table, uint32_t tableLen, uint32_t off,
ACPI_FIXED_REGISTER* out) {
if (!out || off + sizeof(ACPI_GAS) > tableLen) return 0;
ACPI_GAS gas;
memcpy(&gas, (const uint8_t*)table + off, sizeof(gas));
if (!gas.Address || gas.RegisterBitOffset != 0) return 0;
if (gas.AddressSpaceId != 0x00 && gas.AddressSpaceId != 0x01) return 0;
if (gas.AddressSpaceId == 0x01 && gas.Address > 0xFFFFu) return 0;
uint8_t widthBytes = 0;
if (gas.AccessSize >= 1 && gas.AccessSize <= 3)
widthBytes = (uint8_t)(1u << (gas.AccessSize - 1u));
else if (gas.RegisterBitWidth)
widthBytes = (uint8_t)((gas.RegisterBitWidth + 7u) / 8u);
if (widthBytes < 2u) widthBytes = 2u;
if (widthBytes > 4u) widthBytes = 4u;
out->AddressSpaceId = gas.AddressSpaceId;
out->WidthBytes = widthBytes;
out->Address = gas.Address;
return 1;
}
// Decodes one byte-wide ACPI GAS used by the HW-reduced Sleep Control register.
static int AcpiReadByteGasRegister(const void* table, uint32_t tableLen, uint32_t off,
ACPI_FIXED_REGISTER* out) {
if (!out || off + sizeof(ACPI_GAS) > tableLen) return 0;
ACPI_GAS gas;
memcpy(&gas, (const uint8_t*)table + off, sizeof(gas));
if (!gas.Address || gas.RegisterBitOffset != 0 || gas.RegisterBitWidth != 8) return 0;
if (gas.AddressSpaceId != 0x00 && gas.AddressSpaceId != 0x01) return 0;
if (gas.AddressSpaceId == 0x01 && gas.Address > 0xFFFFu) return 0;
out->AddressSpaceId = gas.AddressSpaceId;
out->WidthBytes = 1u;
out->Address = gas.Address;
return 1;
}
// Reads one ACPI fixed register while preserving the address-space distinction.
static uint32_t AcpiReadFixedRegister(const ACPI_FIXED_REGISTER* reg) {
if (!reg || !reg->Address) return 0;
if (reg->AddressSpaceId == 0x01) {
uint16_t port = (uint16_t)reg->Address;
if (reg->WidthBytes <= 1u) return __inbyte(port);
if (reg->WidthBytes == 2u) return __inword(port);
return __indword(port);
}
volatile void* address = HalPhysicalToVirtual(reg->Address);
if (reg->WidthBytes <= 1u) return *(volatile uint8_t*)address;
if (reg->WidthBytes == 2u) return *(volatile uint16_t*)address;
return *(volatile uint32_t*)address;
}
// Writes one ACPI fixed register using the width advertised by firmware.
static void AcpiWriteFixedRegister(const ACPI_FIXED_REGISTER* reg, uint32_t value) {
if (!reg || !reg->Address) return;
if (reg->AddressSpaceId == 0x01) {
uint16_t port = (uint16_t)reg->Address;
if (reg->WidthBytes <= 1u) __outbyte(port, (uint8_t)value);
else if (reg->WidthBytes == 2u) __outword(port, (uint16_t)value);
else __outdword(port, value);
return;
}
volatile void* address = HalPhysicalToVirtual(reg->Address);
if (reg->WidthBytes <= 1u) *(volatile uint8_t*)address = (uint8_t)value;
else if (reg->WidthBytes == 2u) *(volatile uint16_t*)address = (uint16_t)value;
else *(volatile uint32_t*)address = value;
}
// Detects whether PM1 control is using the standard Intel PCH PMBASE layout.
static void AcpiDetectIntelPchPmBase(void) {
gIntelPchPmBase = 0;
if (!gApi.Pci.Read32 || gPm1aCnt.AddressSpaceId != 0x01 ||
gPm1aCnt.Address > 0xFFFFu || (gPm1aCnt.Address & 0x7Fu) != 0x04u) {
return;
}
uint32_t id = gApi.Pci.Read32(0, 0, 31, 0, 0x00u);
uint32_t classRevision = gApi.Pci.Read32(0, 0, 31, 0, 0x08u);
if ((id & 0xFFFFu) == 0x8086u && ((classRevision >> 16) & 0xFFFFu) == 0x0601u)
gIntelPchPmBase = 1;
}
// Disables Intel PCH sleep trapping so SLP_EN reaches the hardware sleep transition.
static void AcpiDisableIntelSleepSmiTrap(void) {
if (!gIntelPchPmBase) return;
uint64_t pmBase = gPm1aCnt.Address - 4u;
if (pmBase > 0xFFFFu - INTEL_PCH_SMI_EN_OFFSET - 3u) return;
uint16_t smiEnablePort = (uint16_t)(pmBase + INTEL_PCH_SMI_EN_OFFSET);
uint32_t smiEnable = __indword(smiEnablePort);
if (!(smiEnable & INTEL_PCH_SMI_ON_SLP_EN)) return;
__outdword(smiEnablePort, smiEnable & ~INTEL_PCH_SMI_ON_SLP_EN);
if (__indword(smiEnablePort) & INTEL_PCH_SMI_ON_SLP_EN)
HalDbgPrint("HAL: Intel PCH refused to clear SMI_ON_SLP_EN.\n");
}
// Requests ACPI mode when firmware left fixed power registers in legacy mode.
static KSTATUS AcpiEnableModeForShutdown(void) {
if (!gPm1aCnt.Address) return NTLSTATUS_NOT_SUPPORTED;
if (AcpiReadFixedRegister(&gPm1aCnt) & 0x0001u) return NTLSTATUS_SUCCESS;
if (!gSmiCommandPort || !gAcpiEnableValue || gSmiCommandPort > 0xFFFFu)
return NTLSTATUS_NOT_SUPPORTED;
__outbyte((uint16_t)gSmiCommandPort, gAcpiEnableValue);
uint64_t startTick = gApi.Timer.QueryTicks ? gApi.Timer.QueryTicks() : 0;
uint64_t deadline = startTick > UINT64_MAX - 3000000ull ? UINT64_MAX : startTick + 3000000ull;
uint32_t fallbackWaits = 0;
for (;;) {
if (AcpiReadFixedRegister(&gPm1aCnt) & 0x0001u) return NTLSTATUS_SUCCESS;
if (gApi.Timer.QueryTicks && gApi.Timer.QueryTicks() >= deadline) return NTLSTATUS_TIMEOUT;
if (++fallbackWaits >= 3000u) return NTLSTATUS_TIMEOUT;
if (gApi.Timer.Stall) gApi.Timer.Stall(1000u);
else _mm_pause();
}
}
static int AmlParsePkgLength(const uint8_t* p, const uint8_t* end,
const uint8_t** body, const uint8_t** pkgEnd) {
if (p >= end) return 0;
const uint8_t* start = p;
uint8_t first = *p++;
uint32_t follow = first >> 6;
uint32_t length;
if (follow == 0) {
length = first & 0x3F;
} else {
length = first & 0x0F;
for (uint32_t i = 0; i < follow; i++) {
if (p >= end) return 0;
length |= ((uint32_t)*p++) << (4 + i * 8);
}
}
if (start + length < p || start + length > end) return 0;
*body = p;
*pkgEnd = start + length;
return 1;
}
static const uint8_t* AmlParseNameString(const uint8_t* p, const uint8_t* end,
int* lastSegmentIsS5) {
uint32_t segments = 1;
*lastSegmentIsS5 = 0;
// Optional root or parent prefixes: '\\', '^', '^^', ...
while (p < end && (*p == 0x5C || *p == 0x5E)) p++;
if (p >= end) return NULL;
if (*p == 0x00) { // NullName
return p + 1;
} else if (*p == 0x2E) { // DualNamePrefix
p++;
segments = 2;
} else if (*p == 0x2F) { // MultiNamePrefix
p++;
if (p >= end) return NULL;
segments = *p++;
}
for (uint32_t s = 0; s < segments; s++) {
if (p + 4 > end) return NULL;
if (s == segments - 1 && memcmp(p, "_S5_", 4) == 0)
*lastSegmentIsS5 = 1;
p += 4;
}
return p;
}
// Implements the aml Read Integer operation.
static int AmlReadInteger(const uint8_t** pp, const uint8_t* end, uint8_t* out) {
const uint8_t* p = *pp;
if (p >= end) return 0;
switch (*p++) {
case 0x00: // ZeroOp
*out = 0;
break;
case 0x01: // OneOp
*out = 1;
break;
case 0x0A: // BytePrefix
if (p >= end) return 0;
*out = *p++;
break;
case 0x0B: // WordPrefix
if (p + 2 > end) return 0;
*out = p[0];
p += 2;
break;
case 0x0C: // DWordPrefix
if (p + 4 > end) return 0;
*out = p[0];
p += 4;
break;
case 0x0E: // QWordPrefix
if (p + 8 > end) return 0;
*out = p[0];
p += 8;
break;
default:
return 0;
}
*pp = p;
return 1;
}
// Scan DSDT AML for the _S5_ Name object and extract SLP_TYP values. Real
// firmware commonly encodes the name as "\\_S5_" instead of just "_S5_", and
// package integers may be ZeroOp/OneOp/Byte/Word/DWord/QWord constants.
static int ScanAmlForS5(const uint8_t* aml, uint32_t len, const char* source) {
const uint8_t* end = aml + len;
for (const uint8_t* cur = aml; cur < end; cur++) {
if (*cur != 0x08) continue; // NameOp
const uint8_t* p = cur + 1;
int isS5 = 0;
p = AmlParseNameString(p, end, &isS5);
if (!p || !isS5 || p >= end || *p != 0x12) continue; // PackageOp
const uint8_t* body;
const uint8_t* pkgEnd;
if (!AmlParsePkgLength(p + 1, end, &body, &pkgEnd)) continue;
if (body >= pkgEnd) continue;
uint8_t elements = *body++;
if (elements < 2) continue;
uint8_t typA, typB;
if (!AmlReadInteger(&body, pkgEnd, &typA)) continue;
if (!AmlReadInteger(&body, pkgEnd, &typB)) continue;
gSlpTypA = typA & 0x07;
gSlpTypB = typB & 0x07;
gS5Found = 1;
HalDbgPrint("HAL: _S5_ source=%s SLP_TYP_A=0x%02x SLP_TYP_B=0x%02x\n",
source ? source : "AML", gSlpTypA, gSlpTypB);
return 1;
}
return 0;
}
// Validates checksum.
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;
}
// Parses fadt.
static void ParseFadt(const ACPISDTHeader* header) {
const void* fadt = header;
uint32_t len = header->Length;
memset(&gPm1aCnt, 0, sizeof(gPm1aCnt));
memset(&gPm1bCnt, 0, sizeof(gPm1bCnt));
memset(&gSleepControl, 0, sizeof(gSleepControl));
gLegacyPm1aPort = 0;
gLegacyPm1bPort = 0;
gFadtFlags = AcpiReadU32(fadt, len, FADT_FLAGS_OFFSET);
gHwReducedAcpi = (gFadtFlags & FADT_FLAG_HW_REDUCED_ACPI) != 0;
uint8_t pm1CntLength = FADT_PM1_CNT_LEN_OFFSET < len
? *((const uint8_t*)fadt + FADT_PM1_CNT_LEN_OFFSET)
: 2u;
if (pm1CntLength < 2u) pm1CntLength = 2u;
if (pm1CntLength > 4u) pm1CntLength = 4u;
uint32_t legacyPm1a = AcpiReadU32(fadt, len, FADT_PM1A_CNT_BLK_OFFSET);
uint32_t legacyPm1b = AcpiReadU32(fadt, len, FADT_PM1B_CNT_BLK_OFFSET);
gLegacyPm1aPort = legacyPm1a <= 0xFFFFu ? legacyPm1a : 0u;
gLegacyPm1bPort = legacyPm1b <= 0xFFFFu ? legacyPm1b : 0u;
if (legacyPm1a && legacyPm1a <= 0xFFFFu) {
gPm1aCnt.AddressSpaceId = 0x01;
gPm1aCnt.WidthBytes = pm1CntLength;
gPm1aCnt.Address = legacyPm1a;
}
if (legacyPm1b && legacyPm1b <= 0xFFFFu) {
gPm1bCnt.AddressSpaceId = 0x01;
gPm1bCnt.WidthBytes = pm1CntLength;
gPm1bCnt.Address = legacyPm1b;
}
// ACPI 2.0+ GAS fields supersede the legacy blocks when firmware supplies them.
ACPI_FIXED_REGISTER extended;
memset(&extended, 0, sizeof(extended));
if (AcpiReadGasRegister(fadt, len, FADT_X_PM1A_CNT_BLK_OFFSET, &extended))
gPm1aCnt = extended;
memset(&extended, 0, sizeof(extended));
if (AcpiReadGasRegister(fadt, len, FADT_X_PM1B_CNT_BLK_OFFSET, &extended))
gPm1bCnt = extended;
if (gHwReducedAcpi)
(void)AcpiReadByteGasRegister(fadt, len, FADT_SLEEP_CONTROL_OFFSET, &gSleepControl);
gSmiCommandPort = AcpiReadU32(fadt, len, FADT_SMI_CMD_OFFSET);
gAcpiEnableValue = FADT_ACPI_ENABLE_OFFSET < len
? *((const uint8_t*)fadt + FADT_ACPI_ENABLE_OFFSET)
: 0u;
uint64_t dsdtPhys = AcpiReadU64(fadt, len, FADT_X_DSDT_OFFSET);
if (!dsdtPhys)
dsdtPhys = AcpiReadU32(fadt, len, FADT_DSDT_OFFSET);
uint8_t centuryRegister = 0;
if (FADT_CENTURY_OFFSET < len)
centuryRegister = *((const uint8_t*)fadt + FADT_CENTURY_OFFSET);
HalRtcSetCenturyRegister(centuryRegister);
HalDbgPrint("HAL: FADT flags=0x%08x hw_reduced=%u PM1a_CNT=%u:0x%016llx legacy=0x%04x "
"PM1b_CNT=%u:0x%016llx legacy=0x%04x SLEEP_CTL=%u:0x%016llx "
"SMI=0x%08x DSDT=0x%016llx RTC_CENTURY=0x%02x\n",
gFadtFlags, gHwReducedAcpi ? 1u : 0u,
gPm1aCnt.AddressSpaceId, gPm1aCnt.Address, gLegacyPm1aPort,
gPm1bCnt.AddressSpaceId, gPm1bCnt.Address, gLegacyPm1bPort,
gSleepControl.AddressSpaceId, gSleepControl.Address,
gSmiCommandPort, dsdtPhys, centuryRegister);
if (dsdtPhys) {
const ACPISDTHeader* dsdt = (const ACPISDTHeader*)(dsdtPhys + gPhysBase);
if (memcmp(dsdt->Signature, "DSDT", 4) == 0 &&
// Validates checksum.
validate_checksum((void*)dsdt, dsdt->Length)) {
const uint8_t* aml = (const uint8_t*)(dsdt + 1);
(void)ScanAmlForS5(aml, dsdt->Length - (uint32_t)sizeof(ACPISDTHeader), "DSDT");
} else {
HalDbgPrint("HAL: DSDT invalid or checksum failed\n");
}
}
}
// Implements the add Cpu Record operation.
static void HalAddCpuRecord(uint32_t uid, uint32_t lapicId, uint32_t flags) {
if (gCpuTopology.Count >= HAL_MAX_PROCESSORS)
return;
for (uint32_t i = 0; i < gCpuTopology.Count; i++) {
if (gCpuTopology.Processors[i].HardwareId == lapicId)
return;
}
HAL_PROCESSOR_INFO* cpu = &gCpuTopology.Processors[gCpuTopology.Count++];
memset(cpu, 0, sizeof(*cpu));
cpu->FirmwareId = uid;
cpu->HardwareId = lapicId;
cpu->Flags = flags;
cpu->Enabled = (flags & 1u) ? 1u : 0u;
}
// Parses madt.
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 + 2 <= end) {
uint8_t type = p[0], len = p[1];
if (!len || p + len > end) break;
if (type == 0 && len >= 8) { // Processor Local APIC
uint32_t uid = p[2];
uint32_t lapicId = p[3];
uint32_t flags = *(uint32_t*)(p + 4);
if (flags & 1u)
HalAddCpuRecord(uid, lapicId, flags);
} else if (type == 1 && len >= 12) { // I/O APIC entry
gIoapicPhys = *(uint32_t*)(p + 4);
} else if (type == 5 && len >= 12) { // Local APIC Address Override
gLapicPhys = (uintptr_t)*(uint64_t*)(p + 4);
} else if (type == 9 && len >= 16) { // Processor Local x2APIC
uint32_t lapicId = *(uint32_t*)(p + 4);
uint32_t flags = *(uint32_t*)(p + 8);
uint32_t uid = *(uint32_t*)(p + 12);
if (flags & 1u)
HalAddCpuRecord(uid, lapicId, flags);
}
p += len;
}
}
// Implements the walk Acpi operation.
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) &&
// Implements the memcmp operation.
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) &&
// Implements the memcmp operation.
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(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;
} else if (memcmp(header->Signature, "SSDT", 4) == 0 && !gS5Found &&
header->Length >= sizeof(ACPISDTHeader)) {
const uint8_t* aml = (const uint8_t*)(header + 1);
(void)ScanAmlForS5(aml, header->Length - (uint32_t)sizeof(ACPISDTHeader), "SSDT");
}
}
if (!gS5Found)
HalDbgPrint("HAL: _S5_ not found in DSDT or SSDT AML; ACPI S5 unavailable\n");
}
// HalShutdown - ACPI S5 soft power-off. Does not return.
static void HalShutdown(void) {
if (!gS5Found) {
HalDbgPrint("HAL: shutdown unsupported: firmware _S5_ object was not resolved.\n");
HalStopProcessor();
}
if (gHwReducedAcpi) {
if (!gSleepControl.Address) {
HalDbgPrint("HAL: shutdown unsupported: HW-reduced ACPI has no Sleep Control register.\n");
HalStopProcessor();
}
uint32_t sleepValue = ((uint32_t)gSlpTypA << 2) | (1u << 5);
(void)HalDisableInterrupts();
AcpiWriteFixedRegister(&gSleepControl, sleepValue);
} else {
if (!gPm1aCnt.Address) {
HalDbgPrint("HAL: shutdown unsupported: PM1a control register is unavailable.\n");
HalStopProcessor();
}
// Some Intel PCH firmware leaves SMI_ON_SLP_EN armed after boot. When set,
// the chipset traps SLP_EN into SMM and explicitly suppresses the sleep transition.
AcpiDisableIntelSleepSmiTrap();
// Try the fixed-register S5 request before changing ACPI ownership. A late
// SMI_CMD transition can itself enter firmware SMM on machines that boot with SCI_EN clear.
uint32_t valA = AcpiReadFixedRegister(&gPm1aCnt);
valA &= ~(0x7u << 10);
valA |= ((uint32_t)gSlpTypA << 10) | (1u << 13);
uint32_t valB = 0u;
if (gPm1bCnt.Address) {
valB = AcpiReadFixedRegister(&gPm1bCnt);
valB &= ~(0x7u << 10);
valB |= ((uint32_t)gSlpTypB << 10) | (1u << 13);
}
uintptr_t interruptFlags = HalDisableInterrupts();
AcpiWriteFixedRegister(&gPm1aCnt, valA);
if (gPm1bCnt.Address)
AcpiWriteFixedRegister(&gPm1bCnt, valB);
if (gApi.Timer.Stall) gApi.Timer.Stall(50000u);
// If the direct request returned, fall back to the firmware ownership path.
HalRestoreInterrupts(interruptFlags);
(void)AcpiEnableModeForShutdown();
AcpiDisableIntelSleepSmiTrap();
valA = AcpiReadFixedRegister(&gPm1aCnt);
valA &= ~(0x7u << 10);
valA |= ((uint32_t)gSlpTypA << 10) | (1u << 13);
if (gPm1bCnt.Address) {
valB = AcpiReadFixedRegister(&gPm1bCnt);
valB &= ~(0x7u << 10);
valB |= ((uint32_t)gSlpTypB << 10) | (1u << 13);
}
(void)HalDisableInterrupts();
AcpiWriteFixedRegister(&gPm1aCnt, valA);
if (gPm1bCnt.Address)
AcpiWriteFixedRegister(&gPm1bCnt, valB);
if (gApi.Timer.Stall) gApi.Timer.Stall(50000u);
bool selectedPm1aIsLegacy = gPm1aCnt.AddressSpaceId == 0x01 &&
gPm1aCnt.Address == gLegacyPm1aPort;
if (gLegacyPm1aPort && !selectedPm1aIsLegacy) {
uint16_t legacyA = __inword((uint16_t)gLegacyPm1aPort);
legacyA = (uint16_t)((legacyA & ~(0x7u << 10)) |
((uint16_t)gSlpTypA << 10) | (1u << 13));
uint16_t legacyB = 0u;
if (gLegacyPm1bPort) {
legacyB = __inword((uint16_t)gLegacyPm1bPort);
legacyB = (uint16_t)((legacyB & ~(0x7u << 10)) |
((uint16_t)gSlpTypB << 10) | (1u << 13));
}
AcpiDisableIntelSleepSmiTrap();
__outword((uint16_t)gLegacyPm1aPort, legacyA);
if (gLegacyPm1bPort)
__outword((uint16_t)gLegacyPm1bPort, legacyB);
}
}
if (gApi.Timer.Stall) gApi.Timer.Stall(100000u);
HalDbgPrint("HAL: shutdown failed: firmware returned from S5 request.\n");
HalStopProcessor();
}
static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out) {
if (!out) return NTLSTATUS_INVALID_PARAMETER;
memset(out, 0, sizeof(*out));
*out = gCpuTopology;
if (!out->Count) {
out->Count = 1;
out->BootProcessorHardwareId = HalApicGetBootProcessorId();
out->Processors[0].HardwareId = out->BootProcessorHardwareId;
out->Processors[0].FirmwareId = 0;
out->Processors[0].Flags = 1;
out->Processors[0].Enabled = 1;
out->Processors[0].IsBootProcessor = 1;
out->Processors[0].Startable = 1;
return NTLSTATUS_SUCCESS;
}
uint32_t bsp = HalApicGetBootProcessorId();
out->BootProcessorHardwareId = bsp;
int foundBsp = 0;
for (uint32_t i = 0; i < out->Count; i++) {
out->Processors[i].IsBootProcessor = (out->Processors[i].HardwareId == bsp) ? 1u : 0u;
out->Processors[i].Startable = out->Processors[i].IsBootProcessor || HalApicCanAddressId(out->Processors[i].HardwareId);
if (out->Processors[i].IsBootProcessor) foundBsp = 1;
}
if (!foundBsp && out->Count < HAL_MAX_PROCESSORS) {
HAL_PROCESSOR_INFO* cpu = &out->Processors[out->Count++];
memset(cpu, 0, sizeof(*cpu));
cpu->HardwareId = bsp;
cpu->FirmwareId = bsp;
cpu->Flags = 1;
cpu->Enabled = 1;
cpu->IsBootProcessor = 1;
cpu->Startable = 1;
}
return NTLSTATUS_SUCCESS;
}
HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
if (!initParams || initParams->Size != sizeof(HAL_INIT_PARAMS) || initParams->Flags != 0 ||
!initParams->PhysicalWindowBase || !initParams->AllocatePool)
return NTLSTATUS_INVALID_PARAMETER;
if (gInitialised) return NTLSTATUS_ALREADY_EXISTS;
gPhysBase = initParams->PhysicalWindowBase;
debugEnabled = initParams->Debug;
HalOutputInitialize(&gApi.Output);
HalSerialInitialize(&gApi.Output);
// Screen follows serial so diagnostics still work without a framebuffer.
HalScreenInitialize(initParams->AllocatePool, initParams->FramebufferBase + initParams->PhysicalWindowBase,
(int)initParams->FramebufferWidth,
(int)initParams->FramebufferHeight,
(int)initParams->FramebufferStride,
&gApi.Screen);
HalScreenRegisterOutputEndpoint(&gApi.Output);
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);
HalDbgPrint("HAL: local APIC mode=%s BSP_ID=%u\n",
HalApicUsingX2Apic() ? "x2APIC" : "xAPIC",
HalApicGetBootProcessorId());
KSTATUS ts = HalTimerInitialize(gLapicPhys, gHpetPhys, &gApi.Timer);
if (!KSUCCESS(ts)) HalDbgPrint("HAL: HPET unavailable, using fallback\n");
HalRtcInitialize(&gApi.Rtc);
HalPciInitialize(&gApi.Pci);
gApi.Power.Shutdown = HalShutdown;
AcpiDetectIntelPchPmBase();
gInitialised = 1;
// The 8042 can leave output bytes pending while the keyboard/mouse drivers
// are being brought up with interrupts temporarily disabled. Drain the
// controller before returning from HAL init so stale status/data bytes do
// not suppress the next keyboard or mouse interrupt edge.
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 NTLSTATUS_SUCCESS;
}
// Returns api.
HAL_ENTRY const HAL_API* HalGetApi(void) { return &gApi; }