462 lines
19 KiB
C
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, ¶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;
|
|
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; }
|