// 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 #include #include #include #include #include #include #include #include #include #include #include #include "acpi_local.h" #include // 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; } 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; void AcpiNtlWakeExecutor(void) { (void)KeInsertQueueDpc(&gWakeDpc, NULL, NULL); } 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--; } bool deferred = AcpiNtlEcHasDeferred() || AcpiNtlFixedButtonHasDeferred(); gWorkActive = item.Callback != NULL || deferred; bool running = gWorkerRunning; KeReleaseSpinlock(&gWorkLock, flags); if (!item.Callback && !deferred) { if (!running) goto done; break; } // Service deferred firmware events on the existing worker. if (deferred) { (void)AcpiNtlEcDrainDeferred(); AcpiNtlFixedButtonDrainDeferred(); } if (item.Callback) 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, ¶ms, &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 || AcpiNtlEcHasDeferred() || AcpiNtlFixedButtonHasDeferred(); 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] && ((KeGetDebugFlags() & KE_DEBUG_ACPI_INFO) || strncmp(complete, "ACPI: ", 6) != 0)) DbgPrint("acpi: %s\n", complete); } } void AcpiOsRedirectOutput(void* destination) { (void)destination; }