Files
2026-10-27 09:28:31 +01:00

462 lines
19 KiB
C

// nt-lite OS Services Layer for the ACPICA table and namespace loader.
#include "third_party/acpica/include/acpi.h"
#include "third_party/acpica/include/aclocal.h"
#include "third_party/acpica/include/acobject.h"
#include "third_party/acpica/include/acglobal.h"
#include <ntlite/ddk/ddk.h>
#include <ntlite/ddk/platform.h>
#include <ntlite/ddk/semaphore.h>
#include <ntlite/ddk/object.h>
#include <ntlite/ddk/thread.h>
#include <ntlite/ddk/timer.h>
#include <ntlite/ddk/scheduler.h>
#include <ntlite/ddk/debug.h>
#include <ntlite/ddk/interrupt.h>
#include <ntlite/ddk/dpc.h>
#include <ntlite/ddk/event.h>
#include <intrin.h>
#include "acpi_local.h"
#include <string.h>
// The freestanding formatted-output routine comes from ACPICA utprint.c.
int vsnprintf(char* destination, size_t size, const char* format, va_list args);
static bool gRuntimeMemoryRestricted;
ACPI_STATUS AcpiOsInitialize(void) { gRuntimeMemoryRestricted = false; return AE_OK; }
ACPI_STATUS AcpiOsTerminate(void) { gRuntimeMemoryRestricted = false; return AE_OK; }
ACPI_PHYSICAL_ADDRESS AcpiOsGetRootPointer(void) {
uint64_t root = 0;
return KSUCCESS(KeFirmwareQueryAcpiRsdp(&root)) ? root : 0;
}
ACPI_STATUS AcpiOsPredefinedOverride(const ACPI_PREDEFINED_NAMES* entry, ACPI_STRING* value) {
(void)entry;
if (!value) return AE_BAD_PARAMETER;
*value = NULL;
return AE_OK;
}
ACPI_STATUS AcpiOsTableOverride(ACPI_TABLE_HEADER* original, ACPI_TABLE_HEADER** replacement) {
(void)original;
if (!replacement) return AE_BAD_PARAMETER;
*replacement = NULL;
return AE_OK;
}
ACPI_STATUS AcpiOsPhysicalTableOverride(ACPI_TABLE_HEADER* original, ACPI_PHYSICAL_ADDRESS* address, UINT32* length) {
(void)original;
if (!address || !length) return AE_BAD_PARAMETER;
*address = 0;
*length = 0;
return AE_OK;
}
ACPI_STATUS AcpiOsCreateLock(ACPI_SPINLOCK* handle) {
if (!handle) return AE_BAD_PARAMETER;
SPINLOCK* lock = (SPINLOCK*)MmAllocatePool(sizeof(*lock));
if (!lock) return AE_NO_MEMORY;
memset(lock, 0, sizeof(*lock));
*handle = (ACPI_SPINLOCK)lock;
return AE_OK;
}
void AcpiOsDeleteLock(ACPI_SPINLOCK handle) { MmFreePool(handle); }
ACPI_CPU_FLAGS AcpiOsAcquireLock(ACPI_SPINLOCK handle) {
return (ACPI_CPU_FLAGS)KeAcquireSpinlock((SPINLOCK*)handle);
}
void AcpiOsReleaseLock(ACPI_SPINLOCK handle, ACPI_CPU_FLAGS flags) {
KeReleaseSpinlock((SPINLOCK*)handle, (uintptr_t)flags);
}
ACPI_STATUS AcpiOsCreateSemaphore(UINT32 max, UINT32 initial, ACPI_SEMAPHORE* handle) {
if (!handle || !max || initial > max || initial > INT32_MAX) return AE_BAD_PARAMETER;
int32_t nativeMax = max > INT32_MAX ? INT32_MAX : (int32_t)max;
SEMAPHORE* sem = NULL;
KSTATUS status = KeSemaphoreCreate((int32_t)initial, nativeMax, &sem);
if (!KSUCCESS(status)) return AE_NO_MEMORY;
*handle = (ACPI_SEMAPHORE)sem;
return AE_OK;
}
ACPI_STATUS AcpiOsDeleteSemaphore(ACPI_SEMAPHORE handle) {
if (!handle) return AE_BAD_PARAMETER;
ObDereferenceObject((OBJECT*)handle);
return AE_OK;
}
ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE handle, UINT32 units, UINT16 timeout) {
if (!handle || !units) return AE_BAD_PARAMETER;
uint64_t ms = timeout == ACPI_WAIT_FOREVER ? KE_WAIT_INFINITE : timeout;
for (UINT32 i = 0; i < units; i++) {
KSTATUS result = KeWaitSemaphore((SEMAPHORE*)handle, ms);
if (result != NTLSTATUS_SUCCESS) {
while (i--) KeSignalSemaphore((SEMAPHORE*)handle);
return result == NTLSTATUS_TIMEOUT ? AE_TIME : AE_ERROR;
}
}
return AE_OK;
}
ACPI_STATUS AcpiOsSignalSemaphore(ACPI_SEMAPHORE handle, UINT32 units) {
if (!handle || !units) return AE_BAD_PARAMETER;
for (UINT32 i = 0; i < units; i++) KeSignalSemaphore((SEMAPHORE*)handle);
return AE_OK;
}
void* AcpiOsAllocate(ACPI_SIZE size) { return MmAllocatePool((size_t)size); }
void AcpiOsFree(void* memory) { MmFreePool(memory); }
// Firmware tables remain on the firmware RAM mapping. Device OperationRegions
// receive distinct UC mappings with explicit lifetimes.
void AcpiNtlRestrictRuntimeMappings(void) { gRuntimeMemoryRestricted = true; }
static bool AcpiNtlIsFirmwareTableSpan(uint64_t physical, uint64_t length) {
const ACPI_TABLE_LIST* list = &AcpiGbl_RootTableList;
for (UINT32 i = 0; i < list->CurrentTableCount; i++) {
const ACPI_TABLE_DESC* table = &list->Tables[i];
if (table->Length && physical >= table->Address &&
physical - table->Address < table->Length &&
length <= table->Length - (physical - table->Address)) return true;
}
return false;
}
void* AcpiOsMapMemory(ACPI_PHYSICAL_ADDRESS physical, ACPI_SIZE length) {
if (!length || physical > UINT64_MAX - (uint64_t)length) return NULL;
if (!gRuntimeMemoryRestricted ||
AcpiNtlIsFirmwareTableSpan((uint64_t)physical, (uint64_t)length) ||
(AcpiGbl_FADT.XFacs && physical >= AcpiGbl_FADT.XFacs &&
physical - AcpiGbl_FADT.XFacs < sizeof(ACPI_TABLE_FACS) &&
length <= sizeof(ACPI_TABLE_FACS) - (physical - AcpiGbl_FADT.XFacs)))
return MmDdkPhysToVirt((uint64_t)physical);
void* mapped = NULL;
return KSUCCESS(MmDdkMapIoMemory((uint64_t)physical, (size_t)length, &mapped)) ? mapped : NULL;
}
void AcpiOsUnmapMemory(void* address, ACPI_SIZE length) {
if (!address || !length) return;
(void)MmDdkUnmapIoMemory(address, (size_t)length);
}
ACPI_STATUS AcpiOsGetPhysicalAddress(void* address, ACPI_PHYSICAL_ADDRESS* physical) {
(void)address; (void)physical;
return AE_SUPPORT;
}
// ACPICA's SCI handler owns the PM1 and GPE acknowledgement protocol.
// The interrupt manager owns routing, affinity, vector and handler lifetime.
static KE_INTERRUPT* gSciInterrupt;
static ACPI_OSD_HANDLER gSciRoutine;
static void* gSciContext;
static UINT32 gSciGsi;
static void AcpiNtlSciInterrupt(void* unused) {
(void)unused;
if (gSciRoutine) (void)gSciRoutine(gSciContext);
}
ACPI_STATUS AcpiOsInstallInterruptHandler(UINT32 irq, ACPI_OSD_HANDLER routine, void* context) {
if (!routine || gSciInterrupt) return AE_BAD_PARAMETER;
gSciRoutine = routine;
gSciContext = context;
gSciGsi = irq;
KSTATUS status = irq < 16u
? KeInterruptConnectIsa((uint8_t)irq, 0x0Fu, 0, AcpiNtlSciInterrupt, NULL, &gSciInterrupt)
: KeInterruptConnectGsi(irq, 0x0Fu, 0, AcpiNtlSciInterrupt, NULL, &gSciInterrupt);
if (!KSUCCESS(status)) {
gSciRoutine = NULL;
gSciContext = NULL;
return AE_ERROR;
}
DbgPrint("acpi: SCI connected gsi=%u\n", irq);
return AE_OK;
}
ACPI_STATUS AcpiOsRemoveInterruptHandler(UINT32 irq, ACPI_OSD_HANDLER routine) {
if (!gSciInterrupt || irq != gSciGsi || routine != gSciRoutine) return AE_BAD_PARAMETER;
KE_INTERRUPT* interrupt = gSciInterrupt;
KeInterruptDisconnect(interrupt);
gSciInterrupt = NULL;
gSciRoutine = NULL;
gSciContext = NULL;
return AE_OK;
}
// AML callbacks run on a passive worker, including GPE and notify handlers.
// Queue nodes are preallocated because AcpiOsExecute may run in the SCI ISR.
#define ACPI_EXECUTOR_CAPACITY 256u
typedef struct ACPI_WORK_ITEM {
ACPI_OSD_EXEC_CALLBACK Callback;
void* Context;
} ACPI_WORK_ITEM;
static ACPI_WORK_ITEM gWorkItems[ACPI_EXECUTOR_CAPACITY];
static uint32_t gWorkHead, gWorkTail, gWorkCount;
static bool gWorkerRunning, gWorkActive;
static SPINLOCK gWorkLock = SPINLOCK_INIT;
static SEMAPHORE* gWorkSemaphore;
static EVENT* gWorkerFinished;
static THREAD* gWorkerThread;
static KDPC gWakeDpc;
static void AcpiNtlWakeDpc(KDPC* dpc, void* context, void* arg1, void* arg2) {
(void)dpc; (void)context; (void)arg1; (void)arg2;
if (gWorkSemaphore) KeSignalSemaphore(gWorkSemaphore);
}
static void AcpiNtlWorker(uintptr_t context) {
(void)context;
for (;;) {
(void)KeWaitSemaphore(gWorkSemaphore, KE_WAIT_INFINITE);
for (;;) {
ACPI_WORK_ITEM item = { 0 };
uintptr_t flags = KeAcquireSpinlock(&gWorkLock);
if (gWorkCount) {
item = gWorkItems[gWorkHead];
gWorkHead = (gWorkHead + 1u) % ACPI_EXECUTOR_CAPACITY;
gWorkCount--;
gWorkActive = true;
}
bool running = gWorkerRunning;
KeReleaseSpinlock(&gWorkLock, flags);
if (!item.Callback) {
if (!running) goto done;
break;
}
item.Callback(item.Context);
flags = KeAcquireSpinlock(&gWorkLock);
gWorkActive = false;
KeReleaseSpinlock(&gWorkLock, flags);
}
}
done:
KeSignalEvent(gWorkerFinished);
}
KSTATUS AcpiNtlStartExecutor(void) {
KSTATUS status = KeSemaphoreCreate(0, INT32_MAX, &gWorkSemaphore);
if (!KSUCCESS(status)) return status;
status = KeEventCreate(EventManualReset, false, &gWorkerFinished);
if (!KSUCCESS(status)) {
ObDereferenceObject((OBJECT*)gWorkSemaphore);
gWorkSemaphore = NULL;
return status;
}
KeInitializeDpc(&gWakeDpc, AcpiNtlWakeDpc, NULL);
gWorkHead = gWorkTail = gWorkCount = 0;
gWorkActive = false;
gWorkerRunning = true;
const KE_THREAD_CREATE_PARAMETERS params = KE_THREAD_CREATE_NAMED_NORMAL(PriorityHigh, "acpi.events");
status = KeCreateKernelThread((uintptr_t)AcpiNtlWorker, 0, &params, &gWorkerThread);
if (!KSUCCESS(status)) {
gWorkerRunning = false;
ObDereferenceObject((OBJECT*)gWorkerFinished);
ObDereferenceObject((OBJECT*)gWorkSemaphore);
gWorkerFinished = NULL;
gWorkSemaphore = NULL;
}
return status;
}
void AcpiNtlStopExecutor(void) {
if (!gWorkerThread) return;
AcpiOsWaitEventsComplete();
uintptr_t flags = KeAcquireSpinlock(&gWorkLock);
gWorkerRunning = false;
KeReleaseSpinlock(&gWorkLock, flags);
KeSignalSemaphore(gWorkSemaphore);
(void)KeWaitForEvent(gWorkerFinished, false);
ObDereferenceObject((OBJECT*)gWorkerThread);
ObDereferenceObject((OBJECT*)gWorkerFinished);
ObDereferenceObject((OBJECT*)gWorkSemaphore);
gWorkerThread = NULL;
gWorkerFinished = NULL;
gWorkSemaphore = NULL;
}
ACPI_THREAD_ID AcpiOsGetThreadId(void) { return (ACPI_THREAD_ID)KeCurrentThreadIdentity(); }
ACPI_STATUS AcpiOsExecute(ACPI_EXECUTE_TYPE type, ACPI_OSD_EXEC_CALLBACK routine, void* context) {
(void)type;
if (!routine) return AE_BAD_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&gWorkLock);
if (!gWorkerRunning || gWorkCount == ACPI_EXECUTOR_CAPACITY) {
KeReleaseSpinlock(&gWorkLock, flags);
return AE_NO_MEMORY;
}
gWorkItems[gWorkTail].Callback = routine;
gWorkItems[gWorkTail].Context = context;
gWorkTail = (gWorkTail + 1u) % ACPI_EXECUTOR_CAPACITY;
gWorkCount++;
KeReleaseSpinlock(&gWorkLock, flags);
(void)KeInsertQueueDpc(&gWakeDpc, NULL, NULL);
return AE_OK;
}
void AcpiOsWaitEventsComplete(void) {
if (!gWorkerThread) return;
for (;;) {
uintptr_t flags = KeAcquireSpinlock(&gWorkLock);
bool busy = gWorkCount != 0 || gWorkActive;
KeReleaseSpinlock(&gWorkLock, flags);
if (!busy) break;
(void)KeDelayThread(1);
}
}
void AcpiOsSleep(UINT64 milliseconds) {
if (milliseconds) (void)KeDelayThread(milliseconds);
}
void AcpiOsStall(UINT32 microseconds) { KeStallExecution(microseconds); }
ACPI_STATUS AcpiOsReadPort(ACPI_IO_ADDRESS address, UINT32* value, UINT32 width) {
if (!value || address > UINT16_MAX || (width != 8u && width != 16u && width != 32u))
return AE_BAD_PARAMETER;
if (width == 8u) *value = __inbyte((unsigned short)address);
else if (width == 16u) *value = __inword((unsigned short)address);
else *value = __indword((unsigned short)address);
return AE_OK;
}
ACPI_STATUS AcpiOsWritePort(ACPI_IO_ADDRESS address, UINT32 value, UINT32 width) {
if (address > UINT16_MAX || (width != 8u && width != 16u && width != 32u))
return AE_BAD_PARAMETER;
if (width == 8u) __outbyte((unsigned short)address, (unsigned char)value);
else if (width == 16u) __outword((unsigned short)address, (unsigned short)value);
else __outdword((unsigned short)address, value);
return AE_OK;
}
// FADT GAS registers can be touched in the SCI. Establish these UC mappings
// before enabling the SCI, so interrupt handlers never allocate page tables.
#define ACPI_GAS_WINDOW_COUNT 12u
typedef struct ACPI_GAS_WINDOW {
uint64_t Physical;
uint32_t Length;
volatile uint8_t* Virtual;
} ACPI_GAS_WINDOW;
static ACPI_GAS_WINDOW gGasWindows[ACPI_GAS_WINDOW_COUNT];
static uint32_t gGasWindowCount;
static KSTATUS AcpiNtlMapGas(const ACPI_GENERIC_ADDRESS* gas, uint32_t bytes) {
if (!gas || !gas->Address || gas->SpaceId != ACPI_ADR_SPACE_SYSTEM_MEMORY)
return NTLSTATUS_SUCCESS;
if (!bytes) bytes = (gas->BitWidth + 7u) / 8u;
if (!bytes || gGasWindowCount == ACPI_GAS_WINDOW_COUNT) return NTLSTATUS_INVALID_PARAMETER;
void* virtualAddress = NULL;
KSTATUS status = MmDdkMapIoMemory(gas->Address, bytes, &virtualAddress);
if (!KSUCCESS(status)) return status;
ACPI_GAS_WINDOW* window = &gGasWindows[gGasWindowCount++];
window->Physical = gas->Address;
window->Length = bytes;
window->Virtual = (volatile uint8_t*)virtualAddress;
return NTLSTATUS_SUCCESS;
}
KSTATUS AcpiNtlPrepareMmioRegisters(void) {
// ACPICA's normalized FADT contains the active PM and GPE register blocks.
const struct { const ACPI_GENERIC_ADDRESS* Gas; uint32_t Length; } regions[] = {
{ &AcpiGbl_FADT.XPm1aEventBlock, AcpiGbl_FADT.Pm1EventLength },
{ &AcpiGbl_FADT.XPm1bEventBlock, AcpiGbl_FADT.Pm1EventLength },
{ &AcpiGbl_FADT.XPm1aControlBlock, AcpiGbl_FADT.Pm1ControlLength },
{ &AcpiGbl_FADT.XPm1bControlBlock, AcpiGbl_FADT.Pm1ControlLength },
{ &AcpiGbl_FADT.XPm2ControlBlock, AcpiGbl_FADT.Pm2ControlLength },
{ &AcpiGbl_FADT.XPmTimerBlock, AcpiGbl_FADT.PmTimerLength },
{ &AcpiGbl_FADT.XGpe0Block, AcpiGbl_FADT.Gpe0BlockLength },
{ &AcpiGbl_FADT.XGpe1Block, AcpiGbl_FADT.Gpe1BlockLength },
{ &AcpiGbl_FADT.SleepControl, 1 },
{ &AcpiGbl_FADT.SleepStatus, 1 },
{ &AcpiGbl_FADT.ResetRegister, 1 },
};
for (uint32_t i = 0; i < sizeof(regions) / sizeof(regions[0]); i++) {
KSTATUS status = AcpiNtlMapGas(regions[i].Gas, regions[i].Length);
if (!KSUCCESS(status)) return status;
}
return NTLSTATUS_SUCCESS;
}
void AcpiNtlReleaseMmioRegisters(void) {
while (gGasWindowCount) {
ACPI_GAS_WINDOW* window = &gGasWindows[--gGasWindowCount];
(void)MmDdkUnmapIoMemory((void*)window->Virtual, window->Length);
window->Virtual = NULL;
}
}
static volatile uint8_t* AcpiNtlFindGas(uint64_t address, uint32_t bytes) {
if (!bytes || address > UINT64_MAX - bytes) return NULL;
for (uint32_t i = 0; i < gGasWindowCount; i++) {
const ACPI_GAS_WINDOW* region = &gGasWindows[i];
if (address >= region->Physical && address - region->Physical < region->Length &&
bytes <= region->Length - (address - region->Physical))
return region->Virtual + (size_t)(address - region->Physical);
}
return NULL;
}
ACPI_STATUS AcpiOsReadMemory(ACPI_PHYSICAL_ADDRESS address, UINT64* value, UINT32 width) {
if (!value || (width != 8u && width != 16u && width != 32u && width != 64u))
return AE_BAD_PARAMETER;
volatile uint8_t* mapped = AcpiNtlFindGas(address, width / 8u);
if (!mapped || (address & ((width / 8u) - 1u))) return AE_SUPPORT;
switch (width) {
case 8: *value = *mapped; break;
case 16: *value = *(volatile uint16_t*)mapped; break;
case 32: *value = *(volatile uint32_t*)mapped; break;
default: *value = *(volatile uint64_t*)mapped; break;
}
return AE_OK;
}
ACPI_STATUS AcpiOsWriteMemory(ACPI_PHYSICAL_ADDRESS address, UINT64 value, UINT32 width) {
if (width != 8u && width != 16u && width != 32u && width != 64u)
return AE_BAD_PARAMETER;
volatile uint8_t* mapped = AcpiNtlFindGas(address, width / 8u);
if (!mapped || (address & ((width / 8u) - 1u))) return AE_SUPPORT;
switch (width) {
case 8: *mapped = (uint8_t)value; break;
case 16: *(volatile uint16_t*)mapped = (uint16_t)value; break;
case 32: *(volatile uint32_t*)mapped = (uint32_t)value; break;
default: *(volatile uint64_t*)mapped = value; break;
}
return AE_OK;
}
ACPI_STATUS AcpiOsReadPciConfiguration(ACPI_PCI_ID* id, UINT32 reg, UINT64* value, UINT32 width) {
if (!id || !value || reg > 0xFFu || (width != 8u && width != 16u && width != 32u))
return AE_BAD_PARAMETER;
UINT32 raw = KePciReadConfig32(id->Segment, (uint8_t)id->Bus,
(uint8_t)id->Device, (uint8_t)id->Function, (uint16_t)(reg & ~3u));
if (width == 32u && (reg & 3u)) return AE_BAD_PARAMETER;
if (width == 16u && (reg & 1u)) return AE_BAD_PARAMETER;
if ((reg & 3u) + (width / 8u) > 4u) return AE_BAD_PARAMETER;
*value = ((UINT64)raw >> ((reg & 3u) * 8u)) & ((1ull << width) - 1u);
return AE_OK;
}
ACPI_STATUS AcpiOsWritePciConfiguration(ACPI_PCI_ID* id, UINT32 reg, UINT64 value, UINT32 width) {
if (!id || reg > 0xFCu || width != 32u || (reg & 3u)) return AE_SUPPORT;
KePciWriteConfig32(id->Segment, (uint8_t)id->Bus, (uint8_t)id->Device,
(uint8_t)id->Function, (uint16_t)reg, (uint32_t)value);
return AE_OK;
}
BOOLEAN AcpiOsReadable(void* address, ACPI_SIZE length) { return address && length ? TRUE : FALSE; }
BOOLEAN AcpiOsWritable(void* address, ACPI_SIZE length) { return address && length ? TRUE : FALSE; }
// KeQueryTicks returns microseconds; ACPICA expects 100-nanosecond units.
UINT64 AcpiOsGetTimer(void) { return KeQueryTicks() * 10u; }
ACPI_STATUS AcpiOsSignal(UINT32 function, void* info) {
(void)function; (void)info;
return AE_SUPPORT;
}
ACPI_STATUS AcpiOsEnterSleep(UINT8 state, UINT32 a, UINT32 b) {
(void)a; (void)b;
// ACPICA owns PM1 or extended sleep-control register programming.
return state == ACPI_STATE_S5 ? AE_OK : AE_SUPPORT;
}
void ACPI_INTERNAL_VAR_XFACE AcpiOsPrintf(const char* format, ...) {
va_list arguments;
va_start(arguments, format);
AcpiOsVprintf(format, arguments);
va_end(arguments);
}
static SPINLOCK gOutputLock = SPINLOCK_INIT;
static char gOutputLine[768];
static uint32_t gOutputLength;
void AcpiOsVprintf(const char* format, va_list args) {
char message[512];
vsnprintf(message, sizeof(message), format, args);
for (uint32_t i = 0; i < sizeof(message) && message[i]; i++) {
char complete[sizeof(gOutputLine)];
bool flush = false;
uintptr_t flags = KeAcquireSpinlock(&gOutputLock);
if (message[i] == '\n' || gOutputLength + 1 >= sizeof(gOutputLine)) {
memcpy(complete, gOutputLine, gOutputLength);
complete[gOutputLength] = 0;
gOutputLength = 0;
flush = true;
}
if (message[i] != '\n') gOutputLine[gOutputLength++] = message[i];
KeReleaseSpinlock(&gOutputLock, flags);
if (flush && complete[0]) DbgPrint("acpi: %s\n", complete);
}
}
void AcpiOsRedirectOutput(void* destination) { (void)destination; }