231 lines
9.0 KiB
C
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; }
|