898 lines
31 KiB
C
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; }
|