replace nob with ms' build, move disk creation to build.bat, major comment cleanup, remove old and stupid tests, overhaul interrupt management with per-CPU vector allocation, affinity migration and safe teardown, add proper IOAPIC routing and PCI MSI/MSI-X/INTx support, migrate drivers to the new interrupt API, integrate ACPICA with a proper OS services layer, move ACPI firmware events, power button handling and S5 shutdown into acpi.sys, add parallel driver startup and atomic publication, replace PnP arrays with dynamic lists, redesign service startup again to be asynchronous, add delayed startup mode for services, allow omitting keys in services.ini, fix acpi ec deferred work handling, fix kernel threads having garbage exit code.

This commit is contained in:
maksw committed 2026-10-28 15:18:43 +01:00
1 parent d36f19509c
commit 36eef8f58d
696 files changed
+147281 -7530

No files matched your search

+473
View File
@@ -0,0 +1,473 @@
// 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;
}
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, &params, &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; }