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

231 lines
9.0 KiB
C

// ACPI embedded-controller SystemIO transport and passive GPE query handling.
#include "third_party/acpica/include/acpi.h"
#include <ntlite/ddk/ddk.h>
#include <ntlite/ddk/debug.h>
#include <ntlite/ddk/object.h>
#include <ntlite/ddk/semaphore.h>
#include <ntlite/ddk/platform.h>
#include <intrin.h>
#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, 0);
}
// ACPICA leaves this GPE masked until the query worker calls FinishGpe.
return ACPI_INTERRUPT_HANDLED;
}
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++;
DbgPrint("acpi: EC configured data=0x%x command=0x%x gpe=%s\n",
ec->DataPort, ec->CommandPort, ec->HaveGpe ? "configured" : "unsupported");
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);
}
DbgPrint("acpi: EC GPE=%u activation st=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; }