// ACPI embedded-controller SystemIO transport and passive GPE query handling. #include "third_party/acpica/include/acpi.h" #include #include #include #include #include #include #include "acpi_local.h" #define ACPI_NTL_EC_COUNT 4u #define EC_STATUS_OBF 0x01u #define EC_STATUS_IBF 0x02u #define EC_STATUS_SCI 0x20u #define EC_CMD_READ 0x80u #define EC_CMD_WRITE 0x81u #define EC_CMD_QUERY 0x84u #define EC_POLL_COUNT 10000u typedef struct ACPI_NTL_EC { ACPI_HANDLE Handle; SEMAPHORE* Mutex; uint16_t DataPort; uint16_t CommandPort; ACPI_HANDLE GpeDevice; uint32_t Gpe; volatile long Pending; uint8_t PortCount; bool HaveGpe; bool HandlerInstalled; bool GpeInstalled; } ACPI_NTL_EC; static ACPI_NTL_EC gEc[ACPI_NTL_EC_COUNT]; static uint32_t gEcCount; static bool AcpiNtlEcWait(ACPI_NTL_EC* ec, uint8_t mask, bool asserted) { for (uint32_t i = 0; i < EC_POLL_COUNT; i++) { bool set = (__inbyte(ec->CommandPort) & mask) != 0; if (set == asserted) return true; KeStallExecution(10); } return false; } static bool AcpiNtlEcReadByte(ACPI_NTL_EC* ec, uint8_t offset, uint8_t* result) { if (!AcpiNtlEcWait(ec, EC_STATUS_IBF, false)) return false; __outbyte(ec->CommandPort, EC_CMD_READ); if (!AcpiNtlEcWait(ec, EC_STATUS_IBF, false)) return false; __outbyte(ec->DataPort, offset); if (!AcpiNtlEcWait(ec, EC_STATUS_OBF, true)) return false; *result = __inbyte(ec->DataPort); return true; } static bool AcpiNtlEcWriteByte(ACPI_NTL_EC* ec, uint8_t offset, uint8_t value) { if (!AcpiNtlEcWait(ec, EC_STATUS_IBF, false)) return false; __outbyte(ec->CommandPort, EC_CMD_WRITE); if (!AcpiNtlEcWait(ec, EC_STATUS_IBF, false)) return false; __outbyte(ec->DataPort, offset); if (!AcpiNtlEcWait(ec, EC_STATUS_IBF, false)) return false; __outbyte(ec->DataPort, value); return AcpiNtlEcWait(ec, EC_STATUS_IBF, false); } static ACPI_STATUS AcpiNtlEcRegion(UINT32 function, ACPI_PHYSICAL_ADDRESS address, UINT32 width, UINT64* value, void* context, void* regionContext) { (void)regionContext; ACPI_NTL_EC* ec = (ACPI_NTL_EC*)context; if (!ec || !value || !width || width > 64u || (width % 8u) || address > 0xFFu || width / 8u > 256u - address || (function != ACPI_READ && function != ACPI_WRITE)) return AE_BAD_PARAMETER; if (KeWaitSemaphore(ec->Mutex, KE_WAIT_INFINITE) != NTLSTATUS_SUCCESS) return AE_ERROR; UINT64 data = function == ACPI_WRITE ? *value : 0; bool success = true; for (uint32_t i = 0; i < width / 8u; i++) { if (function == ACPI_READ) { uint8_t byte = 0; success = AcpiNtlEcReadByte(ec, (uint8_t)(address + i), &byte); data |= (UINT64)byte << (8u * i); } else { success = AcpiNtlEcWriteByte(ec, (uint8_t)(address + i), (uint8_t)(data >> (8u * i))); } if (!success) break; } KeSignalSemaphore(ec->Mutex); if (!success) return AE_TIME; if (function == ACPI_READ) *value = data; return AE_OK; } // A disabled GPE remains masked until its passive query callback completes. static void AcpiNtlEcQuery(void* context) { ACPI_NTL_EC* ec = (ACPI_NTL_EC*)context; for (unsigned count = 0; count < 32u; count++) { if (KeWaitSemaphore(ec->Mutex, KE_WAIT_INFINITE) != NTLSTATUS_SUCCESS) break; uint8_t status = __inbyte(ec->CommandPort); bool ready = (status & EC_STATUS_SCI) != 0; bool success = false; uint8_t query = 0; if (ready && AcpiNtlEcWait(ec, EC_STATUS_IBF, false)) { __outbyte(ec->CommandPort, EC_CMD_QUERY); if (AcpiNtlEcWait(ec, EC_STATUS_OBF, true)) { query = __inbyte(ec->DataPort); success = true; } } KeSignalSemaphore(ec->Mutex); if (!ready || !success) break; if (query) { static const char digits[] = "0123456789ABCDEF"; char method[5] = { '_', 'Q', digits[query >> 4], digits[query & 15], 0 }; (void)AcpiEvaluateObject(ec->Handle, method, NULL, NULL); } } _InterlockedExchange(&ec->Pending, 0); if (ec->GpeInstalled) (void)AcpiFinishGpe(ec->GpeDevice, ec->Gpe); } static UINT32 AcpiNtlEcGpe(ACPI_HANDLE device, UINT32 number, void* context) { (void)device; (void)number; ACPI_NTL_EC* ec = (ACPI_NTL_EC*)context; if (_InterlockedCompareExchange(&ec->Pending, 1, 0) == 0) { if (ACPI_FAILURE(AcpiOsExecute(OSL_GPE_HANDLER, AcpiNtlEcQuery, ec))) { _InterlockedExchange(&ec->Pending, 2); AcpiNtlWakeExecutor(); } } // ACPICA leaves this GPE masked until the query worker calls FinishGpe. return ACPI_INTERRUPT_HANDLED; } // The ACPI executor drains deferred EC queries after queued work completes. bool AcpiNtlEcDrainDeferred(void) { bool drained = false; for (uint32_t i = 0; i < gEcCount; ++i) { ACPI_NTL_EC* ec = &gEc[i]; if (_InterlockedCompareExchange(&ec->Pending, 1, 2) != 2) continue; AcpiNtlEcQuery(ec); drained = true; } return drained; } bool AcpiNtlEcHasDeferred(void) { for (uint32_t i = 0; i < gEcCount; ++i) if (_InterlockedCompareExchange(&gEc[i].Pending, 2, 2) == 2) return true; return false; } static ACPI_STATUS AcpiNtlEcResource(ACPI_RESOURCE* resource, void* context) { ACPI_NTL_EC* ec = (ACPI_NTL_EC*)context; uint16_t port; if (resource->Type == ACPI_RESOURCE_TYPE_IO) port = resource->Data.Io.Minimum; else if (resource->Type == ACPI_RESOURCE_TYPE_FIXED_IO) port = resource->Data.FixedIo.Address; else return AE_OK; if (ec->PortCount == 0) ec->DataPort = port; else if (ec->PortCount == 1) ec->CommandPort = port; ec->PortCount++; return AE_OK; } static ACPI_STATUS AcpiNtlEcDiscover(ACPI_HANDLE handle, UINT32 depth, void* context, void** returnValue) { (void)depth; (void)context; (void)returnValue; if (gEcCount >= ACPI_NTL_EC_COUNT) return AE_OK; ACPI_NTL_EC* ec = &gEc[gEcCount]; ec->Handle = handle; ACPI_STATUS status = AcpiWalkResources(handle, "_CRS", AcpiNtlEcResource, ec); if (ACPI_FAILURE(status) || ec->PortCount != 2 || !ec->DataPort || !ec->CommandPort || ec->DataPort == ec->CommandPort) { DbgPrint("acpi: EC has unsupported _CRS resources st=0x%x\n", (unsigned)status); ec->PortCount = 0; return AE_OK; } if (KeSemaphoreCreate(1, 1, &ec->Mutex) != NTLSTATUS_SUCCESS) return AE_NO_MEMORY; status = AcpiInstallAddressSpaceHandler(handle, ACPI_ADR_SPACE_EC, AcpiNtlEcRegion, NULL, ec); if (ACPI_FAILURE(status)) { ObDereferenceObject((OBJECT*)ec->Mutex); ec->Mutex = NULL; return AE_OK; } ec->HandlerInstalled = true; ACPI_BUFFER result = { ACPI_ALLOCATE_BUFFER, NULL }; status = AcpiEvaluateObject(handle, "_GPE", NULL, &result); if (ACPI_SUCCESS(status) && result.Pointer) { ACPI_OBJECT* gpe = (ACPI_OBJECT*)result.Pointer; if (gpe->Type == ACPI_TYPE_INTEGER && gpe->Integer.Value <= UINT32_MAX) { ec->Gpe = (uint32_t)gpe->Integer.Value; ec->GpeDevice = NULL; ec->HaveGpe = true; } else if (gpe->Type == ACPI_TYPE_PACKAGE && gpe->Package.Count == 2 && gpe->Package.Elements && gpe->Package.Elements[0].Type == ACPI_TYPE_LOCAL_REFERENCE && gpe->Package.Elements[1].Type == ACPI_TYPE_INTEGER && gpe->Package.Elements[1].Integer.Value <= UINT32_MAX) { ec->GpeDevice = gpe->Package.Elements[0].Reference.Handle; ec->Gpe = (uint32_t)gpe->Package.Elements[1].Integer.Value; ec->HaveGpe = ec->GpeDevice != NULL; } AcpiOsFree(result.Pointer); } gEcCount++; return AE_OK; } ACPI_STATUS AcpiNtlEcInitialize(void) { // Region handlers are established before ACPICA executes _REG methods. return AcpiGetDevices("PNP0C09", AcpiNtlEcDiscover, NULL, NULL); } void AcpiNtlEcActivate(void) { for (uint32_t i = 0; i < gEcCount; i++) { ACPI_NTL_EC* ec = &gEc[i]; if (!ec->HaveGpe) continue; ACPI_STATUS status = AcpiInstallGpeHandler(ec->GpeDevice, ec->Gpe, ACPI_GPE_LEVEL_TRIGGERED, AcpiNtlEcGpe, ec); if (ACPI_SUCCESS(status)) { ec->GpeInstalled = true; status = AcpiEnableGpe(ec->GpeDevice, ec->Gpe); } if (ACPI_FAILURE(status)) DbgPrint("acpi: EC GPE enable failed gpe=%u status=0x%x\n", ec->Gpe, (unsigned)status); } } void AcpiNtlEcTerminate(void) { for (uint32_t i = 0; i < gEcCount; i++) { ACPI_NTL_EC* ec = &gEc[i]; if (ec->GpeInstalled) { (void)AcpiDisableGpe(ec->GpeDevice, ec->Gpe); (void)AcpiRemoveGpeHandler(ec->GpeDevice, ec->Gpe, AcpiNtlEcGpe); ec->GpeInstalled = false; } } AcpiOsWaitEventsComplete(); for (uint32_t i = 0; i < gEcCount; i++) { ACPI_NTL_EC* ec = &gEc[i]; if (ec->HandlerInstalled) (void)AcpiRemoveAddressSpaceHandler(ec->Handle, ACPI_ADR_SPACE_EC, AcpiNtlEcRegion); if (ec->Mutex) ObDereferenceObject((OBJECT*)ec->Mutex); ec->Mutex = NULL; } gEcCount = 0; } uint32_t AcpiNtlEcCount(void) { return gEcCount; }