From 64c26f42885258e59e5a51865b6670e19e1e48b4 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sun, 15 Mar 2026 00:55:19 +0100 Subject: [PATCH] feat: expanded ACPI support, initial support for S3 sleep --- kernel/src/ACPI/AML/AmlInterpreter.cpp | 1613 +++++++++++++++++++++ kernel/src/ACPI/AML/AmlInterpreter.hpp | 200 +++ kernel/src/ACPI/AML/AmlNamespace.cpp | 204 +++ kernel/src/ACPI/AML/AmlNamespace.hpp | 199 +++ kernel/src/ACPI/AML/AmlParser.cpp | 137 +- kernel/src/ACPI/AML/AmlParser.hpp | 30 +- kernel/src/ACPI/AML/AmlResource.cpp | 222 +++ kernel/src/ACPI/AML/AmlResource.hpp | 158 ++ kernel/src/ACPI/AcpiDevices.cpp | 394 +++++ kernel/src/ACPI/AcpiDevices.hpp | 112 ++ kernel/src/ACPI/AcpiShutdown.cpp | 21 +- kernel/src/ACPI/AcpiSleep.cpp | 403 +++++ kernel/src/ACPI/AcpiSleep.hpp | 90 ++ kernel/src/ACPI/FADT.cpp | 20 + kernel/src/ACPI/FADT.hpp | 12 + kernel/src/ACPI/S3Trampoline.asm | 115 ++ kernel/src/ACPI/S3Wake.asm | 184 +++ kernel/src/Api/Power.hpp | 10 +- kernel/src/Api/Syscall.cpp | 4 +- kernel/src/Api/Syscall.hpp | 3 + kernel/src/Drivers/Graphics/IntelGPU.cpp | 54 + kernel/src/Drivers/Graphics/IntelGPU.hpp | 3 + kernel/src/Drivers/PS2/PS2Controller.cpp | 25 + kernel/src/Drivers/PS2/PS2Controller.hpp | 5 + kernel/src/Hal/Apic/Apic.cpp | 24 + kernel/src/Hal/Apic/Apic.hpp | 6 + kernel/src/Hal/Apic/IoApic.cpp | 30 + kernel/src/Hal/Apic/IoApic.hpp | 5 + kernel/src/Timekeeping/ApicTimer.cpp | 17 + kernel/src/Timekeeping/ApicTimer.hpp | 5 + programs/include/Api/Syscall.hpp | 3 + programs/include/gui/desktop.hpp | 1 + programs/include/montauk/syscall.h | 4 + programs/src/desktop/apps/apps_common.hpp | 1 + programs/src/desktop/compose.cpp | 3 +- programs/src/desktop/dialogs.cpp | 130 +- programs/src/desktop/input.cpp | 8 +- programs/src/desktop/main.cpp | 6 +- scripts/copy_icons.sh | 1 + 39 files changed, 4403 insertions(+), 59 deletions(-) create mode 100644 kernel/src/ACPI/AML/AmlInterpreter.cpp create mode 100644 kernel/src/ACPI/AML/AmlInterpreter.hpp create mode 100644 kernel/src/ACPI/AML/AmlNamespace.cpp create mode 100644 kernel/src/ACPI/AML/AmlNamespace.hpp create mode 100644 kernel/src/ACPI/AML/AmlResource.cpp create mode 100644 kernel/src/ACPI/AML/AmlResource.hpp create mode 100644 kernel/src/ACPI/AcpiDevices.cpp create mode 100644 kernel/src/ACPI/AcpiDevices.hpp create mode 100644 kernel/src/ACPI/AcpiSleep.cpp create mode 100644 kernel/src/ACPI/AcpiSleep.hpp create mode 100644 kernel/src/ACPI/S3Trampoline.asm create mode 100644 kernel/src/ACPI/S3Wake.asm diff --git a/kernel/src/ACPI/AML/AmlInterpreter.cpp b/kernel/src/ACPI/AML/AmlInterpreter.cpp new file mode 100644 index 0000000..a8cb2c0 --- /dev/null +++ b/kernel/src/ACPI/AML/AmlInterpreter.cpp @@ -0,0 +1,1613 @@ +/* + * AmlInterpreter.cpp + * AML bytecode interpreter — loads DSDT/SSDT into namespace, executes methods + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AmlInterpreter.hpp" +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace AML { + + // ── Global instance ───────────────────────────────────────────── + static Interpreter g_interpreter; + + Interpreter& GetInterpreter() { + return g_interpreter; + } + + // ── Helper: is a byte a lead name character? ──────────────────── + static bool IsLeadNameChar(uint8_t c) { + return (c >= 'A' && c <= 'Z') || c == '_'; + } + + static bool IsNameChar(uint8_t c) { + return IsLeadNameChar(c) || (c >= '0' && c <= '9'); + } + + // ── Constructor ───────────────────────────────────────────────── + Interpreter::Interpreter() + : m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false) {} + + // ── PkgLength decoding ────────────────────────────────────────── + uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) { + uint8_t lead = aml[*pos]; + uint32_t byteCount = (lead >> 6) & 0x03; + + if (byteCount == 0) { + (*pos)++; + return lead & 0x3F; + } + + uint32_t length = lead & 0x0F; + (*pos)++; + + for (uint32_t i = 0; i < byteCount; i++) { + length |= (uint32_t)aml[*pos] << (4 + 8 * i); + (*pos)++; + } + + return length; + } + + // ── Integer decoding (data objects) ───────────────────────────── + uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* pos) { + uint8_t op = aml[*pos]; + + switch (op) { + case ZeroOp: + (*pos)++; + return 0; + case OneOp: + (*pos)++; + return 1; + case OnesOp: + (*pos)++; + return 0xFFFFFFFFFFFFFFFFULL; + case BytePrefix: { + (*pos)++; + uint8_t val = aml[*pos]; + (*pos)++; + return val; + } + case WordPrefix: { + (*pos)++; + uint16_t val = aml[*pos] | ((uint16_t)aml[*pos + 1] << 8); + *pos += 2; + return val; + } + case DWordPrefix: { + (*pos)++; + uint32_t val = aml[*pos] + | ((uint32_t)aml[*pos + 1] << 8) + | ((uint32_t)aml[*pos + 2] << 16) + | ((uint32_t)aml[*pos + 3] << 24); + *pos += 4; + return val; + } + case QWordPrefix: { + (*pos)++; + uint64_t val = 0; + for (int i = 0; i < 8; i++) + val |= (uint64_t)aml[*pos + i] << (8 * i); + *pos += 8; + return val; + } + default: + (*pos)++; + return 0; + } + } + + // ── NameSeg reading ───────────────────────────────────────────── + void Interpreter::ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg) { + for (int i = 0; i < 4; i++) { + outSeg[i] = (char)aml[*pos + i]; + } + outSeg[4] = '\0'; + *pos += 4; + } + + // ── NameString reading ────────────────────────────────────────── + // Reads a NameString (which may include root prefix, parent prefixes, + // dual/multi name prefix) and produces an absolute path. + int Interpreter::ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode, + char* outPath, int maxLen) { + int pathPos = 0; + bool isAbsolute = false; + int parentPrefixCount = 0; + + // Handle root prefix + if (aml[*pos] == '\\') { + isAbsolute = true; + (*pos)++; + } + + // Handle parent prefixes (^) + while (aml[*pos] == '^') { + parentPrefixCount++; + (*pos)++; + } + + // Determine segment count + int segCount = 0; + char segments[MaxPathDepth][5]; + + if (aml[*pos] == 0x00) { + // NullName — zero segments + (*pos)++; + segCount = 0; + } else if (aml[*pos] == DualNamePrefix) { + (*pos)++; + segCount = 2; + ReadNameSeg(aml, pos, segments[0]); + ReadNameSeg(aml, pos, segments[1]); + } else if (aml[*pos] == MultiNamePrefix) { + (*pos)++; + segCount = aml[*pos]; + (*pos)++; + for (int i = 0; i < segCount && i < MaxPathDepth; i++) { + ReadNameSeg(aml, pos, segments[i]); + } + } else if (IsLeadNameChar(aml[*pos])) { + segCount = 1; + ReadNameSeg(aml, pos, segments[0]); + } + + // Build the path + if (isAbsolute) { + outPath[pathPos++] = '\\'; + } else { + // Build path from scope, walking up for parent prefixes + int32_t baseScope = scopeNode; + for (int i = 0; i < parentPrefixCount && baseScope > 0; i++) { + auto* node = m_ns.GetNode(baseScope); + if (node && node->ParentIndex >= 0) + baseScope = node->ParentIndex; + } + char scopePath[256]; + m_ns.GetNodePath(baseScope, scopePath, 256); + int sp = 0; + while (scopePath[sp] && pathPos < maxLen - 1) { + outPath[pathPos++] = scopePath[sp++]; + } + if (pathPos > 1 && segCount > 0) + outPath[pathPos++] = '.'; + } + + // Append segments + for (int i = 0; i < segCount; i++) { + if (i > 0 && pathPos < maxLen - 1) + outPath[pathPos++] = '.'; + for (int j = 0; j < 4 && pathPos < maxLen - 1; j++) + outPath[pathPos++] = segments[i][j]; + } + + outPath[pathPos] = '\0'; + return pathPos; + } + + // ── LoadTable ─────────────────────────────────────────────────── + bool Interpreter::LoadTable(void* tableData) { + auto* header = (ACPI::CommonSDTHeader*)tableData; + + if (!ACPI::TestChecksum(header)) { + KernelLogStream(ERROR, "AML") << "Table checksum failed"; + return false; + } + + char sig[5] = {}; + memcpy(sig, header->Signature, 4); + + m_dsdt = (const uint8_t*)tableData; + m_dsdtLength = header->Length; + + uint32_t dataStart = sizeof(ACPI::CommonSDTHeader); + KernelLogStream(OK, "AML") << "Loading " << sig << " table (" + << base::dec << (uint64_t)(m_dsdtLength - dataStart) << " bytes of AML)"; + + // Create standard predefined scopes + m_ns.CreateNode("\\_GPE"); + m_ns.CreateNode("\\_PR_"); + m_ns.CreateNode("\\_SB_"); + m_ns.CreateNode("\\_SI_"); + m_ns.CreateNode("\\_TZ_"); + + bool result = ParseBlock(m_dsdt, dataStart, m_dsdtLength, m_ns.RootIndex()); + + m_initialized = true; + + KernelLogStream(OK, "AML") << "Namespace loaded: " + << base::dec << (uint64_t)m_ns.NodeCount() << " nodes"; + + return result; + } + + // ── ParseBlock ────────────────────────────────────────────────── + // Parse a block of AML opcodes, creating namespace objects. + bool Interpreter::ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset, + int32_t scopeNode) { + uint32_t pos = offset; + + while (pos < endOffset) { + uint8_t op = aml[pos]; + + // Extended opcode + if (op == ExtOpPrefix) { + if (pos + 1 >= endOffset) break; + if (!ParseExtendedOp(aml, &pos, endOffset, scopeNode)) + return false; + continue; + } + + // Named object opcodes + if (op == NameOp || op == ScopeOp || op == MethodOp || + op == BufferOp || op == PackageOp) { + if (!ParseNamedObject(aml, &pos, endOffset, scopeNode)) + return false; + continue; + } + + // Skip opcodes we don't handle during loading + // We need to advance past them correctly + if (op == BytePrefix) { pos += 2; continue; } + if (op == WordPrefix) { pos += 3; continue; } + if (op == DWordPrefix) { pos += 5; continue; } + if (op == QWordPrefix) { pos += 9; continue; } + if (op == StringPrefix) { + pos++; + while (pos < endOffset && aml[pos] != 0) pos++; + pos++; // skip null terminator + continue; + } + if (op == ZeroOp || op == OneOp || op == OnesOp || op == NoopOp) { + pos++; + continue; + } + + // If/Else/While — skip the block (we only parse structure during load) + if (op == IfOp || op == ElseOp || op == WhileOp) { + pos++; + uint32_t pkgStart = pos; + uint32_t pkgLen = DecodePkgLength(aml, &pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + // Parse inside for nested definitions + ParseBlock(aml, pos, blockEnd, scopeNode); + pos = blockEnd; + continue; + } + + // Store, arithmetic, logical, etc. — skip during load + if (op == StoreOp || op == ReturnOp || op == BreakOp || + op == AddOp || op == SubtractOp || op == MultiplyOp || + op == AndOp || op == OrOp || op == XorOp || op == NotOp || + op == ShiftLeftOp || op == ShiftRightOp || + op == IncrementOp || op == DecrementOp || + op == DivideOp || op == IndexOp || op == SizeOfOp || + op == DerefOfOp || op == ConcatOp || + (op >= LAndOp && op <= LLessOp) || + op == ToIntegerOp || op == ToBufferOp) { + // Can't easily skip these without parsing — just advance one byte + // and hope the next byte resynchronizes. This is imprecise but + // works for top-level definitions which are what we need. + pos++; + continue; + } + + // Name characters (part of a name reference we don't need during load) + if (IsLeadNameChar(op) || op == '\\' || op == '^' || + op == DualNamePrefix || op == MultiNamePrefix) { + // Skip the name + if (op == '\\' || op == '^') { pos++; continue; } + if (op == DualNamePrefix) { pos += 9; continue; } // prefix + 2*4 + if (op == MultiNamePrefix) { + uint8_t count = aml[pos + 1]; + pos += 2 + count * 4; + continue; + } + // Single NameSeg + pos += 4; + continue; + } + + // Locals and args + if ((op >= 0x60 && op <= 0x67) || (op >= 0x68 && op <= 0x6E)) { + pos++; + continue; + } + + // Unknown — advance + pos++; + } + + return true; + } + + // ── ParseNamedObject ──────────────────────────────────────────── + bool Interpreter::ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, + int32_t scopeNode) { + uint8_t op = aml[*pos]; + (*pos)++; + + if (op == ScopeOp) { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node < 0) { + *pos = blockEnd; + return true; + } + + ParseBlock(aml, *pos, blockEnd, node); + *pos = blockEnd; + return true; + } + + if (op == NameOp) { + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node < 0) return true; + + auto* nsNode = m_ns.GetNode(node); + if (!nsNode) return true; + + // Parse the data object + uint8_t dataOp = aml[*pos]; + + if (dataOp == ZeroOp || dataOp == OneOp || dataOp == OnesOp || + dataOp == BytePrefix || dataOp == WordPrefix || + dataOp == DWordPrefix || dataOp == QWordPrefix) { + nsNode->Obj.Type = ObjectType::Integer; + nsNode->Obj.Integer = DecodeInteger(aml, pos); + } else if (dataOp == StringPrefix) { + nsNode->Obj.Type = ObjectType::String; + (*pos)++; // skip prefix + int i = 0; + while (*pos < endOffset && aml[*pos] != 0 && i < MaxStringLen - 1) { + nsNode->Obj.String.Data[i++] = (char)aml[*pos]; + (*pos)++; + } + nsNode->Obj.String.Data[i] = '\0'; + nsNode->Obj.String.Length = (uint16_t)i; + if (*pos < endOffset) (*pos)++; // skip null + } else if (dataOp == BufferOp) { + nsNode->Obj.Type = ObjectType::Buffer; + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t bufEnd = pkgStart + pkgLen; + if (bufEnd > endOffset) bufEnd = endOffset; + + // Buffer size is an integer term + uint64_t bufSize = DecodeInteger(aml, pos); + if (bufSize > MaxBufferLen) bufSize = MaxBufferLen; + nsNode->Obj.Buffer.Length = (uint32_t)bufSize; + + // Copy initializer data + uint32_t initLen = bufEnd - *pos; + if (initLen > bufSize) initLen = (uint32_t)bufSize; + memcpy(nsNode->Obj.Buffer.Data, &aml[*pos], initLen); + + *pos = bufEnd; + } else if (dataOp == PackageOp || dataOp == VarPackageOp) { + nsNode->Obj.Type = ObjectType::Package; + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t pkgEnd = pkgStart + pkgLen; + if (pkgEnd > endOffset) pkgEnd = endOffset; + + // Store the raw package data as a buffer for later evaluation + uint32_t dataLen = pkgEnd - *pos; + if (dataLen > MaxBufferLen) dataLen = MaxBufferLen; + nsNode->Obj.Buffer.Length = dataLen; + memcpy(nsNode->Obj.Buffer.Data, &aml[*pos], dataLen); + + *pos = pkgEnd; + } else { + // Unknown data object — might be a method call or complex expression. + // Just set it as integer 0 for now. + nsNode->Obj.Type = ObjectType::Integer; + nsNode->Obj.Integer = 0; + // Try to skip past a reasonable amount + if (IsLeadNameChar(dataOp)) { + *pos += 4; // NameSeg reference + } + } + + return true; + } + + if (op == MethodOp) { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t methodEnd = pkgStart + pkgLen; + if (methodEnd > endOffset) methodEnd = endOffset; + + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node < 0) { + *pos = methodEnd; + return true; + } + + auto* nsNode = m_ns.GetNode(node); + if (!nsNode) { *pos = methodEnd; return true; } + + // Method flags byte + uint8_t flags = aml[*pos]; + (*pos)++; + + nsNode->Obj.Type = ObjectType::Method; + nsNode->Obj.Method.ArgCount = flags & 0x07; + nsNode->Obj.Method.Serialized = (flags >> 3) & 1; + nsNode->Obj.Method.AmlOffset = *pos; + nsNode->Obj.Method.AmlLength = methodEnd - *pos; + + // Don't parse method body during load — it's executed on demand. + // But we do scan it for nested definitions (devices, scopes). + ParseBlock(aml, *pos, methodEnd, node); + + *pos = methodEnd; + return true; + } + + // Buffer/Package at top level (not as part of a Name) + if (op == BufferOp || op == PackageOp) { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + *pos = blockEnd; + return true; + } + + return true; + } + + // ── ParseExtendedOp ───────────────────────────────────────────── + bool Interpreter::ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, + int32_t scopeNode) { + (*pos)++; // skip ExtOpPrefix + if (*pos >= endOffset) return true; + + uint8_t extOp = aml[*pos]; + (*pos)++; + + switch (extOp) { + case DeviceOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) nsNode->Obj.Type = ObjectType::Device; + ParseBlock(aml, *pos, blockEnd, node); + } + + *pos = blockEnd; + return true; + } + + case OpRegionOp: { + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) { + nsNode->Obj.Type = ObjectType::OperationRegion; + nsNode->Obj.Region.Space = (RegionSpace)aml[*pos]; + (*pos)++; + nsNode->Obj.Region.Offset = DecodeInteger(aml, pos); + nsNode->Obj.Region.Length = DecodeInteger(aml, pos); + } + } + return true; + } + + case FieldOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t fieldEnd = pkgStart + pkgLen; + if (fieldEnd > endOffset) fieldEnd = endOffset; + + // Region name + char regionPath[256]; + ReadNameString(aml, pos, scopeNode, regionPath, 256); + int32_t regionNode = m_ns.FindNode(regionPath); + if (regionNode < 0) { + regionNode = m_ns.ResolveName(regionPath, scopeNode); + } + + // Field flags + uint8_t fieldFlags = aml[*pos]; + (*pos)++; + uint8_t accessType = fieldFlags & 0x0F; + + // Parse field elements + uint32_t bitOffset = 0; + while (*pos < fieldEnd) { + uint8_t fb = aml[*pos]; + + if (fb == 0x00) { + // ReservedField — skip bits + (*pos)++; + uint32_t bits = DecodePkgLength(aml, pos); + bitOffset += bits; + } else if (fb == 0x01) { + // AccessField + (*pos)++; + (*pos)++; // access type + (*pos)++; // access attrib + } else if (fb == 0x03) { + // ExtendedAccessField + (*pos)++; + (*pos)++; // access type + (*pos)++; // access attrib + (*pos)++; // access length + } else if (IsLeadNameChar(fb)) { + // Named field + char fieldSeg[5]; + ReadNameSeg(aml, pos, fieldSeg); + uint32_t bitLen = DecodePkgLength(aml, pos); + + // Build full path for the field + char scopePath[256]; + m_ns.GetNodePath(scopeNode, scopePath, 256); + char fieldPath[256]; + int fp = 0; + int sp = 0; + while (scopePath[sp] && fp < 250) + fieldPath[fp++] = scopePath[sp++]; + fieldPath[fp++] = '.'; + for (int j = 0; j < 4 && fp < 255; j++) + fieldPath[fp++] = fieldSeg[j]; + fieldPath[fp] = '\0'; + + int32_t fieldNode = m_ns.CreateNode(fieldPath); + if (fieldNode >= 0) { + auto* fn = m_ns.GetNode(fieldNode); + if (fn) { + fn->Obj.Type = ObjectType::Field; + fn->Obj.Field.RegionNodeIndex = (uint32_t)regionNode; + fn->Obj.Field.BitOffset = bitOffset; + fn->Obj.Field.BitLength = bitLen; + fn->Obj.Field.AccessType = accessType; + } + } + + bitOffset += bitLen; + } else { + // Unknown field element, skip + (*pos)++; + } + } + + *pos = fieldEnd; + return true; + } + + case ProcessorOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) { + nsNode->Obj.Type = ObjectType::Processor; + nsNode->Obj.Processor.ProcId = aml[*pos]; (*pos)++; + nsNode->Obj.Processor.PblkAddr = aml[*pos] + | ((uint32_t)aml[*pos + 1] << 8) + | ((uint32_t)aml[*pos + 2] << 16) + | ((uint32_t)aml[*pos + 3] << 24); + *pos += 4; + nsNode->Obj.Processor.PblkLen = aml[*pos]; (*pos)++; + } + ParseBlock(aml, *pos, blockEnd, node); + } + + *pos = blockEnd; + return true; + } + + case ThermalZoneOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) nsNode->Obj.Type = ObjectType::ThermalZone; + ParseBlock(aml, *pos, blockEnd, node); + } + + *pos = blockEnd; + return true; + } + + case PowerResOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + + int32_t node = m_ns.CreateNode(path); + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) nsNode->Obj.Type = ObjectType::PowerResource; + // Skip SystemLevel and ResourceOrder + if (*pos + 3 <= blockEnd) *pos += 3; + ParseBlock(aml, *pos, blockEnd, node); + } + + *pos = blockEnd; + return true; + } + + case MutexOp: { + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + (*pos)++; // SyncFlags + + int32_t node = m_ns.CreateNode(path); + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) nsNode->Obj.Type = ObjectType::Mutex; + } + return true; + } + + case IndexFieldOp: { + // Similar to FieldOp but with index/data register pair + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + *pos = blockEnd; // Skip for now + return true; + } + + case BankFieldOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t blockEnd = pkgStart + pkgLen; + if (blockEnd > endOffset) blockEnd = endOffset; + *pos = blockEnd; + return true; + } + + // Stall/Sleep at top level (rare) + case StallOp: + case SleepOp: + DecodeInteger(aml, pos); + return true; + + case AcquireOp: { + // NameString + Timeout(word) + char dummy[256]; + ReadNameString(aml, pos, scopeNode, dummy, 256); + *pos += 2; // timeout + return true; + } + + case ReleaseOp: { + char dummy[256]; + ReadNameString(aml, pos, scopeNode, dummy, 256); + return true; + } + + default: + // Unknown extended opcode — skip one byte + return true; + } + } + + // ── EvaluateObject ────────────────────────────────────────────── + bool Interpreter::EvaluateObject(const char* path, Object& result) { + int32_t node = m_ns.FindNode(path); + if (node < 0) return false; + + auto* nsNode = m_ns.GetNode(node); + if (!nsNode) return false; + + if (nsNode->Obj.Type == ObjectType::Method) { + return EvaluateMethod(path, nullptr, 0, result); + } + + // For non-method objects, just return the stored value + result = nsNode->Obj; + + // For fields, read the actual hardware value + if (nsNode->Obj.Type == ObjectType::Field) { + uint64_t val = 0; + if (ReadField(node, val)) { + result.Type = ObjectType::Integer; + result.Integer = val; + return true; + } + return false; + } + + return true; + } + + // ── EvaluateMethod ────────────────────────────────────────────── + bool Interpreter::EvaluateMethod(const char* path, const Object* args, int argCount, + Object& result) { + int32_t node = m_ns.FindNode(path); + if (node < 0) return false; + + auto* nsNode = m_ns.GetNode(node); + if (!nsNode || nsNode->Obj.Type != ObjectType::Method) return false; + + ExecContext ctx{}; + ctx.Aml = m_dsdt; + ctx.AmlBase = nsNode->Obj.Method.AmlOffset; + ctx.AmlLength = nsNode->Obj.Method.AmlLength; + ctx.ScopeNode = node; + ctx.Returned = false; + ctx.Broken = false; + ctx.Depth = 0; + + // Set up arguments + for (int i = 0; i < argCount && i < MaxMethodArgs; i++) { + ctx.Args[i] = args[i]; + } + + // Initialize locals to zero + for (int i = 0; i < MaxMethodLocals; i++) { + ctx.Locals[i].Type = ObjectType::Integer; + ctx.Locals[i].Integer = 0; + } + + bool ok = ExecuteBlock(ctx, ctx.AmlBase, ctx.AmlBase + ctx.AmlLength); + + result = ctx.ReturnValue; + if (result.Type == ObjectType::None) { + result.Type = ObjectType::Integer; + result.Integer = 0; + } + + return ok; + } + + // ── ExecuteBlock ──────────────────────────────────────────────── + bool Interpreter::ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset) { + uint32_t pos = offset; + + while (pos < endOffset && !ctx.Returned && !ctx.Broken) { + if (!ExecuteOpcode(ctx, &pos, endOffset)) + return false; + } + + return true; + } + + // ── ExecuteOpcode ─────────────────────────────────────────────── + bool Interpreter::ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset) { + if (*pos >= endOffset) return true; + + uint8_t op = ctx.Aml[*pos]; + + // Return + if (op == ReturnOp) { + (*pos)++; + Object val{}; + EvalTerm(ctx, pos, endOffset, val); + ctx.ReturnValue = val; + ctx.Returned = true; + return true; + } + + // Break + if (op == BreakOp) { + (*pos)++; + ctx.Broken = true; + return true; + } + + // Noop + if (op == NoopOp) { + (*pos)++; + return true; + } + + // If + if (op == IfOp) { + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t ifEnd = pkgStart + pkgLen; + if (ifEnd > endOffset) ifEnd = endOffset; + + Object predicate{}; + EvalTerm(ctx, pos, ifEnd, predicate); + + bool condition = (predicate.Type == ObjectType::Integer && predicate.Integer != 0); + + if (condition) { + ExecuteBlock(ctx, *pos, ifEnd); + } + + *pos = ifEnd; + + // Check for Else + if (*pos < endOffset && ctx.Aml[*pos] == ElseOp) { + (*pos)++; + uint32_t elsePkgStart = *pos; + uint32_t elsePkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t elseEnd = elsePkgStart + elsePkgLen; + if (elseEnd > endOffset) elseEnd = endOffset; + + if (!condition) { + ExecuteBlock(ctx, *pos, elseEnd); + } + + *pos = elseEnd; + } + + return true; + } + + // While + if (op == WhileOp) { + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t whileEnd = pkgStart + pkgLen; + if (whileEnd > endOffset) whileEnd = endOffset; + + uint32_t bodyStart = *pos; + + for (int iter = 0; iter < MaxLoopIterations; iter++) { + uint32_t condPos = bodyStart; + Object predicate{}; + EvalTerm(ctx, &condPos, whileEnd, predicate); + + if (predicate.Type != ObjectType::Integer || predicate.Integer == 0) + break; + + ctx.Broken = false; + ExecuteBlock(ctx, condPos, whileEnd); + + if (ctx.Returned) break; + if (ctx.Broken) { ctx.Broken = false; break; } + } + + *pos = whileEnd; + return true; + } + + // Store + if (op == StoreOp) { + (*pos)++; + Object value{}; + EvalTerm(ctx, pos, endOffset, value); + + int32_t targetNode = -1; + bool isLocal = false, isArg = false; + int localIdx = 0, argIdx = 0; + EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx); + StoreToTarget(ctx, value, targetNode, isLocal, localIdx, isArg, argIdx); + return true; + } + + // Increment/Decrement + if (op == IncrementOp || op == DecrementOp) { + (*pos)++; + int32_t targetNode = -1; + bool isLocal = false, isArg = false; + int localIdx = 0, argIdx = 0; + EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx); + + Object current{}; + if (isLocal) current = ctx.Locals[localIdx]; + else if (isArg) current = ctx.Args[argIdx]; + else if (targetNode >= 0) { + auto* n = m_ns.GetNode(targetNode); + if (n) current = n->Obj; + } + + if (current.Type == ObjectType::Integer) { + current.Integer += (op == IncrementOp) ? 1 : -1; + } + StoreToTarget(ctx, current, targetNode, isLocal, localIdx, isArg, argIdx); + return true; + } + + // Extended opcodes during execution + if (op == ExtOpPrefix) { + (*pos)++; + if (*pos >= endOffset) return true; + uint8_t extOp = ctx.Aml[*pos]; + (*pos)++; + + // Acquire mutex — just skip + if (extOp == AcquireOp) { + char dummy[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, dummy, 256); + *pos += 2; // timeout + return true; + } + // Release mutex — just skip + if (extOp == ReleaseOp) { + char dummy[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, dummy, 256); + return true; + } + // Sleep + if (extOp == SleepOp) { + Object msObj{}; + EvalTerm(ctx, pos, endOffset, msObj); + // We don't actually sleep in the kernel interpreter + return true; + } + // Stall + if (extOp == StallOp) { + Object usObj{}; + EvalTerm(ctx, pos, endOffset, usObj); + // Brief stall via IO port wait + if (usObj.Integer > 0 && usObj.Integer <= 100) { + for (uint64_t i = 0; i < usObj.Integer; i++) + Io::IoPortWait(); + } + return true; + } + // OpRegion/Field/Device during method execution — handle as in ParseExtendedOp + if (extOp == OpRegionOp || extOp == FieldOp || extOp == DeviceOp || + extOp == ProcessorOp || extOp == ThermalZoneOp || extOp == PowerResOp || + extOp == MutexOp || extOp == IndexFieldOp || extOp == BankFieldOp) { + *pos -= 2; // back up to re-parse + return ParseExtendedOp(ctx.Aml, pos, endOffset, ctx.ScopeNode); + } + + return true; + } + + // Name/Scope/Method definitions can appear inside methods + if (op == NameOp || op == ScopeOp || op == MethodOp) { + return ParseNamedObject(ctx.Aml, pos, endOffset, ctx.ScopeNode); + } + + // Anything else — try to evaluate as an expression and discard the result + Object discard{}; + return EvalTerm(ctx, pos, endOffset, discard); + } + + // ── EvalTerm ──────────────────────────────────────────────────── + // Evaluate an AML term that produces a value. + bool Interpreter::EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + if (*pos >= endOffset) return false; + + uint8_t op = ctx.Aml[*pos]; + + // Integer constants + if (op == ZeroOp || op == OneOp || op == OnesOp || + op == BytePrefix || op == WordPrefix || + op == DWordPrefix || op == QWordPrefix) { + result.Type = ObjectType::Integer; + result.Integer = DecodeInteger(ctx.Aml, pos); + return true; + } + + // String + if (op == StringPrefix) { + (*pos)++; + result.Type = ObjectType::String; + int i = 0; + while (*pos < endOffset && ctx.Aml[*pos] != 0 && i < MaxStringLen - 1) { + result.String.Data[i++] = (char)ctx.Aml[*pos]; + (*pos)++; + } + result.String.Data[i] = '\0'; + result.String.Length = (uint16_t)i; + if (*pos < endOffset) (*pos)++; // null terminator + return true; + } + + // Buffer + if (op == BufferOp) { + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t bufEnd = pkgStart + pkgLen; + if (bufEnd > endOffset) bufEnd = endOffset; + + Object sizeObj{}; + EvalTerm(ctx, pos, bufEnd, sizeObj); + uint32_t sz = (uint32_t)sizeObj.Integer; + if (sz > MaxBufferLen) sz = MaxBufferLen; + + result.Type = ObjectType::Buffer; + result.Buffer.Length = sz; + memset(result.Buffer.Data, 0, sz); + + uint32_t initLen = bufEnd - *pos; + if (initLen > sz) initLen = sz; + memcpy(result.Buffer.Data, &ctx.Aml[*pos], initLen); + + *pos = bufEnd; + return true; + } + + // Package + if (op == PackageOp) { + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t pkgEnd = pkgStart + pkgLen; + if (pkgEnd > endOffset) pkgEnd = endOffset; + + // Store raw package as buffer for now + result.Type = ObjectType::Package; + uint32_t dataLen = pkgEnd - *pos; + if (dataLen > MaxBufferLen) dataLen = MaxBufferLen; + result.Buffer.Length = dataLen; + memcpy(result.Buffer.Data, &ctx.Aml[*pos], dataLen); + + *pos = pkgEnd; + return true; + } + + // Locals (0x60-0x67) + if (op >= 0x60 && op <= 0x67) { + int idx = op - 0x60; + result = ctx.Locals[idx]; + (*pos)++; + return true; + } + + // Args (0x68-0x6E) + if (op >= 0x68 && op <= 0x6E) { + int idx = op - 0x68; + result = ctx.Args[idx]; + (*pos)++; + return true; + } + + // Logical operators (single-byte opcodes, not behind ExtOpPrefix) + if (op == LAndOp) { + (*pos)++; + Object a{}, b{}; + EvalTerm(ctx, pos, endOffset, a); + EvalTerm(ctx, pos, endOffset, b); + result.Type = ObjectType::Integer; + result.Integer = (a.Integer && b.Integer) ? 1 : 0; + return true; + } + if (op == LOrOp) { + (*pos)++; + Object a{}, b{}; + EvalTerm(ctx, pos, endOffset, a); + EvalTerm(ctx, pos, endOffset, b); + result.Type = ObjectType::Integer; + result.Integer = (a.Integer || b.Integer) ? 1 : 0; + return true; + } + if (op == LNotOp) { + (*pos)++; + Object a{}; + EvalTerm(ctx, pos, endOffset, a); + result.Type = ObjectType::Integer; + result.Integer = (a.Integer == 0) ? 1 : 0; + return true; + } + if (op == LEqualOp) { + (*pos)++; + Object a{}, b{}; + EvalTerm(ctx, pos, endOffset, a); + EvalTerm(ctx, pos, endOffset, b); + result.Type = ObjectType::Integer; + result.Integer = (a.Integer == b.Integer) ? 1 : 0; + return true; + } + if (op == LGreaterOp) { + (*pos)++; + Object a{}, b{}; + EvalTerm(ctx, pos, endOffset, a); + EvalTerm(ctx, pos, endOffset, b); + result.Type = ObjectType::Integer; + result.Integer = (a.Integer > b.Integer) ? 1 : 0; + return true; + } + if (op == LLessOp) { + (*pos)++; + Object a{}, b{}; + EvalTerm(ctx, pos, endOffset, a); + EvalTerm(ctx, pos, endOffset, b); + result.Type = ObjectType::Integer; + result.Integer = (a.Integer < b.Integer) ? 1 : 0; + return true; + } + + // Arithmetic / bitwise + if (op == AddOp || op == SubtractOp || op == MultiplyOp || + op == AndOp || op == OrOp || op == XorOp || + op == ShiftLeftOp || op == ShiftRightOp || + op == NandOp || op == NorOp) { + (*pos)++; + Object a{}, b{}; + EvalTerm(ctx, pos, endOffset, a); + EvalTerm(ctx, pos, endOffset, b); + + result.Type = ObjectType::Integer; + switch (op) { + case AddOp: result.Integer = a.Integer + b.Integer; break; + case SubtractOp: result.Integer = a.Integer - b.Integer; break; + case MultiplyOp: result.Integer = a.Integer * b.Integer; break; + case AndOp: result.Integer = a.Integer & b.Integer; break; + case OrOp: result.Integer = a.Integer | b.Integer; break; + case XorOp: result.Integer = a.Integer ^ b.Integer; break; + case ShiftLeftOp: result.Integer = a.Integer << b.Integer; break; + case ShiftRightOp: result.Integer = a.Integer >> b.Integer; break; + case NandOp: result.Integer = ~(a.Integer & b.Integer); break; + case NorOp: result.Integer = ~(a.Integer | b.Integer); break; + default: break; + } + + // Optional target operand — skip if present + if (*pos < endOffset) { + uint8_t next = ctx.Aml[*pos]; + if ((next >= 0x60 && next <= 0x67) || (next >= 0x68 && next <= 0x6E)) { + // Store result to local/arg + int32_t tn = -1; + bool il = false, ia = false; + int li = 0, ai = 0; + EvalTarget(ctx, pos, tn, il, li, ia, ai); + StoreToTarget(ctx, result, tn, il, li, ia, ai); + } else if (IsLeadNameChar(next) || next == '\\' || next == '^') { + int32_t tn = -1; + bool il = false, ia = false; + int li = 0, ai = 0; + EvalTarget(ctx, pos, tn, il, li, ia, ai); + StoreToTarget(ctx, result, tn, il, li, ia, ai); + } + // ZeroOp as "no target" — skip + else if (next == ZeroOp) { + (*pos)++; + } + } + + return true; + } + + // Not + if (op == NotOp) { + (*pos)++; + Object a{}; + EvalTerm(ctx, pos, endOffset, a); + result.Type = ObjectType::Integer; + result.Integer = ~a.Integer; + // Optional target + if (*pos < endOffset && ctx.Aml[*pos] != ZeroOp) { + int32_t tn = -1; + bool il = false, ia = false; + int li = 0, ai = 0; + EvalTarget(ctx, pos, tn, il, li, ia, ai); + StoreToTarget(ctx, result, tn, il, li, ia, ai); + } else if (*pos < endOffset && ctx.Aml[*pos] == ZeroOp) { + (*pos)++; + } + return true; + } + + // DivideOp: Divide(Dividend, Divisor, Remainder, Result) + if (op == DivideOp) { + (*pos)++; + Object dividend{}, divisor{}; + EvalTerm(ctx, pos, endOffset, dividend); + EvalTerm(ctx, pos, endOffset, divisor); + + Object remainder{}; + remainder.Type = ObjectType::Integer; + result.Type = ObjectType::Integer; + + if (divisor.Integer != 0) { + remainder.Integer = dividend.Integer % divisor.Integer; + result.Integer = dividend.Integer / divisor.Integer; + } else { + remainder.Integer = 0; + result.Integer = 0; + } + + // Remainder target + if (*pos < endOffset) { + int32_t tn = -1; + bool il = false, ia = false; + int li = 0, ai = 0; + EvalTarget(ctx, pos, tn, il, li, ia, ai); + StoreToTarget(ctx, remainder, tn, il, li, ia, ai); + } + // Result target + if (*pos < endOffset) { + int32_t tn = -1; + bool il = false, ia = false; + int li = 0, ai = 0; + EvalTarget(ctx, pos, tn, il, li, ia, ai); + StoreToTarget(ctx, result, tn, il, li, ia, ai); + } + return true; + } + + // SizeOf + if (op == SizeOfOp) { + (*pos)++; + Object obj{}; + EvalTerm(ctx, pos, endOffset, obj); + result.Type = ObjectType::Integer; + if (obj.Type == ObjectType::String) + result.Integer = obj.String.Length; + else if (obj.Type == ObjectType::Buffer) + result.Integer = obj.Buffer.Length; + else + result.Integer = 0; + return true; + } + + // Index + if (op == IndexOp) { + (*pos)++; + Object source{}, index{}; + EvalTerm(ctx, pos, endOffset, source); + EvalTerm(ctx, pos, endOffset, index); + + result.Type = ObjectType::Integer; + uint64_t idx = index.Integer; + + if (source.Type == ObjectType::Buffer && idx < source.Buffer.Length) { + result.Integer = source.Buffer.Data[idx]; + } else { + result.Integer = 0; + } + + // Optional target + if (*pos < endOffset && ctx.Aml[*pos] != ZeroOp) { + int32_t tn = -1; + bool il = false, ia = false; + int li = 0, ai = 0; + EvalTarget(ctx, pos, tn, il, li, ia, ai); + StoreToTarget(ctx, result, tn, il, li, ia, ai); + } else if (*pos < endOffset && ctx.Aml[*pos] == ZeroOp) { + (*pos)++; + } + return true; + } + + // ToInteger + if (op == ToIntegerOp) { + (*pos)++; + Object obj{}; + EvalTerm(ctx, pos, endOffset, obj); + result.Type = ObjectType::Integer; + result.Integer = obj.Integer; + return true; + } + + // Extended opcodes as terms + if (op == ExtOpPrefix && *pos + 1 < endOffset) { + uint8_t extOp = ctx.Aml[*pos + 1]; + + // CondRefOf, etc. — not commonly needed, skip + // For now, return 0 + *pos += 2; + result.Type = ObjectType::Integer; + result.Integer = 0; + return true; + } + + // Name reference — look up the name and return its value + if (IsLeadNameChar(op) || op == '\\' || op == '^' || + op == DualNamePrefix || op == MultiNamePrefix) { + char path[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256); + + int32_t node = m_ns.FindNode(path); + if (node < 0) { + node = m_ns.ResolveName(path, ctx.ScopeNode); + } + + if (node >= 0) { + auto* nsNode = m_ns.GetNode(node); + if (nsNode) { + if (nsNode->Obj.Type == ObjectType::Method) { + // Method invocation — evaluate arguments + Object args[MaxMethodArgs]; + int argc = nsNode->Obj.Method.ArgCount; + for (int i = 0; i < argc && *pos < endOffset; i++) { + EvalTerm(ctx, pos, endOffset, args[i]); + } + + // Recursive method call + if (ctx.Depth >= MaxCallDepth) { + result.Type = ObjectType::Integer; + result.Integer = 0; + return true; + } + + ExecContext subCtx{}; + subCtx.Aml = ctx.Aml; + subCtx.AmlBase = nsNode->Obj.Method.AmlOffset; + subCtx.AmlLength = nsNode->Obj.Method.AmlLength; + subCtx.ScopeNode = node; + subCtx.Returned = false; + subCtx.Broken = false; + subCtx.Depth = ctx.Depth + 1; + for (int i = 0; i < argc; i++) + subCtx.Args[i] = args[i]; + for (int i = 0; i < MaxMethodLocals; i++) { + subCtx.Locals[i].Type = ObjectType::Integer; + subCtx.Locals[i].Integer = 0; + } + + ExecuteBlock(subCtx, subCtx.AmlBase, subCtx.AmlBase + subCtx.AmlLength); + result = subCtx.ReturnValue; + if (result.Type == ObjectType::None) { + result.Type = ObjectType::Integer; + result.Integer = 0; + } + } else if (nsNode->Obj.Type == ObjectType::Field) { + uint64_t val = 0; + ReadField(node, val); + result.Type = ObjectType::Integer; + result.Integer = val; + } else { + result = nsNode->Obj; + } + return true; + } + } + + // Name not found — return 0 + result.Type = ObjectType::Integer; + result.Integer = 0; + return true; + } + + // Unknown opcode — skip and return 0 + (*pos)++; + result.Type = ObjectType::Integer; + result.Integer = 0; + return true; + } + + // ── EvalTarget ────────────────────────────────────────────────── + bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos, + int32_t& nodeIndex, bool& isLocal, int& localIdx, + bool& isArg, int& argIdx) { + nodeIndex = -1; + isLocal = false; + isArg = false; + localIdx = 0; + argIdx = 0; + + if (*pos >= ctx.AmlBase + ctx.AmlLength) return false; + + uint8_t op = ctx.Aml[*pos]; + + // ZeroOp = null target (discard) + if (op == ZeroOp) { + (*pos)++; + return true; + } + + // Local + if (op >= 0x60 && op <= 0x67) { + isLocal = true; + localIdx = op - 0x60; + (*pos)++; + return true; + } + + // Arg + if (op >= 0x68 && op <= 0x6E) { + isArg = true; + argIdx = op - 0x68; + (*pos)++; + return true; + } + + // Named target + if (IsLeadNameChar(op) || op == '\\' || op == '^' || + op == DualNamePrefix || op == MultiNamePrefix) { + char path[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256); + nodeIndex = m_ns.FindNode(path); + if (nodeIndex < 0) + nodeIndex = m_ns.ResolveName(path, ctx.ScopeNode); + return true; + } + + // DerefOf or other complex target — skip + (*pos)++; + return true; + } + + // ── StoreToTarget ─────────────────────────────────────────────── + void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value, + int32_t nodeIndex, bool isLocal, int localIdx, + bool isArg, int argIdx) { + if (isLocal && localIdx >= 0 && localIdx < MaxMethodLocals) { + ctx.Locals[localIdx] = value; + } else if (isArg && argIdx >= 0 && argIdx < MaxMethodArgs) { + ctx.Args[argIdx] = value; + } else if (nodeIndex >= 0) { + auto* node = m_ns.GetNode(nodeIndex); + if (node) { + if (node->Obj.Type == ObjectType::Field && value.Type == ObjectType::Integer) { + WriteField(nodeIndex, value.Integer); + } else { + node->Obj = value; + } + } + } + } + + // ── ReadField ─────────────────────────────────────────────────── + bool Interpreter::ReadField(int32_t nodeIndex, uint64_t& value) { + auto* node = m_ns.GetNode(nodeIndex); + if (!node || node->Obj.Type != ObjectType::Field) return false; + + int32_t regionIdx = (int32_t)node->Obj.Field.RegionNodeIndex; + auto* regionNode = m_ns.GetNode(regionIdx); + if (!regionNode || regionNode->Obj.Type != ObjectType::OperationRegion) + return false; + + uint64_t regBase = regionNode->Obj.Region.Offset; + uint32_t bitOff = node->Obj.Field.BitOffset; + uint32_t bitLen = node->Obj.Field.BitLength; + + // Calculate byte-aligned address + uint64_t byteAddr = regBase + (bitOff / 8); + uint32_t bitShift = bitOff % 8; + + return ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value); + } + + // ── WriteField ────────────────────────────────────────────────── + bool Interpreter::WriteField(int32_t nodeIndex, uint64_t value) { + auto* node = m_ns.GetNode(nodeIndex); + if (!node || node->Obj.Type != ObjectType::Field) return false; + + int32_t regionIdx = (int32_t)node->Obj.Field.RegionNodeIndex; + auto* regionNode = m_ns.GetNode(regionIdx); + if (!regionNode || regionNode->Obj.Type != ObjectType::OperationRegion) + return false; + + uint64_t regBase = regionNode->Obj.Region.Offset; + uint32_t bitOff = node->Obj.Field.BitOffset; + uint32_t bitLen = node->Obj.Field.BitLength; + + uint64_t byteAddr = regBase + (bitOff / 8); + uint32_t bitShift = bitOff % 8; + + // For sub-byte fields, do read-modify-write + if (bitShift != 0 || bitLen < 8) { + uint64_t existing = 0; + ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, existing); + uint64_t mask = ((1ULL << bitLen) - 1) << bitShift; + existing = (existing & ~mask) | ((value << bitShift) & mask); + value = existing; + } + + return WriteRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value); + } + + // ── ReadRegion ────────────────────────────────────────────────── + bool Interpreter::ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, + uint64_t& value) { + value = 0; + + // Round up to access width + uint32_t accessBytes = (bitWidth + 7) / 8; + if (accessBytes > 8) accessBytes = 8; + + if (space == RegionSpace::SystemIO) { + uint16_t port = (uint16_t)address; + if (accessBytes <= 1) + value = Io::In8(port); + else if (accessBytes <= 2) + value = Io::In16(port); + else + value = Io::In32(port); + return true; + } + + if (space == RegionSpace::SystemMemory) { + volatile uint8_t* mmio = (volatile uint8_t*)Memory::HHDM(address); + if (accessBytes <= 1) + value = *mmio; + else if (accessBytes <= 2) + value = *(volatile uint16_t*)mmio; + else if (accessBytes <= 4) + value = *(volatile uint32_t*)mmio; + else + value = *(volatile uint64_t*)mmio; + return true; + } + + if (space == RegionSpace::PciConfig) { + // PCI config space access — address encodes bus/dev/func/offset + // Not implemented yet; would need PCI config read support + return false; + } + + return false; + } + + // ── WriteRegion ───────────────────────────────────────────────── + bool Interpreter::WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, + uint64_t value) { + uint32_t accessBytes = (bitWidth + 7) / 8; + if (accessBytes > 8) accessBytes = 8; + + if (space == RegionSpace::SystemIO) { + uint16_t port = (uint16_t)address; + if (accessBytes <= 1) + Io::Out8((uint8_t)value, port); + else if (accessBytes <= 2) + Io::Out16((uint16_t)value, port); + else + Io::Out32((uint32_t)value, port); + return true; + } + + if (space == RegionSpace::SystemMemory) { + volatile uint8_t* mmio = (volatile uint8_t*)Memory::HHDM(address); + if (accessBytes <= 1) + *mmio = (uint8_t)value; + else if (accessBytes <= 2) + *(volatile uint16_t*)mmio = (uint16_t)value; + else if (accessBytes <= 4) + *(volatile uint32_t*)mmio = (uint32_t)value; + else + *(volatile uint64_t*)mmio = value; + return true; + } + + return false; + } + + }; +}; diff --git a/kernel/src/ACPI/AML/AmlInterpreter.hpp b/kernel/src/ACPI/AML/AmlInterpreter.hpp new file mode 100644 index 0000000..a6b9cbc --- /dev/null +++ b/kernel/src/ACPI/AML/AmlInterpreter.hpp @@ -0,0 +1,200 @@ +/* + * AmlInterpreter.hpp + * AML bytecode interpreter — parses DSDT/SSDT into the ACPI namespace + * and evaluates methods, fields, and device status + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include "AmlNamespace.hpp" +#include + +namespace Hal { + namespace AML { + + // ── Extended AML Opcodes ──────────────────────────────────────── + // Single-byte opcodes + static constexpr uint8_t ZeroOp = 0x00; + static constexpr uint8_t OneOp = 0x01; + static constexpr uint8_t AliasOp = 0x06; + static constexpr uint8_t NameOp = 0x08; + static constexpr uint8_t BytePrefix = 0x0A; + static constexpr uint8_t WordPrefix = 0x0B; + static constexpr uint8_t DWordPrefix = 0x0C; + static constexpr uint8_t StringPrefix = 0x0D; + static constexpr uint8_t QWordPrefix = 0x0E; + static constexpr uint8_t ScopeOp = 0x10; + static constexpr uint8_t BufferOp = 0x11; + static constexpr uint8_t PackageOp = 0x12; + static constexpr uint8_t VarPackageOp = 0x13; + static constexpr uint8_t MethodOp = 0x14; + static constexpr uint8_t DualNamePrefix = 0x2E; + static constexpr uint8_t MultiNamePrefix = 0x2F; + static constexpr uint8_t LocalPrefix = 0x60; // Local0..Local7 = 0x60..0x67 + static constexpr uint8_t ArgPrefix = 0x68; // Arg0..Arg6 = 0x68..0x6E + static constexpr uint8_t StoreOp = 0x70; + static constexpr uint8_t AddOp = 0x72; + static constexpr uint8_t SubtractOp = 0x74; + static constexpr uint8_t MultiplyOp = 0x77; + static constexpr uint8_t ShiftLeftOp = 0x79; + static constexpr uint8_t ShiftRightOp = 0x7A; + static constexpr uint8_t AndOp = 0x7B; + static constexpr uint8_t NandOp = 0x7C; + static constexpr uint8_t OrOp = 0x7D; + static constexpr uint8_t NorOp = 0x7E; + static constexpr uint8_t XorOp = 0x7F; + static constexpr uint8_t NotOp = 0x80; + static constexpr uint8_t DerefOfOp = 0x83; + static constexpr uint8_t SizeOfOp = 0x87; + static constexpr uint8_t IndexOp = 0x88; + static constexpr uint8_t CreateDWordFieldOp = 0x8A; + static constexpr uint8_t CreateWordFieldOp = 0x8B; + static constexpr uint8_t CreateByteFieldOp = 0x8C; + static constexpr uint8_t CreateBitFieldOp = 0x8D; + static constexpr uint8_t OnesOp = 0xFF; + static constexpr uint8_t ReturnOp = 0xA4; + static constexpr uint8_t BreakOp = 0xA5; + static constexpr uint8_t IfOp = 0xA0; + static constexpr uint8_t ElseOp = 0xA1; + static constexpr uint8_t WhileOp = 0xA2; + static constexpr uint8_t NoopOp = 0xA3; + static constexpr uint8_t ConcatOp = 0x73; + static constexpr uint8_t ToIntegerOp = 0x99; + static constexpr uint8_t ToBufferOp = 0x96; + static constexpr uint8_t RevisionOp = 0x30; // not a real AML op, used internally + + // ExtOp prefix (0x5B) followed by second byte + static constexpr uint8_t ExtOpPrefix = 0x5B; + static constexpr uint8_t MutexOp = 0x01; // after ExtOpPrefix + static constexpr uint8_t EventOp = 0x02; + static constexpr uint8_t OpRegionOp = 0x80; + static constexpr uint8_t FieldOp = 0x81; + static constexpr uint8_t DeviceOp = 0x82; + static constexpr uint8_t ProcessorOp = 0x83; + static constexpr uint8_t PowerResOp = 0x84; + static constexpr uint8_t ThermalZoneOp = 0x85; + static constexpr uint8_t IndexFieldOp = 0x86; + static constexpr uint8_t BankFieldOp = 0x87; + static constexpr uint8_t AcquireOp = 0x23; + static constexpr uint8_t ReleaseOp = 0x27; + static constexpr uint8_t SleepOp = 0x22; + static constexpr uint8_t StallOp = 0x21; + static constexpr uint8_t LNotOp = 0x92; // single-byte, actually + static constexpr uint8_t LEqualOp = 0x93; // single-byte + static constexpr uint8_t LGreaterOp = 0x94; // single-byte + static constexpr uint8_t LLessOp = 0x95; // single-byte + static constexpr uint8_t LAndOp = 0x90; // single-byte + static constexpr uint8_t LOrOp = 0x91; // single-byte + static constexpr uint8_t IncrementOp = 0x75; + static constexpr uint8_t DecrementOp = 0x76; + static constexpr uint8_t DivideOp = 0x78; + static constexpr uint8_t ModOp = 0x85; + static constexpr uint8_t ConcatResOp = 0x84; + static constexpr uint8_t ToHexStringOp = 0x98; + static constexpr uint8_t ToDecimalStringOp = 0x97; + + // ── Interpreter Configuration ─────────────────────────────────── + static constexpr int MaxCallDepth = 16; + static constexpr int MaxLoopIterations = 1024; + + // ── Interpreter ───────────────────────────────────────────────── + class Interpreter { + public: + Interpreter(); + + // Parse a DSDT or SSDT table into the namespace. + // tableData points to the CommonSDTHeader (HHDM-mapped). + bool LoadTable(void* tableData); + + // Evaluate a named object, returning its value. + // For methods, executes them with no arguments. + bool EvaluateObject(const char* path, Object& result); + + // Evaluate a method with arguments. + bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result); + + // Read a field value. Returns integer. + bool ReadField(int32_t nodeIndex, uint64_t& value); + + // Write a field value. + bool WriteField(int32_t nodeIndex, uint64_t value); + + // Get the namespace for direct queries. + Namespace& GetNamespace() { return m_ns; } + const Namespace& GetNamespace() const { return m_ns; } + + // Check if the interpreter has been initialized + bool IsInitialized() const { return m_initialized; } + + private: + // ── Parsing (table load) ──────────────────────────────────── + bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset, + int32_t scopeNode); + + bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, + int32_t scopeNode); + + bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, + int32_t scopeNode); + + // ── Name resolution ───────────────────────────────────────── + // Read a NameString from AML and produce an absolute path. + // Advances *pos past the name. + int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode, + char* outPath, int maxLen); + + // Read a single 4-char NameSeg from AML. Advances *pos. + void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg); + + // ── Value decoding ────────────────────────────────────────── + uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos); + uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos); + + // ── Method execution ──────────────────────────────────────── + struct ExecContext { + const uint8_t* Aml; + uint32_t AmlBase; // start of the block within the table + uint32_t AmlLength; + int32_t ScopeNode; + Object Locals[MaxMethodLocals]; + Object Args[MaxMethodArgs]; + Object ReturnValue; + bool Returned; + bool Broken; + int Depth; + }; + + bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset); + bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset); + + // Evaluate a term (expression that produces a value) within an execution context. + bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + + // Evaluate a "SuperName" target for Store operations. + // Returns the node index for named targets, or handles locals/args. + // If isLocal/isArg is set, localIdx/argIdx contains the index. + bool EvalTarget(ExecContext& ctx, uint32_t* pos, + int32_t& nodeIndex, bool& isLocal, int& localIdx, + bool& isArg, int& argIdx); + + // Store a value to a target (node, local, or arg). + void StoreToTarget(ExecContext& ctx, const Object& value, + int32_t nodeIndex, bool isLocal, int localIdx, + bool isArg, int argIdx); + + // ── Field I/O ─────────────────────────────────────────────── + bool ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t& value); + bool WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t value); + + // ── State ─────────────────────────────────────────────────── + Namespace m_ns; + const uint8_t* m_dsdt; + uint32_t m_dsdtLength; + bool m_initialized; + }; + + // ── Global interpreter instance ───────────────────────────────── + Interpreter& GetInterpreter(); + + }; +}; diff --git a/kernel/src/ACPI/AML/AmlNamespace.cpp b/kernel/src/ACPI/AML/AmlNamespace.cpp new file mode 100644 index 0000000..4a14470 --- /dev/null +++ b/kernel/src/ACPI/AML/AmlNamespace.cpp @@ -0,0 +1,204 @@ +/* + * AmlNamespace.cpp + * ACPI namespace tree implementation + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AmlNamespace.hpp" +#include + +namespace Hal { + namespace AML { + + Namespace::Namespace() : m_nodeCount(0) { + for (int i = 0; i < MaxNamespaceNodes; i++) + m_nodes[i].Clear(); + + // Create root node "\" + int32_t root = AllocNode(); + m_nodes[root].Name[0] = '\\'; + m_nodes[root].Name[1] = '\0'; + m_nodes[root].ParentIndex = -1; + } + + int32_t Namespace::AllocNode() { + if (m_nodeCount >= MaxNamespaceNodes) + return -1; + int32_t idx = m_nodeCount++; + m_nodes[idx].Clear(); + return idx; + } + + bool Namespace::SegmentEqual(const char* a, const char* b) { + for (int i = 0; i < MaxNameSegLen; i++) { + char ca = a[i] ? a[i] : '_'; + char cb = b[i] ? b[i] : '_'; + if (ca != cb) return false; + } + return true; + } + + void Namespace::PadSegment(const char* src, char* dst) { + int i = 0; + while (i < MaxNameSegLen && src[i] != '\0') { + dst[i] = src[i]; + i++; + } + while (i < MaxNameSegLen) { + dst[i] = '_'; + i++; + } + dst[MaxNameSegLen] = '\0'; + } + + int Namespace::ParsePath(const char* path, char segments[][MaxNameSegLen + 1], int maxSegments) { + if (!path || !*path) return 0; + + const char* p = path; + + // Skip leading backslash (root prefix) + if (*p == '\\') p++; + + int count = 0; + while (*p && count < maxSegments) { + // Skip dots (parent prefix / dual/multi name prefix separator) + if (*p == '.') { p++; continue; } + // Skip caret (parent prefix) — we don't handle relative paths here + if (*p == '^') { p++; continue; } + + // Read up to 4 characters for a name segment + int i = 0; + while (i < MaxNameSegLen && *p && *p != '.' && *p != '\\') { + segments[count][i] = *p; + i++; + p++; + } + // Pad with underscores + while (i < MaxNameSegLen) { + segments[count][i] = '_'; + i++; + } + segments[count][MaxNameSegLen] = '\0'; + count++; + } + + return count; + } + + int32_t Namespace::FindChildByName(int32_t parentIndex, const char* seg) const { + auto* parent = GetNode(parentIndex); + if (!parent) return -1; + + for (int32_t i = 0; i < parent->ChildCount; i++) { + int32_t ci = parent->ChildIndices[i]; + if (ci < 0 || ci >= m_nodeCount) continue; + if (SegmentEqual(m_nodes[ci].Name, seg)) + return ci; + } + return -1; + } + + int32_t Namespace::CreateNode(const char* absolutePath) { + char segments[MaxPathDepth][MaxNameSegLen + 1]; + int segCount = ParsePath(absolutePath, segments, MaxPathDepth); + + int32_t current = 0; // root + for (int i = 0; i < segCount; i++) { + int32_t child = FindChildByName(current, segments[i]); + if (child < 0) { + // Create the node + child = AllocNode(); + if (child < 0) return -1; + + memcpy(m_nodes[child].Name, segments[i], MaxNameSegLen + 1); + m_nodes[child].ParentIndex = current; + + // Add to parent's children + auto* parent = &m_nodes[current]; + if (parent->ChildCount < MaxChildren) { + parent->ChildIndices[parent->ChildCount++] = child; + } else { + return -1; // too many children + } + } + current = child; + } + return current; + } + + int32_t Namespace::FindNode(const char* absolutePath) const { + char segments[MaxPathDepth][MaxNameSegLen + 1]; + int segCount = ParsePath(absolutePath, segments, MaxPathDepth); + + int32_t current = 0; // root + for (int i = 0; i < segCount; i++) { + current = FindChildByName(current, segments[i]); + if (current < 0) return -1; + } + return current; + } + + int32_t Namespace::ResolveName(const char* name, int32_t scopeNodeIndex) const { + // If it starts with '\', it's absolute + if (name[0] == '\\') + return FindNode(name); + + // Try to find relative to the current scope, walking up + char padded[MaxNameSegLen + 1]; + PadSegment(name, padded); + + int32_t scope = scopeNodeIndex; + while (scope >= 0) { + int32_t found = FindChildByName(scope, padded); + if (found >= 0) return found; + scope = m_nodes[scope].ParentIndex; + } + + return -1; + } + + NamespaceNode* Namespace::GetNode(int32_t index) { + if (index < 0 || index >= m_nodeCount) return nullptr; + return &m_nodes[index]; + } + + const NamespaceNode* Namespace::GetNode(int32_t index) const { + if (index < 0 || index >= m_nodeCount) return nullptr; + return &m_nodes[index]; + } + + char* Namespace::GetNodePath(int32_t index, char* outBuf, int maxLen) const { + if (!outBuf || maxLen < 2) return outBuf; + + // Build path by walking up to root + // Collect segments in reverse + char segments[MaxPathDepth][MaxNameSegLen + 1]; + int segCount = 0; + + int32_t cur = index; + while (cur > 0 && segCount < MaxPathDepth) { // stop at root (index 0) + auto* node = GetNode(cur); + if (!node) break; + memcpy(segments[segCount], node->Name, MaxNameSegLen + 1); + segCount++; + cur = node->ParentIndex; + } + + // Write root prefix + int pos = 0; + outBuf[pos++] = '\\'; + + // Write segments in reverse order + for (int i = segCount - 1; i >= 0 && pos < maxLen - 1; i--) { + if (i < segCount - 1 && pos < maxLen - 1) + outBuf[pos++] = '.'; + for (int j = 0; j < MaxNameSegLen && pos < maxLen - 1; j++) + outBuf[pos++] = segments[i][j]; + } + + outBuf[pos] = '\0'; + return outBuf; + } + + }; +}; diff --git a/kernel/src/ACPI/AML/AmlNamespace.hpp b/kernel/src/ACPI/AML/AmlNamespace.hpp new file mode 100644 index 0000000..4b4c476 --- /dev/null +++ b/kernel/src/ACPI/AML/AmlNamespace.hpp @@ -0,0 +1,199 @@ +/* + * AmlNamespace.hpp + * AML object types and ACPI namespace tree + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace AML { + + // ── AML Object Types ──────────────────────────────────────────── + enum class ObjectType : uint8_t { + None = 0, + Integer, + String, + Buffer, + Package, + Device, + Method, + OperationRegion, + Field, + Mutex, + Processor, + ThermalZone, + PowerResource, + BufferField, + }; + + // ── Region address spaces (OperationRegion) ───────────────────── + enum class RegionSpace : uint8_t { + SystemMemory = 0x00, + SystemIO = 0x01, + PciConfig = 0x02, + EmbeddedControl = 0x03, + SMBus = 0x04, + CMOS = 0x05, + PciBarTarget = 0x06, + }; + + // ── Constants ─────────────────────────────────────────────────── + static constexpr int MaxNameSegLen = 4; + static constexpr int MaxPathDepth = 16; + static constexpr int MaxChildren = 32; + static constexpr int MaxStringLen = 64; + static constexpr int MaxBufferLen = 256; + static constexpr int MaxPackageElements = 16; + static constexpr int MaxMethodArgs = 7; + static constexpr int MaxMethodLocals = 8; + static constexpr int MaxNamespaceNodes = 256; + + // ── AML Object ────────────────────────────────────────────────── + // Tagged union representing any AML value. Kept small for kernel use. + struct Object { + ObjectType Type = ObjectType::None; + + union { + uint64_t Integer; + + struct { + char Data[MaxStringLen]; + uint16_t Length; + } String; + + struct { + uint8_t Data[MaxBufferLen]; + uint32_t Length; + } Buffer; + + struct { + uint8_t ArgCount; // bits 0-2 of method flags + bool Serialized; // bit 3 + uint32_t AmlOffset; // offset into DSDT AML where the method body starts + uint32_t AmlLength; // length of the method body + } Method; + + struct { + RegionSpace Space; + uint64_t Offset; + uint64_t Length; + } Region; + + struct { + uint32_t RegionNodeIndex; // index of the parent OperationRegion node + uint32_t BitOffset; + uint32_t BitLength; + uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer + } Field; + + struct { + uint8_t ProcId; + uint32_t PblkAddr; + uint8_t PblkLen; + } Processor; + }; + + Object() : Type(ObjectType::None), Integer(0) {} + }; + + // ── Namespace Node ────────────────────────────────────────────── + // Each node has a 4-char name segment and an associated object. + struct NamespaceNode { + char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment + Object Obj; + int32_t ParentIndex; // -1 for root + int32_t ChildIndices[MaxChildren]; + int32_t ChildCount; + + void Clear() { + Name[0] = 0; + Obj = Object{}; + ParentIndex = -1; + ChildCount = 0; + for (int i = 0; i < MaxChildren; i++) + ChildIndices[i] = -1; + } + }; + + // ── Namespace ─────────────────────────────────────────────────── + // Flat array of nodes forming a tree via parent/child indices. + class Namespace { + public: + Namespace(); + + // Create or find a node at the given absolute path (e.g. "\\_SB_.PCI0"). + // Returns the node index, or -1 on failure. + int32_t CreateNode(const char* absolutePath); + + // Find a node by absolute path. Returns index or -1. + int32_t FindNode(const char* absolutePath) const; + + // Find a node relative to a scope. Tries: + // 1. scopePath + name + // 2. Walk up parent scopes + // 3. Root scope + int32_t ResolveName(const char* name, int32_t scopeNodeIndex) const; + + // Get a node by index. + NamespaceNode* GetNode(int32_t index); + const NamespaceNode* GetNode(int32_t index) const; + + // Get the root node index (always 0). + int32_t RootIndex() const { return 0; } + + // Build the absolute path of a node into outBuf. Returns outBuf. + char* GetNodePath(int32_t index, char* outBuf, int maxLen) const; + + // Get the number of nodes in the namespace. + int32_t NodeCount() const { return m_nodeCount; } + + // Iterate children of a node matching a given object type. + // callback returns true to continue, false to stop. + // Returns the index of the node that stopped iteration, or -1. + template + int32_t ForEachChild(int32_t parentIndex, ObjectType type, Fn callback) const { + auto* parent = GetNode(parentIndex); + if (!parent) return -1; + for (int32_t i = 0; i < parent->ChildCount; i++) { + int32_t ci = parent->ChildIndices[i]; + auto* child = GetNode(ci); + if (!child) continue; + if (type != ObjectType::None && child->Obj.Type != type) continue; + if (!callback(ci, child)) return ci; + } + return -1; + } + + // Recursively find all descendants of a given type. + template + void WalkDescendants(int32_t nodeIndex, ObjectType type, Fn callback) const { + auto* node = GetNode(nodeIndex); + if (!node) return; + for (int32_t i = 0; i < node->ChildCount; i++) { + int32_t ci = node->ChildIndices[i]; + auto* child = GetNode(ci); + if (!child) continue; + if (type == ObjectType::None || child->Obj.Type == type) + callback(ci, child); + WalkDescendants(ci, type, callback); + } + } + + private: + int32_t AllocNode(); + int32_t FindChildByName(int32_t parentIndex, const char* seg) const; + + // Parse an absolute path into segments. Returns number of segments. + static int ParsePath(const char* path, char segments[][MaxNameSegLen + 1], int maxSegments); + static bool SegmentEqual(const char* a, const char* b); + static void PadSegment(const char* src, char* dst); // pad to 4 chars with '_' + + NamespaceNode m_nodes[MaxNamespaceNodes]; + int32_t m_nodeCount; + }; + + }; +}; diff --git a/kernel/src/ACPI/AML/AmlParser.cpp b/kernel/src/ACPI/AML/AmlParser.cpp index b48fd5e..768bdb4 100644 --- a/kernel/src/ACPI/AML/AmlParser.cpp +++ b/kernel/src/ACPI/AML/AmlParser.cpp @@ -1,10 +1,11 @@ /* * AmlParser.cpp - * Primitive AML bytecode parser for extracting ACPI sleep state values + * AML bytecode parser — S5 extraction (brute-force) and interpreter init * Copyright (c) 2026 Daniel Hammer */ #include "AmlParser.hpp" +#include "AmlInterpreter.hpp" #include #include #include @@ -14,19 +15,28 @@ using namespace Kt; namespace Hal { namespace AML { - // Decode a PkgLength field and return its value. - // Advances *pos past the PkgLength bytes. + // ── Legacy S5 extraction (brute-force scan) ───────────────────── + // Kept for fast S5 extraction during early boot before the full + // interpreter is loaded. + + static constexpr uint8_t NameOp_ = 0x08; + static constexpr uint8_t PackageOp_ = 0x12; + static constexpr uint8_t ZeroOp_ = 0x00; + static constexpr uint8_t OneOp_ = 0x01; + static constexpr uint8_t OnesOp_ = 0xFF; + static constexpr uint8_t BytePrefix_ = 0x0A; + static constexpr uint8_t WordPrefix_ = 0x0B; + static constexpr uint8_t DWordPrefix_= 0x0C; + static uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos) { uint8_t lead = aml[*pos]; uint32_t byteCount = (lead >> 6) & 0x03; if (byteCount == 0) { - // Single byte encoding: bits 0-5 are the length (*pos)++; return lead & 0x3F; } - // Multi-byte: lead bits 0-3 are low nibble, followed by byteCount bytes uint32_t length = lead & 0x0F; (*pos)++; @@ -38,35 +48,32 @@ namespace Hal { return length; } - // Decode an AML integer at position *pos. - // Handles ZeroOp, OneOp, OnesOp, BytePrefix, WordPrefix, DWordPrefix. - // Returns the decoded value and advances *pos. - static uint32_t DecodeInteger(const uint8_t* aml, uint32_t* pos) { + static uint32_t DecodeIntegerLegacy(const uint8_t* aml, uint32_t* pos) { uint8_t op = aml[*pos]; switch (op) { - case ZeroOp: + case ZeroOp_: (*pos)++; return 0; - case OneOp: + case OneOp_: (*pos)++; return 1; - case OnesOp: + case OnesOp_: (*pos)++; return 0xFFFFFFFF; - case BytePrefix: { + case BytePrefix_: { (*pos)++; uint8_t val = aml[*pos]; (*pos)++; return val; } - case WordPrefix: { + case WordPrefix_: { (*pos)++; uint16_t val = aml[*pos] | ((uint16_t)aml[*pos + 1] << 8); *pos += 2; return val; } - case DWordPrefix: { + case DWordPrefix_: { (*pos)++; uint32_t val = aml[*pos] | ((uint32_t)aml[*pos + 1] << 8) @@ -76,7 +83,6 @@ namespace Hal { return val; } default: - // Unknown encoding — treat as zero and skip (*pos)++; return 0; } @@ -93,23 +99,17 @@ namespace Hal { return result; } - // The AML bytecode starts right after the CommonSDTHeader const uint8_t* aml = (const uint8_t*)dsdtData; uint32_t amlLength = header->Length; uint32_t dataStart = sizeof(ACPI::CommonSDTHeader); - // Scan for the \_S5_ name in the AML stream. - // We look for the 4-byte sequence '_S5_' preceded by a NameOp (0x08) - // or preceded by a scope path like '\' (0x5C). for (uint32_t i = dataStart; i + 4 < amlLength; i++) { if (aml[i] == '_' && aml[i+1] == 'S' && aml[i+2] == '5' && aml[i+3] == '_') { - // Verify a valid AML context: either NameOp before it, - // or '\' + NameOp pattern, or just the name in a scope bool validContext = false; - if (i >= 1 && aml[i-1] == NameOp) { + if (i >= 1 && aml[i-1] == NameOp_) { validContext = true; - } else if (i >= 2 && aml[i-2] == NameOp && aml[i-1] == '\\') { + } else if (i >= 2 && aml[i-2] == NameOp_ && aml[i-1] == '\\') { validContext = true; } @@ -118,21 +118,16 @@ namespace Hal { KernelLogStream(OK, "AML") << "Found \\_S5_ object at offset " << base::hex << (uint64_t)i; - // Move past the name uint32_t pos = i + 4; - // Expect PackageOp - if (pos >= amlLength || aml[pos] != PackageOp) { + if (pos >= amlLength || aml[pos] != PackageOp_) { KernelLogStream(ERROR, "AML") << "Expected PackageOp after \\_S5_, got " << base::hex << (uint64_t)aml[pos]; continue; } pos++; - // Decode package length (we don't actually need the value, - // but must advance past it) DecodePkgLength(aml, &pos); - // Number of elements in the package if (pos >= amlLength) continue; uint8_t numElements = aml[pos]; pos++; @@ -142,12 +137,10 @@ namespace Hal { continue; } - // First element: SLP_TYPa - result.SLP_TYPa = (uint16_t)DecodeInteger(aml, &pos); + result.SLP_TYPa = (uint16_t)DecodeIntegerLegacy(aml, &pos); - // Second element: SLP_TYPb (if present) if (numElements >= 2 && pos < amlLength) { - result.SLP_TYPb = (uint16_t)DecodeInteger(aml, &pos); + result.SLP_TYPb = (uint16_t)DecodeIntegerLegacy(aml, &pos); } else { result.SLP_TYPb = 0; } @@ -165,5 +158,81 @@ namespace Hal { return result; } + // ── Generalized brute-force sleep state scanner ────────────────── + SleepObject FindSleepState(void* dsdtData, int state) { + SleepObject result{}; + result.Valid = false; + + if (state < 0 || state > 5) return result; + + auto* header = (ACPI::CommonSDTHeader*)dsdtData; + + if (!ACPI::TestChecksum(header)) { + KernelLogStream(ERROR, "AML") << "DSDT checksum failed"; + return result; + } + + // Build the 4-char name we're looking for: _S0_ through _S5_ + char target[4] = { '_', 'S', (char)('0' + state), '_' }; + + const uint8_t* aml = (const uint8_t*)dsdtData; + uint32_t amlLength = header->Length; + uint32_t dataStart = sizeof(ACPI::CommonSDTHeader); + + for (uint32_t i = dataStart; i + 4 < amlLength; i++) { + if (aml[i] == target[0] && aml[i+1] == target[1] && + aml[i+2] == target[2] && aml[i+3] == target[3]) { + + bool validContext = false; + if (i >= 1 && aml[i-1] == NameOp_) + validContext = true; + else if (i >= 2 && aml[i-2] == NameOp_ && aml[i-1] == '\\') + validContext = true; + + if (!validContext) continue; + + uint32_t pos = i + 4; + + if (pos >= amlLength || aml[pos] != PackageOp_) continue; + pos++; + + DecodePkgLength(aml, &pos); + + if (pos >= amlLength) continue; + uint8_t numElements = aml[pos]; + pos++; + + if (numElements < 1) continue; + + result.SLP_TYPa = (uint16_t)DecodeIntegerLegacy(aml, &pos); + + if (numElements >= 2 && pos < amlLength) + result.SLP_TYPb = (uint16_t)DecodeIntegerLegacy(aml, &pos); + else + result.SLP_TYPb = 0; + + result.Valid = true; + + KernelLogStream(OK, "AML") << "\\_S" << base::dec << (uint64_t)state + << "_ found: SLP_TYPa=" << base::hex << (uint64_t)result.SLP_TYPa + << " SLP_TYPb=" << base::hex << (uint64_t)result.SLP_TYPb; + + return result; + } + } + + KernelLogStream(INFO, "AML") << "\\_S" << base::dec << (uint64_t)state + << "_ not found in DSDT"; + return result; + } + + // ── Full interpreter initialization ───────────────────────────── + void InitializeInterpreter(void* dsdtData) { + auto& interp = GetInterpreter(); + if (!interp.LoadTable(dsdtData)) { + KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter"; + } + } + }; }; diff --git a/kernel/src/ACPI/AML/AmlParser.hpp b/kernel/src/ACPI/AML/AmlParser.hpp index f468fa4..ffddf77 100644 --- a/kernel/src/ACPI/AML/AmlParser.hpp +++ b/kernel/src/ACPI/AML/AmlParser.hpp @@ -1,6 +1,6 @@ /* * AmlParser.hpp - * Primitive AML bytecode parser for extracting ACPI sleep state values + * AML bytecode parser — S5 extraction and interpreter initialization * Copyright (c) 2026 Daniel Hammer */ @@ -10,26 +10,28 @@ namespace Hal { namespace AML { - // AML opcodes used during \_S5_ parsing - static constexpr uint8_t NameOp = 0x08; - static constexpr uint8_t PackageOp = 0x12; - static constexpr uint8_t ZeroOp = 0x00; - static constexpr uint8_t OneOp = 0x01; - static constexpr uint8_t OnesOp = 0xFF; - static constexpr uint8_t BytePrefix = 0x0A; - static constexpr uint8_t WordPrefix = 0x0B; - static constexpr uint8_t DWordPrefix = 0x0C; - - struct S5Object { + struct SleepObject { uint16_t SLP_TYPa; uint16_t SLP_TYPb; bool Valid; }; + // Legacy compat alias + using S5Object = SleepObject; + // Parse a DSDT (or SSDT) AML block to find the \_S5_ object. - // dsdtData points to the CommonSDTHeader of the DSDT (HHDM-mapped). - // Returns the parsed S5 values on success. S5Object FindS5(void* dsdtData); + // Parse a DSDT to find any \_Sx_ object (x = 0-5) via brute-force scan. + // Works on any DSDT regardless of complexity — does not require the + // interpreter or namespace. + SleepObject FindSleepState(void* dsdtData, int state); + + // Initialize the AML interpreter with the DSDT. + // This loads the full table into the namespace and enables + // method evaluation, device enumeration, and field access. + // Should be called during boot after ACPI table discovery. + void InitializeInterpreter(void* dsdtData); + }; }; diff --git a/kernel/src/ACPI/AML/AmlResource.cpp b/kernel/src/ACPI/AML/AmlResource.cpp new file mode 100644 index 0000000..5da9d20 --- /dev/null +++ b/kernel/src/ACPI/AML/AmlResource.cpp @@ -0,0 +1,222 @@ +/* + * AmlResource.cpp + * ACPI resource descriptor parsing + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AmlResource.hpp" + +namespace Hal { + namespace AML { + + // ── Small Resource Tags (bits 6:3 of the tag byte) ────────────── + static constexpr uint8_t SmallIrqTag = 0x04; // IRQ descriptor + static constexpr uint8_t SmallDmaTag = 0x05; // DMA descriptor + static constexpr uint8_t SmallIoPortTag = 0x08; // I/O port descriptor + static constexpr uint8_t SmallFixedIoTag = 0x09; // Fixed I/O port descriptor + static constexpr uint8_t SmallEndTag = 0x0F; // End tag + + // ── Large Resource Tags (byte following the large tag prefix) ─── + static constexpr uint8_t LargeMemory24Tag = 0x01; + static constexpr uint8_t LargeVendorTag = 0x04; + static constexpr uint8_t LargeMemory32Tag = 0x05; + static constexpr uint8_t LargeMem32FixedTag = 0x06; + static constexpr uint8_t LargeDWordAddrTag = 0x07; + static constexpr uint8_t LargeWordAddrTag = 0x08; + static constexpr uint8_t LargeExtIrqTag = 0x09; + static constexpr uint8_t LargeQWordAddrTag = 0x0A; + static constexpr uint8_t LargeGpioTag = 0x0C; + + static uint16_t Read16(const uint8_t* p) { + return (uint16_t)p[0] | ((uint16_t)p[1] << 8); + } + + static uint32_t Read32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) + | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); + } + + static uint64_t Read64(const uint8_t* p) { + uint64_t val = 0; + for (int i = 0; i < 8; i++) + val |= (uint64_t)p[i] << (i * 8); + return val; + } + + // Find the lowest set bit in a mask. Returns the bit number, or -1. + static int FirstSetBit(uint16_t mask) { + for (int i = 0; i < 16; i++) { + if (mask & (1 << i)) return i; + } + return -1; + } + + bool ParseResourceTemplate(const uint8_t* data, uint32_t length, ResourceList& result) { + result.Count = 0; + uint32_t pos = 0; + + while (pos < length && result.Count < MaxResources) { + uint8_t tag = data[pos]; + + // End tag + if ((tag & 0x80) == 0 && ((tag >> 3) & 0x0F) == SmallEndTag) + break; + + if (tag & 0x80) { + // ── Large resource descriptor ─────────────────────── + uint8_t largeType = tag & 0x7F; + if (pos + 3 > length) break; + uint16_t resLen = Read16(&data[pos + 1]); + uint32_t dataStart = pos + 3; + uint32_t dataEnd = dataStart + resLen; + if (dataEnd > length) break; + + auto& res = result.Resources[result.Count]; + + switch (largeType) { + case LargeExtIrqTag: { + if (resLen < 2) break; + res.Type = ResourceType::ExtendedIrq; + uint8_t flags = data[dataStart]; + res.ExtendedIrq.Flags = flags; + res.ExtendedIrq.Shareable = (flags >> 3) & 1; + uint8_t irqCount = data[dataStart + 1]; + if (irqCount > 0 && resLen >= 6) { + res.ExtendedIrq.Interrupt = Read32(&data[dataStart + 2]); + } else { + res.ExtendedIrq.Interrupt = 0; + } + result.Count++; + break; + } + + case LargeMemory32Tag: { + if (resLen < 17) break; + res.Type = ResourceType::Memory32; + res.Memory32.ReadWrite = data[dataStart] & 1; + res.Memory32.Base = Read32(&data[dataStart + 1]); + // Max = data[dataStart + 5..8] + // Alignment = data[dataStart + 9..12] + res.Memory32.Length = Read32(&data[dataStart + 13]); + result.Count++; + break; + } + + case LargeMem32FixedTag: { + if (resLen < 9) break; + res.Type = ResourceType::Memory32; + res.Memory32.ReadWrite = data[dataStart] & 1; + res.Memory32.Base = Read32(&data[dataStart + 1]); + res.Memory32.Length = Read32(&data[dataStart + 5]); + result.Count++; + break; + } + + case LargeDWordAddrTag: { + if (resLen < 23) break; + res.Type = ResourceType::DWordAddress; + // ResourceType at dataStart+0, GenFlags at +1, TypeFlags at +2 + res.AddressSpace.GranularityMin = Read32(&data[dataStart + 3]); + res.AddressSpace.GranularityMax = Read32(&data[dataStart + 7]); + // Min at +7, Max at +11, Translation at +15, Length at +19 + res.AddressSpace.Base = Read32(&data[dataStart + 7]); + res.AddressSpace.Length = Read32(&data[dataStart + 19]); + result.Count++; + break; + } + + case LargeQWordAddrTag: { + if (resLen < 43) break; + res.Type = ResourceType::QWordAddress; + res.AddressSpace.GranularityMin = Read64(&data[dataStart + 3]); + res.AddressSpace.GranularityMax = Read64(&data[dataStart + 11]); + res.AddressSpace.Base = Read64(&data[dataStart + 11]); + res.AddressSpace.Length = Read64(&data[dataStart + 35]); + result.Count++; + break; + } + + case LargeWordAddrTag: { + if (resLen < 13) break; + res.Type = ResourceType::WordAddress; + res.AddressSpace.GranularityMin = Read16(&data[dataStart + 3]); + res.AddressSpace.GranularityMax = Read16(&data[dataStart + 5]); + res.AddressSpace.Base = Read16(&data[dataStart + 5]); + res.AddressSpace.Length = Read16(&data[dataStart + 11]); + result.Count++; + break; + } + + default: + // Unknown large descriptor — skip + break; + } + + pos = dataEnd; + } else { + // ── Small resource descriptor ─────────────────────── + uint8_t smallType = (tag >> 3) & 0x0F; + uint8_t resLen = tag & 0x07; + uint32_t dataStart = pos + 1; + uint32_t dataEnd = dataStart + resLen; + if (dataEnd > length) break; + + auto& res = result.Resources[result.Count]; + + switch (smallType) { + case SmallIrqTag: { + if (resLen < 2) break; + res.Type = ResourceType::Irq; + res.Irq.Mask = Read16(&data[dataStart]); + res.Irq.Flags = (resLen >= 3) ? data[dataStart + 2] : 0; + int irq = FirstSetBit(res.Irq.Mask); + res.Irq.Irq = (irq >= 0) ? (uint8_t)irq : 0; + result.Count++; + break; + } + + case SmallDmaTag: { + if (resLen < 2) break; + res.Type = ResourceType::Dma; + res.Dma.Mask = data[dataStart]; + res.Dma.Flags = data[dataStart + 1]; + int ch = FirstSetBit(res.Dma.Mask); + res.Dma.Channel = (ch >= 0) ? (uint8_t)ch : 0; + result.Count++; + break; + } + + case SmallIoPortTag: { + if (resLen < 7) break; + res.Type = ResourceType::IoPort; + res.IoPort.Decode16Bit = data[dataStart] & 1; + res.IoPort.Base = Read16(&data[dataStart + 1]); + // Max = data[dataStart + 3..4] + res.IoPort.Alignment = data[dataStart + 5]; + res.IoPort.Length = data[dataStart + 6]; + result.Count++; + break; + } + + case SmallFixedIoTag: { + if (resLen < 3) break; + res.Type = ResourceType::FixedIoPort; + res.FixedIoPort.Base = Read16(&data[dataStart]); + res.FixedIoPort.Length = data[dataStart + 2]; + result.Count++; + break; + } + + default: + break; + } + + pos = dataEnd; + } + } + + return result.Count > 0; + } + + }; +}; diff --git a/kernel/src/ACPI/AML/AmlResource.hpp b/kernel/src/ACPI/AML/AmlResource.hpp new file mode 100644 index 0000000..4152df8 --- /dev/null +++ b/kernel/src/ACPI/AML/AmlResource.hpp @@ -0,0 +1,158 @@ +/* + * AmlResource.hpp + * ACPI resource descriptor parsing (_CRS, _PRS, _SRS buffers) + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include + +namespace Hal { + namespace AML { + + // ── Resource Types ────────────────────────────────────────────── + enum class ResourceType : uint8_t { + None = 0, + Irq, + Dma, + IoPort, + FixedIoPort, + Memory16, + Memory32, + Memory32Fixed, + QWordAddress, + DWordAddress, + WordAddress, + ExtendedIrq, + GpioConnection, + }; + + // ── Single Resource Descriptor ────────────────────────────────── + struct ResourceDescriptor { + ResourceType Type; + + union { + struct { + uint16_t Mask; // bitmask of supported IRQs + uint8_t Flags; + uint8_t Irq; // decoded first IRQ number + } Irq; + + struct { + uint32_t Interrupt; // GSI number + uint8_t Flags; // edge/level, active high/low + bool Shareable; + } ExtendedIrq; + + struct { + uint8_t Mask; // bitmask of supported DMA channels + uint8_t Flags; + uint8_t Channel; // decoded first channel + } Dma; + + struct { + uint16_t Base; + uint16_t Length; + uint8_t Alignment; + bool Decode16Bit; // true = 16-bit decode, false = 10-bit + } IoPort; + + struct { + uint16_t Base; + uint8_t Length; + } FixedIoPort; + + struct { + uint32_t Base; + uint32_t Length; + bool ReadWrite; // true = R/W, false = read-only + } Memory32; + + struct { + uint64_t Base; + uint64_t Length; + uint64_t GranularityMin; + uint64_t GranularityMax; + } AddressSpace; + }; + + ResourceDescriptor() : Type(ResourceType::None) { + // Zero the largest union member + AddressSpace = {}; + } + }; + + // ── Parsed Resource List ──────────────────────────────────────── + static constexpr int MaxResources = 16; + + struct ResourceList { + ResourceDescriptor Resources[MaxResources]; + int Count; + + ResourceList() : Count(0) {} + + // Find the first resource of a given type. Returns nullptr if not found. + const ResourceDescriptor* FindFirst(ResourceType type) const { + for (int i = 0; i < Count; i++) { + if (Resources[i].Type == type) + return &Resources[i]; + } + return nullptr; + } + + // Get the IRQ number from the first IRQ or ExtendedIrq resource. + // Returns -1 if no IRQ found. + int GetIrq() const { + for (int i = 0; i < Count; i++) { + if (Resources[i].Type == ResourceType::Irq) + return Resources[i].Irq.Irq; + if (Resources[i].Type == ResourceType::ExtendedIrq) + return (int)Resources[i].ExtendedIrq.Interrupt; + } + return -1; + } + + // Get the first IO port base and length. + // Returns false if no IO port found. + bool GetIoPort(uint16_t& base, uint16_t& length) const { + for (int i = 0; i < Count; i++) { + if (Resources[i].Type == ResourceType::IoPort) { + base = Resources[i].IoPort.Base; + length = Resources[i].IoPort.Length; + return true; + } + if (Resources[i].Type == ResourceType::FixedIoPort) { + base = Resources[i].FixedIoPort.Base; + length = Resources[i].FixedIoPort.Length; + return true; + } + } + return false; + } + + // Get the first memory base and length. + bool GetMemory(uint64_t& base, uint64_t& length) const { + for (int i = 0; i < Count; i++) { + if (Resources[i].Type == ResourceType::Memory32) { + base = Resources[i].Memory32.Base; + length = Resources[i].Memory32.Length; + return true; + } + if (Resources[i].Type == ResourceType::QWordAddress || + Resources[i].Type == ResourceType::DWordAddress || + Resources[i].Type == ResourceType::WordAddress) { + base = Resources[i].AddressSpace.Base; + length = Resources[i].AddressSpace.Length; + return true; + } + } + return false; + } + }; + + // Parse a resource template buffer (as returned by _CRS evaluation) + // into a structured ResourceList. + bool ParseResourceTemplate(const uint8_t* data, uint32_t length, ResourceList& result); + + }; +}; diff --git a/kernel/src/ACPI/AcpiDevices.cpp b/kernel/src/ACPI/AcpiDevices.cpp new file mode 100644 index 0000000..bec11de --- /dev/null +++ b/kernel/src/ACPI/AcpiDevices.cpp @@ -0,0 +1,394 @@ +/* + * AcpiDevices.cpp + * ACPI device enumeration via AML namespace + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AcpiDevices.hpp" +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace AcpiDevices { + + // ── EISAID decoding ───────────────────────────────────────────── + // ACPI encodes PNP IDs as compressed 32-bit EISAIDs. + static void DecodeEisaId(uint32_t id, char* out) { + // EISA ID encoding: + // Bits 31-16: 3 compressed letters (5 bits each, '@' based) + // Bits 15-0: 4 hex digits (product number) + out[0] = (char)(((id >> 26) & 0x1F) + '@'); + out[1] = (char)(((id >> 21) & 0x1F) + '@'); + out[2] = (char)(((id >> 16) & 0x1F) + '@'); + + // Product ID as 4 hex digits + static const char hex[] = "0123456789ABCDEF"; + out[3] = hex[(id >> 12) & 0xF]; + out[4] = hex[(id >> 8) & 0xF]; + out[5] = hex[(id >> 4) & 0xF]; + out[6] = hex[(id >> 0) & 0xF]; + out[7] = '\0'; + } + + // ── String comparison ─────────────────────────────────────────── + static bool StrEqual(const char* a, const char* b) { + while (*a && *b) { + if (*a != *b) return false; + a++; b++; + } + return *a == *b; + } + + static void StrCopy(char* dst, const char* src, int maxLen) { + int i = 0; + while (src[i] && i < maxLen - 1) { + dst[i] = src[i]; + i++; + } + dst[i] = '\0'; + } + + // ── DeviceList methods ────────────────────────────────────────── + const DeviceInfo* DeviceList::FindByHid(const char* hid) const { + for (int i = 0; i < Count; i++) { + if (StrEqual(Devices[i].HardwareId, hid)) + return &Devices[i]; + } + return nullptr; + } + + const DeviceInfo* DeviceList::FindByPath(const char* path) const { + for (int i = 0; i < Count; i++) { + if (StrEqual(Devices[i].Path, path)) + return &Devices[i]; + } + return nullptr; + } + + // ── EvaluateSta ───────────────────────────────────────────────── + uint32_t EvaluateSta(int32_t deviceNodeIndex) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + // Look for _STA child + int32_t staNode = -1; + ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, + [&](int32_t idx, const AML::NamespaceNode* node) -> bool { + if (node->Name[0] == '_' && node->Name[1] == 'S' && + node->Name[2] == 'T' && node->Name[3] == 'A') { + staNode = idx; + return false; // stop + } + return true; + }); + + if (staNode < 0) return STA_DEFAULT; + + auto* staObj = ns.GetNode(staNode); + if (!staObj) return STA_DEFAULT; + + if (staObj->Obj.Type == AML::ObjectType::Method) { + AML::Object result{}; + char path[256]; + ns.GetNodePath(staNode, path, 256); + if (interp.EvaluateObject(path, result)) + return (uint32_t)result.Integer; + return STA_DEFAULT; + } + + if (staObj->Obj.Type == AML::ObjectType::Integer) + return (uint32_t)staObj->Obj.Integer; + + return STA_DEFAULT; + } + + // ── EvaluateAdr ───────────────────────────────────────────────── + uint64_t EvaluateAdr(int32_t deviceNodeIndex) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + int32_t adrNode = -1; + ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, + [&](int32_t idx, const AML::NamespaceNode* node) -> bool { + if (node->Name[0] == '_' && node->Name[1] == 'A' && + node->Name[2] == 'D' && node->Name[3] == 'R') { + adrNode = idx; + return false; + } + return true; + }); + + if (adrNode < 0) return 0; + + auto* adrObj = ns.GetNode(adrNode); + if (!adrObj) return 0; + + if (adrObj->Obj.Type == AML::ObjectType::Method) { + AML::Object result{}; + char path[256]; + ns.GetNodePath(adrNode, path, 256); + if (interp.EvaluateObject(path, result)) + return result.Integer; + return 0; + } + + if (adrObj->Obj.Type == AML::ObjectType::Integer) + return adrObj->Obj.Integer; + + return 0; + } + + // ── EvaluateHid ───────────────────────────────────────────────── + bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + int32_t hidNode = -1; + ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, + [&](int32_t idx, const AML::NamespaceNode* node) -> bool { + if (node->Name[0] == '_' && node->Name[1] == 'H' && + node->Name[2] == 'I' && node->Name[3] == 'D') { + hidNode = idx; + return false; + } + return true; + }); + + if (hidNode < 0) return false; + + AML::Object result{}; + char path[256]; + ns.GetNodePath(hidNode, path, 256); + + if (!interp.EvaluateObject(path, result)) + return false; + + if (result.Type == AML::ObjectType::Integer) { + // EISA ID + DecodeEisaId((uint32_t)result.Integer, outHid); + return true; + } + + if (result.Type == AML::ObjectType::String) { + StrCopy(outHid, result.String.Data, maxLen); + return true; + } + + return false; + } + + // ── EvaluateUid ───────────────────────────────────────────────── + bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + int32_t uidNode = -1; + ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, + [&](int32_t idx, const AML::NamespaceNode* node) -> bool { + if (node->Name[0] == '_' && node->Name[1] == 'U' && + node->Name[2] == 'I' && node->Name[3] == 'D') { + uidNode = idx; + return false; + } + return true; + }); + + if (uidNode < 0) return false; + + AML::Object result{}; + char path[256]; + ns.GetNodePath(uidNode, path, 256); + + if (!interp.EvaluateObject(path, result)) + return false; + + if (result.Type == AML::ObjectType::Integer) { + // Convert integer to string + uint64_t val = result.Integer; + char buf[21]; + int i = 0; + if (val == 0) { + buf[i++] = '0'; + } else { + char tmp[21]; + int t = 0; + while (val > 0) { + tmp[t++] = '0' + (val % 10); + val /= 10; + } + for (int j = t - 1; j >= 0; j--) + buf[i++] = tmp[j]; + } + buf[i] = '\0'; + StrCopy(outUid, buf, maxLen); + return true; + } + + if (result.Type == AML::ObjectType::String) { + StrCopy(outUid, result.String.Data, maxLen); + return true; + } + + return false; + } + + // ── EvaluateCrs ───────────────────────────────────────────────── + bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + int32_t crsNode = -1; + ns.ForEachChild(deviceNodeIndex, AML::ObjectType::None, + [&](int32_t idx, const AML::NamespaceNode* node) -> bool { + if (node->Name[0] == '_' && node->Name[1] == 'C' && + node->Name[2] == 'R' && node->Name[3] == 'S') { + crsNode = idx; + return false; + } + return true; + }); + + if (crsNode < 0) return false; + + AML::Object crsResult{}; + char path[256]; + ns.GetNodePath(crsNode, path, 256); + + if (!interp.EvaluateObject(path, crsResult)) + return false; + + if (crsResult.Type != AML::ObjectType::Buffer) + return false; + + return AML::ParseResourceTemplate(crsResult.Buffer.Data, crsResult.Buffer.Length, result); + } + + // ── EnumerateAll ──────────────────────────────────────────────── + void EnumerateAll(DeviceList& result) { + auto& interp = AML::GetInterpreter(); + if (!interp.IsInitialized()) return; + + auto& ns = interp.GetNamespace(); + result.Count = 0; + + // Walk all namespace nodes looking for Device objects + ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device, + [&](int32_t idx, [[maybe_unused]] const AML::NamespaceNode* node) { + if (result.Count >= MaxDevices) return; + + auto& dev = result.Devices[result.Count]; + dev.NodeIndex = idx; + + // Get the path + ns.GetNodePath(idx, dev.Path, 128); + + // Evaluate _STA + dev.Status = EvaluateSta(idx); + dev.IsPresent = (dev.Status & STA_PRESENT) && (dev.Status & STA_FUNCTIONAL); + + // Evaluate _HID + if (!EvaluateHid(idx, dev.HardwareId, 16)) + dev.HardwareId[0] = '\0'; + + // Evaluate _UID + if (!EvaluateUid(idx, dev.UniqueId, 16)) + dev.UniqueId[0] = '\0'; + + // Evaluate _ADR + dev.Address = EvaluateAdr(idx); + + result.Count++; + }); + + KernelLogStream(OK, "ACPI") << "Enumerated " << base::dec + << (uint64_t)result.Count << " ACPI devices"; + + // Log discovered devices + for (int i = 0; i < result.Count; i++) { + auto& dev = result.Devices[i]; + if (dev.HardwareId[0]) { + KernelLogStream(DEBUG, "ACPI") << " " << dev.Path + << " HID=" << dev.HardwareId + << (dev.IsPresent ? " [present]" : " [not present]"); + } else if (dev.Address != 0) { + KernelLogStream(DEBUG, "ACPI") << " " << dev.Path + << " ADR=" << base::hex << dev.Address + << (dev.IsPresent ? " [present]" : " [not present]"); + } + } + } + + // ── GetSleepState ─────────────────────────────────────────────── + SleepState GetSleepState(int state) { + SleepState result{}; + result.Valid = false; + + if (state < 0 || state > 5) return result; + + char path[8] = "\\_Sx_"; + path[2] = 'S'; + path[3] = '0' + (char)state; + path[4] = '_'; + path[5] = '\0'; + + auto& interp = AML::GetInterpreter(); + AML::Object obj{}; + + if (!interp.EvaluateObject(path, obj)) + return result; + + // The sleep state object should be a Package with at least 2 integers. + // During loading, we stored Package data as a raw buffer. + // For the common case, the \_S5_ was already extracted by the old parser. + // Try to get it from namespace directly. + auto& ns = interp.GetNamespace(); + int32_t node = ns.FindNode(path); + if (node < 0) return result; + + auto* nsNode = ns.GetNode(node); + if (!nsNode) return result; + + if (nsNode->Obj.Type == AML::ObjectType::Package) { + // Parse the raw package buffer to extract SLP_TYP values + const uint8_t* data = nsNode->Obj.Buffer.Data; + uint32_t len = nsNode->Obj.Buffer.Length; + if (len < 2) return result; + + // NumElements + uint8_t numElements = data[0]; + uint32_t pos = 1; + + if (numElements >= 1 && pos < len) { + // Decode first element + uint8_t elem0 = data[pos]; + if (elem0 == AML::ZeroOp) { result.SLP_TYPa = 0; pos++; } + else if (elem0 == AML::OneOp) { result.SLP_TYPa = 1; pos++; } + else if (elem0 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPa = data[pos + 1]; pos += 2; } + else { pos++; } + } + + if (numElements >= 2 && pos < len) { + uint8_t elem1 = data[pos]; + if (elem1 == AML::ZeroOp) { result.SLP_TYPb = 0; pos++; } + else if (elem1 == AML::OneOp) { result.SLP_TYPb = 1; pos++; } + else if (elem1 == AML::BytePrefix && pos + 1 < len) { result.SLP_TYPb = data[pos + 1]; pos += 2; } + else { pos++; } + } + + result.Valid = true; + } else if (nsNode->Obj.Type == AML::ObjectType::Integer) { + result.SLP_TYPa = (uint16_t)nsNode->Obj.Integer; + result.SLP_TYPb = 0; + result.Valid = true; + } + + return result; + } + + }; +}; diff --git a/kernel/src/ACPI/AcpiDevices.hpp b/kernel/src/ACPI/AcpiDevices.hpp new file mode 100644 index 0000000..bd5ccf2 --- /dev/null +++ b/kernel/src/ACPI/AcpiDevices.hpp @@ -0,0 +1,112 @@ +/* + * AcpiDevices.hpp + * ACPI device enumeration — walks namespace to discover and query devices + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace AcpiDevices { + + // ── Device Status Flags (_STA) ────────────────────────────────── + static constexpr uint32_t STA_PRESENT = (1 << 0); + static constexpr uint32_t STA_ENABLED = (1 << 1); + static constexpr uint32_t STA_VISIBLE = (1 << 2); + static constexpr uint32_t STA_FUNCTIONAL = (1 << 3); + static constexpr uint32_t STA_BATTERY = (1 << 4); // battery present (for batteries) + + // Default _STA when no _STA method exists: present + enabled + visible + functional + static constexpr uint32_t STA_DEFAULT = 0x0F; + + // ── Device Info ───────────────────────────────────────────────── + struct DeviceInfo { + char Path[128]; // full namespace path + char HardwareId[16]; // _HID value (e.g. "PNP0A03", "ACPI0001") + char UniqueId[16]; // _UID value + uint64_t Address; // _ADR value (for PCI: (device << 16) | function) + uint32_t Status; // _STA flags + int32_t NodeIndex; // namespace node index + bool IsPresent; // STA_PRESENT && STA_FUNCTIONAL + }; + + static constexpr int MaxDevices = 64; + + struct DeviceList { + DeviceInfo Devices[MaxDevices]; + int Count; + + DeviceList() : Count(0) {} + + // Find a device by HID. Returns nullptr if not found. + const DeviceInfo* FindByHid(const char* hid) const; + + // Find a device by path. Returns nullptr if not found. + const DeviceInfo* FindByPath(const char* path) const; + }; + + // ── Enumeration API ───────────────────────────────────────────── + + // Enumerate all ACPI devices in the namespace. + // The interpreter must be initialized first (LoadTable called). + void EnumerateAll(DeviceList& result); + + // Evaluate _STA for a device node. Returns STA_DEFAULT if no _STA exists. + uint32_t EvaluateSta(int32_t deviceNodeIndex); + + // Evaluate _ADR for a device node. Returns 0 if no _ADR exists. + uint64_t EvaluateAdr(int32_t deviceNodeIndex); + + // Evaluate _HID for a device node. Returns false if no _HID exists. + // Writes the HID string (or EISAID) into outHid. + bool EvaluateHid(int32_t deviceNodeIndex, char* outHid, int maxLen); + + // Evaluate _UID for a device node. + bool EvaluateUid(int32_t deviceNodeIndex, char* outUid, int maxLen); + + // Evaluate _CRS (Current Resource Settings) for a device node. + bool EvaluateCrs(int32_t deviceNodeIndex, AML::ResourceList& result); + + // ── Well-known HIDs ───────────────────────────────────────────── + // PCI Host Bridge + static constexpr const char* HID_PCI_HOST = "PNP0A03"; + static constexpr const char* HID_PCIE_HOST = "PNP0A08"; + // System Timer (PIT) + static constexpr const char* HID_PIT = "PNP0100"; + // RTC / CMOS + static constexpr const char* HID_RTC = "PNP0B00"; + // Keyboard controller (i8042) + static constexpr const char* HID_KBD = "PNP0303"; + // PS/2 Mouse + static constexpr const char* HID_MOUSE = "PNP0F13"; + // Serial port (UART) + static constexpr const char* HID_SERIAL = "PNP0501"; + // ACPI Embedded Controller + static constexpr const char* HID_EC = "PNP0C09"; + // ACPI Power Button + static constexpr const char* HID_PWRBTN = "PNP0C0C"; + // ACPI Sleep Button + static constexpr const char* HID_SLPBTN = "PNP0C0E"; + // ACPI Lid device + static constexpr const char* HID_LID = "PNP0C0D"; + // ACPI Thermal Zone + static constexpr const char* HID_THERMAL = "PNP0C0B"; + // ACPI Battery + static constexpr const char* HID_BATTERY = "PNP0C0A"; + // ACPI AC Adapter + static constexpr const char* HID_AC_ADAPTER = "ACPI0003"; + + // ── Sleep State Support ───────────────────────────────────────── + // Extract sleep state values from namespace objects (\_S0_ through \_S5_). + struct SleepState { + uint16_t SLP_TYPa; + uint16_t SLP_TYPb; + bool Valid; + }; + + // Get the sleep type values for a given sleep state (0-5). + SleepState GetSleepState(int state); + }; +}; diff --git a/kernel/src/ACPI/AcpiShutdown.cpp b/kernel/src/ACPI/AcpiShutdown.cpp index bac6e12..90f06f9 100644 --- a/kernel/src/ACPI/AcpiShutdown.cpp +++ b/kernel/src/ACPI/AcpiShutdown.cpp @@ -5,8 +5,11 @@ */ #include "AcpiShutdown.hpp" +#include "AcpiSleep.hpp" #include #include +#include +#include #include #include #include @@ -29,7 +32,7 @@ namespace Hal { void Initialize(ACPI::CommonSDTHeader* xsdt) { FADT::ParsedFADT fadt{}; if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) { - KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT — ACPI shutdown unavailable"; + KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT - ACPI shutdown unavailable"; return; } @@ -38,7 +41,7 @@ namespace Hal { AML::S5Object s5 = AML::FindS5(dsdt); if (!s5.Valid) { - KernelLogStream(ERROR, "ACPI") << "Could not find \\_S5_ in DSDT — ACPI shutdown unavailable"; + KernelLogStream(ERROR, "ACPI") << "Could not find \\_S5_ in DSDT - ACPI shutdown unavailable"; return; } @@ -50,6 +53,20 @@ namespace Hal { KernelLogStream(OK, "ACPI") << "ACPI shutdown initialized (PM1a=" << base::hex << (uint64_t)g_pm1aControlBlock << " SLP_TYPa=" << (uint64_t)g_slpTypA << ")"; + + // Initialize the full AML interpreter with the DSDT. + // This loads the entire DSDT namespace, enabling device enumeration, + // method evaluation, and field I/O for the rest of the kernel. + AML::InitializeInterpreter(dsdt); + + // Enumerate ACPI devices now that the namespace is loaded + if (AML::GetInterpreter().IsInitialized()) { + AcpiDevices::DeviceList devices{}; + AcpiDevices::EnumerateAll(devices); + } + + // Initialize S3 suspend support (reads FACS and \_S3_ from namespace) + AcpiSleep::Initialize(xsdt); } bool IsAvailable() { diff --git a/kernel/src/ACPI/AcpiSleep.cpp b/kernel/src/ACPI/AcpiSleep.cpp new file mode 100644 index 0000000..8f0d295 --- /dev/null +++ b/kernel/src/ACPI/AcpiSleep.cpp @@ -0,0 +1,403 @@ +/* + * AcpiSleep.cpp + * ACPI S3 suspend-to-RAM implementation + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AcpiSleep.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +// Assembly routines from S3Wake.asm +extern "C" int AcpiSaveAndSuspend(Hal::AcpiSleep::CpuState* stateArea); +extern "C" void AcpiResumeLongMode(Hal::AcpiSleep::CpuState* stateArea); +extern "C" void AcpiWakeEntry(); +extern "C" void* g_wakeStatePtr; + +// Real-mode trampoline from S3Trampoline.asm +extern "C" char S3TrampolineStart[]; +extern "C" char S3Trampoline64[]; +extern "C" char S3TrampolineData[]; +extern "C" char S3TrampolineEnd[]; + +// Physical address where the trampoline is copied (must be < 1MB, page-aligned) +static constexpr uint32_t TRAMPOLINE_PHYS = 0x8000; + +// GDT/TSS reload helpers +namespace Hal { + extern void BridgeLoadGDT(); + extern void LoadTSS(); +} + +namespace Hal { + namespace AcpiSleep { + + // ── State ─────────────────────────────────────────────────────── + static bool g_s3Available = false; + static uint16_t g_s3SlpTypA = 0; + static uint16_t g_s3SlpTypB = 0; + + static uint32_t g_pm1aEventBlock = 0; + static uint32_t g_pm1bEventBlock = 0; + static uint32_t g_pm1aControlBlock = 0; + static uint32_t g_pm1bControlBlock = 0; + static uint8_t g_pm1EventLength = 0; + + static FACS* g_facs = nullptr; + + // CPU state save area (aligned for fxsave) + static CpuState g_cpuState __attribute__((aligned(64))); + + // ── Initialize ────────────────────────────────────────────────── + void Initialize(ACPI::CommonSDTHeader* xsdt) { + g_s3Available = false; + + FADT::ParsedFADT fadt{}; + if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) + return; + + // Check if FACS exists + if (fadt.FacsAddress == 0) { + KernelLogStream(INFO, "S3") << "No FACS - S3 unavailable"; + return; + } + + g_facs = (FACS*)Memory::HHDM(fadt.FacsAddress); + + // Verify FACS signature + if (g_facs->Signature[0] != 'F' || g_facs->Signature[1] != 'A' || + g_facs->Signature[2] != 'C' || g_facs->Signature[3] != 'S') { + KernelLogStream(ERROR, "S3") << "Invalid FACS signature"; + g_facs = nullptr; + return; + } + + // Store PM register addresses + g_pm1aEventBlock = fadt.PM1aEventBlock; + g_pm1bEventBlock = fadt.PM1bEventBlock; + g_pm1aControlBlock = fadt.PM1aControlBlock; + g_pm1bControlBlock = fadt.PM1bControlBlock; + g_pm1EventLength = fadt.PM1EventLength; + + // Find \_S3_ via brute-force DSDT scan (same approach as \_S5_). + // This works on any DSDT regardless of complexity — does not + // require the AML interpreter or namespace to be loaded. + auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress); + AML::SleepObject s3 = AML::FindSleepState(dsdt, 3); + if (s3.Valid) { + g_s3SlpTypA = s3.SLP_TYPa; + g_s3SlpTypB = s3.SLP_TYPb; + g_s3Available = true; + + KernelLogStream(OK, "S3") << "S3 suspend available (SLP_TYPa=" + << base::hex << (uint64_t)g_s3SlpTypA + << " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")"; + KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex + << (uint64_t)Memory::VMM::g_paging->PML4; + } else { + KernelLogStream(INFO, "S3") << "\\_S3_ not found in DSDT - S3 unavailable"; + } + } + + bool IsS3Available() { + return g_s3Available; + } + + // ── Evaluate _PTS (Prepare To Sleep) ──────────────────────────── + static void EvaluatePts(int sleepState) { + auto& interp = AML::GetInterpreter(); + if (!interp.IsInitialized()) return; + + int32_t node = interp.GetNamespace().FindNode("\\_PTS"); + if (node < 0) return; + + AML::Object arg{}; + arg.Type = AML::ObjectType::Integer; + arg.Integer = (uint64_t)sleepState; + + AML::Object result{}; + interp.EvaluateMethod("\\_PTS", &arg, 1, result); + + KernelLogStream(DEBUG, "S3") << "Evaluated \\_PTS(" << base::dec << (uint64_t)sleepState << ")"; + } + + // ── Evaluate _WAK (System Wake) ───────────────────────────────── + static void EvaluateWak(int sleepState) { + auto& interp = AML::GetInterpreter(); + if (!interp.IsInitialized()) return; + + int32_t node = interp.GetNamespace().FindNode("\\_WAK"); + if (node < 0) return; + + AML::Object arg{}; + arg.Type = AML::ObjectType::Integer; + arg.Integer = (uint64_t)sleepState; + + AML::Object result{}; + interp.EvaluateMethod("\\_WAK", &arg, 1, result); + + KernelLogStream(DEBUG, "S3") << "Evaluated \\_WAK(" << base::dec << (uint64_t)sleepState << ")"; + } + + // ── Clear PM1 Status Registers ────────────────────────────────── + static void ClearPM1Status() { + // Write 1 to clear all status bits (write-1-to-clear semantics) + uint16_t clearMask = PM1_WAK_STS | PM1_PWRBTN_STS | PM1_SLPBTN_STS | + PM1_RTC_STS | PM1_TMR_STS | PM1_BM_STS | PM1_GBL_STS; + + if (g_pm1aEventBlock != 0) + Io::Out16(clearMask, (uint16_t)g_pm1aEventBlock); + if (g_pm1bEventBlock != 0) + Io::Out16(clearMask, (uint16_t)g_pm1bEventBlock); + } + + // ── Enable Wake Events ────────────────────────────────────────── + static void EnableWakeEvents() { + // Enable register is at event block + PM1EventLength/2 + uint16_t enableOffset = g_pm1EventLength / 2; + + // Enable power button and RTC as wake sources + uint16_t enableMask = PM1_PWRBTN_EN | PM1_RTC_EN; + + if (g_pm1aEventBlock != 0 && enableOffset > 0) + Io::Out16(enableMask, (uint16_t)(g_pm1aEventBlock + enableOffset)); + if (g_pm1bEventBlock != 0 && enableOffset > 0) + Io::Out16(enableMask, (uint16_t)(g_pm1bEventBlock + enableOffset)); + } + + // ── Install real-mode trampoline and set waking vector ──────────── + static void SetWakingVector() { + if (!g_facs) return; + + // Store the CpuState pointer where the 64-bit wake stub can find it. + g_wakeStatePtr = (void*)&g_cpuState; + + // Copy the real-mode trampoline to low physical memory (0x8000). + // This code transitions real mode → protected mode → long mode + // and then jumps to AcpiResumeLongMode in the kernel. + uint32_t trampolineSize = (uint32_t)(S3TrampolineEnd - S3TrampolineStart); + uint8_t* trampolineDst = (uint8_t*)Memory::HHDM((uint64_t)TRAMPOLINE_PHYS); + uint8_t* trampolineSrc = (uint8_t*)S3TrampolineStart; + memcpy(trampolineDst, trampolineSrc, trampolineSize); + + // Patch the trampoline data area with CR3 and resume addresses. + // Data layout (from S3Trampoline.asm): + // +0: uint64_t CR3 (full 64-bit PML4 physical address) + // +8: uint64_t CpuState virtual address + // +16: uint64_t AcpiResumeLongMode virtual address + uint32_t dataOffset = (uint32_t)(S3TrampolineData - S3TrampolineStart); + uint8_t* dataArea = trampolineDst + dataOffset; + + // Use the kernel master PML4 (which has 0x8000 identity-mapped) + // rather than the saved process PML4 (which doesn't). + // AcpiResumeLongMode will restore the saved CR3 after we're + // safely back in long mode with kernel GDT/IDT. + uint64_t cr3val = (uint64_t)Memory::VMM::g_paging->PML4; + + // The 16-bit->32-bit trampoline path can only load 32-bit CR3. + // If the PML4 is above 4GB, the real-mode wake path would fail. + if (cr3val > 0xFFFFFFFF) { + KernelLogStream(ERROR, "S3") << "PML4 at " << base::hex << cr3val + << " is above 4GB - real-mode wake path will fail!"; + } + + memcpy(dataArea + 0, &cr3val, 8); + + uint64_t statePtr = (uint64_t)&g_cpuState; + memcpy(dataArea + 8, &statePtr, 8); + + uint64_t resumeAddr = (uint64_t)&AcpiResumeLongMode; + memcpy(dataArea + 16, &resumeAddr, 8); + + // Set the 32-bit waking vector only. Setting X_FirmwareWakingVector + // to non-zero causes this laptop's firmware to hang during wake. + g_facs->FirmwareWakingVector = TRAMPOLINE_PHYS; + g_facs->X_FirmwareWakingVector = 0; + + KernelLogStream(DEBUG, "S3") << "Trampoline installed at " << base::hex + << (uint64_t)TRAMPOLINE_PHYS << " (" << base::dec + << (uint64_t)trampolineSize << " bytes)"; + KernelLogStream(DEBUG, "S3") << "Trampoline CR3 = " << base::hex << cr3val; + } + + // ── Wait for WAK_STS ──────────────────────────────────────────── + static void WaitForWake() { + // After entering S3, the CPU halts. On resume, firmware runs the + // waking vector. But if we somehow didn't enter sleep (e.g. immediate + // wake), poll WAK_STS. + for (int i = 0; i < 10000; i++) { + if (g_pm1aEventBlock != 0) { + uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock); + if (sts & PM1_WAK_STS) return; + } + Io::IoPortWait(); + } + } + + // ── Suspend ───────────────────────────────────────────────────── + int Suspend() { + if (!g_s3Available) { + KernelLogStream(ERROR, "S3") << "S3 suspend not available"; + return -1; + } + + KernelLogStream(INFO, "S3") << "Preparing for S3 suspend..."; + + // 1. Evaluate _PTS(3) — Prepare To Sleep + EvaluatePts(3); + + // 2. Save CPU state. AcpiSaveAndSuspend returns 1 on initial call, + // and 0 when we resume from S3 (AcpiResumeLongMode sets RAX=0). + int resumed = !AcpiSaveAndSuspend(&g_cpuState); + if (resumed) { + // ── RESUME PATH ───────────────────────────────────────── + // We just woke from S3. Firmware ran our waking vector which + // called AcpiResumeLongMode, restoring all registers and + // returning here with 0. + + // Debug: write a green rectangle to the top-left corner of the + // framebuffer. The framebuffer memory survives S3 (it's RAM). + // This will be visible once the GPU display plane is restored, + // confirming the CPU successfully completed the resume path. + { + uint32_t* fb = ::Graphics::Cursor::GetFramebufferBase(); + uint64_t pitch = ::Graphics::Cursor::GetFramebufferPitch(); + if (fb && pitch > 0) { + for (int y = 0; y < 32; y++) { + uint32_t* row = (uint32_t*)((uint8_t*)fb + y * pitch); + for (int x = 0; x < 32; x++) { + row[x] = 0xFF00FF00; // Green (ARGB) + } + } + } + } + + // Reload GDT and TSS (firmware may have clobbered them) + Hal::BridgeLoadGDT(); + Hal::LoadTSS(); + + // Re-disable legacy 8259 PIC. Firmware may have re-enabled + // it during S3 resume POST, which could cause spurious + // interrupts on conflicting vectors once we enable interrupts. + Hal::DisableLegacyPic(); + + // Re-enable the Local APIC (MSR global enable + SVR + TPR) + Hal::LocalApic::Reinitialize(); + + // Restore I/O APIC redirection entries (lost during S3). + // Must be done before enabling interrupts so IRQ routing + // is in place when devices start generating interrupts. + Hal::IoApic::Reinitialize(); + + // Restart the APIC timer + Timekeeping::ApicTimerReinitialize(); + + // Clear WAK_STS + ClearPM1Status(); + + // Evaluate _WAK(3) + EvaluateWak(3); + + // Re-enable PS/2 controller (ports and interrupts may be + // disabled after S3; skip full self-test to avoid resetting + // attached devices) + Drivers::PS2::Reinitialize(); + + // Restore Intel GPU display (GTT + display plane lost during S3) + if (Drivers::Graphics::IntelGPU::IsInitialized()) { + Drivers::Graphics::IntelGPU::Reinitialize(); + } + + // Re-enable interrupts + asm volatile("sti"); + + KernelLogStream(OK, "S3") << "Resumed from S3 suspend"; + + return 0; + } + + // ── SUSPEND PATH (initial save returned 1) ────────────────── + + // 3. Disable interrupts + asm volatile("cli"); + + // 4. Identity-map the trampoline page so the CPU can execute it + // after paging is re-enabled with our CR3 during wake. + // Physical 0x8000 → virtual 0x8000 (flat identity mapping). + Memory::VMM::g_paging->Map(TRAMPOLINE_PHYS, TRAMPOLINE_PHYS); + + // 5. Flush all caches + asm volatile("wbinvd"); + + // 6. Set the waking vector in FACS + SetWakingVector(); + + // 7. Clear any pending PM1 status bits + ClearPM1Status(); + + // 8. Enable wake events (power button, RTC) + EnableWakeEvents(); + + // 9. Write SLP_TYP to PM1 control registers (without SLP_EN) + uint16_t pm1a = Io::In16((uint16_t)g_pm1aControlBlock); + pm1a = (pm1a & ~PM1_SLP_TYP_MASK) | (g_s3SlpTypA << 10); + Io::Out16(pm1a, (uint16_t)g_pm1aControlBlock); + + if (g_pm1bControlBlock != 0) { + uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock); + pm1b = (pm1b & ~PM1_SLP_TYP_MASK) | (g_s3SlpTypB << 10); + Io::Out16(pm1b, (uint16_t)g_pm1bControlBlock); + } + + // 10. Flush caches again + asm volatile("wbinvd"); + + // 11. Assert SLP_EN — this triggers S3 entry + Io::Out16(pm1a | PM1_SLP_EN, (uint16_t)g_pm1aControlBlock); + + if (g_pm1bControlBlock != 0) { + uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock); + Io::Out16(pm1b | PM1_SLP_EN, (uint16_t)g_pm1bControlBlock); + } + + // The CPU must halt for the chipset/hypervisor to complete the S3 + // transition. On real hardware the chipset cuts power after SLP_EN; + // on QEMU/KVM the vCPU must reach HLT before QEMU finalizes S3. + // On resume, firmware jumps to our waking vector. + for (;;) { + asm volatile("hlt"); + // If we wake from HLT (spurious or real wake), check WAK_STS + if (g_pm1aEventBlock != 0) { + uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock); + if (sts & PM1_WAK_STS) break; + } + } + + // If we get here, either S3 failed or we woke immediately. + // The normal wake path goes through AcpiResumeLongMode instead. + // Re-enable interrupts as a safety measure. + asm volatile("sti"); + + KernelLogStream(WARNING, "S3") << "S3 entry may have failed (fell through)"; + return 0; + } + + }; +}; diff --git a/kernel/src/ACPI/AcpiSleep.hpp b/kernel/src/ACPI/AcpiSleep.hpp new file mode 100644 index 0000000..7b41f8f --- /dev/null +++ b/kernel/src/ACPI/AcpiSleep.hpp @@ -0,0 +1,90 @@ +/* + * AcpiSleep.hpp + * ACPI S3 suspend-to-RAM support + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace AcpiSleep { + + // ── FACS (Firmware ACPI Control Structure) ────────────────────── + struct FACS { + char Signature[4]; // "FACS" + uint32_t Length; + uint32_t HardwareSignature; + uint32_t FirmwareWakingVector; // 32-bit real-mode wake address + uint32_t GlobalLock; + uint32_t Flags; + uint64_t X_FirmwareWakingVector; // 64-bit wake address (ACPI 2.0+) + uint8_t Version; + uint8_t Reserved[3]; + uint32_t OspmFlags; + } __attribute__((packed)); + + // FACS Flags + static constexpr uint32_t FACS_S4BIOS_F = (1 << 0); + static constexpr uint32_t FACS_64BIT_WAKE_F = (1 << 1); + + // FADT Flags relevant to sleep + static constexpr uint32_t FADT_S4_RTC_STS_VALID = (1 << 16); + static constexpr uint32_t FADT_HW_REDUCED_ACPI = (1 << 20); + + // ── PM1 Status Register Bits ──────────────────────────────────── + static constexpr uint16_t PM1_TMR_STS = (1 << 0); + static constexpr uint16_t PM1_BM_STS = (1 << 4); + static constexpr uint16_t PM1_GBL_STS = (1 << 5); + static constexpr uint16_t PM1_PWRBTN_STS = (1 << 8); + static constexpr uint16_t PM1_SLPBTN_STS = (1 << 9); + static constexpr uint16_t PM1_RTC_STS = (1 << 10); + static constexpr uint16_t PM1_WAK_STS = (1 << 15); + + // ── PM1 Enable Register Bits ──────────────────────────────────── + static constexpr uint16_t PM1_TMR_EN = (1 << 0); + static constexpr uint16_t PM1_GBL_EN = (1 << 5); + static constexpr uint16_t PM1_PWRBTN_EN = (1 << 8); + static constexpr uint16_t PM1_SLPBTN_EN = (1 << 9); + static constexpr uint16_t PM1_RTC_EN = (1 << 10); + + // ── PM1 Control Register Bits ─────────────────────────────────── + static constexpr uint16_t PM1_SCI_EN = (1 << 0); + static constexpr uint16_t PM1_BM_RLD = (1 << 1); + static constexpr uint16_t PM1_SLP_TYP_MASK = 0x1C00; // bits 10-12 + static constexpr uint16_t PM1_SLP_EN = (1 << 13); + + // ── CPU State (saved across S3) ───────────────────────────────── + // Layout must match S3Wake.asm offsets exactly. + // No FPU/SSE state — the kernel is compiled with -mno-sse. + struct CpuState { + uint64_t Rax, Rbx, Rcx, Rdx; // 0x00 - 0x18 + uint64_t Rsi, Rdi, Rbp, Rsp; // 0x20 - 0x38 + uint64_t R8, R9, R10, R11; // 0x40 - 0x58 + uint64_t R12, R13, R14, R15; // 0x60 - 0x78 + uint64_t Rflags; // 0x80 + uint64_t Rip; // 0x88 + uint64_t Cr3; // 0x90 + uint64_t Cr0, Cr4; // 0x98, 0xA0 + uint8_t GdtPtr[10]; // 0xA8 + uint8_t IdtPtr[10]; // 0xB2 + } __attribute__((aligned(16))); + + // ── Sleep API ─────────────────────────────────────────────────── + + // Initialize sleep support. Called during boot after ACPI init. + // xsdt is used to read FADT for FACS and PM register addresses. + void Initialize(ACPI::CommonSDTHeader* xsdt); + + // Returns true if S3 suspend is supported by the hardware. + bool IsS3Available(); + + // Perform an S3 suspend-to-RAM. + // Saves CPU state, evaluates _PTS(3), programs PM registers, + // sets waking vector, and enters S3. + // Returns 0 on successful resume, or -1 if S3 is unavailable. + int Suspend(); + + }; +}; diff --git a/kernel/src/ACPI/FADT.cpp b/kernel/src/ACPI/FADT.cpp index cf2d7f4..70699a3 100644 --- a/kernel/src/ACPI/FADT.cpp +++ b/kernel/src/ACPI/FADT.cpp @@ -58,14 +58,34 @@ namespace Hal { result.DsdtAddress = fadt->Dsdt; } + // FACS address (for waking vector) + if (fadt->Header.Length >= offsetof(Table, X_FirmwareControl) + 8 && fadt->X_FirmwareControl != 0) { + result.FacsAddress = fadt->X_FirmwareControl; + } else { + result.FacsAddress = fadt->FirmwareCtrl; + } + + result.PM1aEventBlock = fadt->PM1aEventBlock; + result.PM1bEventBlock = fadt->PM1bEventBlock; result.PM1aControlBlock = fadt->PM1aControlBlock; result.PM1bControlBlock = fadt->PM1bControlBlock; + result.PM2ControlBlock = fadt->PM2ControlBlock; + result.PMTimerBlock = fadt->PMTimerBlock; + result.GPE0Block = fadt->GPE0Block; + result.GPE1Block = fadt->GPE1Block; result.SMI_CommandPort = fadt->SMI_CommandPort; + result.SCI_Interrupt = fadt->SCI_Interrupt; + result.PM1EventLength = fadt->PM1EventLength; + result.GPE0Length = fadt->GPE0Length; + result.GPE1Length = fadt->GPE1Length; result.AcpiEnable = fadt->AcpiEnable; result.AcpiDisable = fadt->AcpiDisable; + result.Flags = fadt->Flags; result.Valid = true; KernelLogStream(INFO, "FADT") << "DSDT at " << base::hex << result.DsdtAddress; + KernelLogStream(INFO, "FADT") << "FACS at " << base::hex << result.FacsAddress; + KernelLogStream(INFO, "FADT") << "PM1a_EVT_BLK: " << base::hex << (uint64_t)result.PM1aEventBlock; KernelLogStream(INFO, "FADT") << "PM1a_CNT_BLK: " << base::hex << (uint64_t)result.PM1aControlBlock; if (result.PM1bControlBlock != 0) { diff --git a/kernel/src/ACPI/FADT.hpp b/kernel/src/ACPI/FADT.hpp index 414a9ab..912ea2e 100644 --- a/kernel/src/ACPI/FADT.hpp +++ b/kernel/src/ACPI/FADT.hpp @@ -71,11 +71,23 @@ namespace Hal { struct ParsedFADT { uint64_t DsdtAddress; + uint64_t FacsAddress; + uint32_t PM1aEventBlock; + uint32_t PM1bEventBlock; uint32_t PM1aControlBlock; uint32_t PM1bControlBlock; + uint32_t PM2ControlBlock; + uint32_t PMTimerBlock; + uint32_t GPE0Block; + uint32_t GPE1Block; uint32_t SMI_CommandPort; + uint16_t SCI_Interrupt; + uint8_t PM1EventLength; + uint8_t GPE0Length; + uint8_t GPE1Length; uint8_t AcpiEnable; uint8_t AcpiDisable; + uint32_t Flags; bool Valid; }; diff --git a/kernel/src/ACPI/S3Trampoline.asm b/kernel/src/ACPI/S3Trampoline.asm new file mode 100644 index 0000000..59974c5 --- /dev/null +++ b/kernel/src/ACPI/S3Trampoline.asm @@ -0,0 +1,115 @@ +; +; S3Trampoline.asm +; S3 resume trampoline with both 16-bit and 64-bit entry points +; Copyright (c) 2026 Daniel Hammer +; +; Copied to physical 0x8000 before entering S3. Two entry points: +; 0x8000: real-mode (16-bit) for legacy BIOS wake +; 0x8100: long-mode (64-bit) for UEFI wake +; + +[bits 16] +section .text + +; ═════════════════════════════════════════════════════════════════════════ +; 16-bit real-mode entry (FirmwareWakingVector = 0x8000) +; ═════════════════════════════════════════════════════════════════════════ +global S3TrampolineStart +S3TrampolineStart: + cli + cld + + mov ax, 0x0800 + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x00F0 + + lgdt [ds:gdt_ptr - S3TrampolineStart] + + mov eax, cr0 + or eax, 1 + mov cr0, eax + + jmp dword 0x08:(0x8000 + pm32_entry - S3TrampolineStart) + +[bits 32] +pm32_entry: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + mov eax, cr4 + or eax, (1 << 5) + mov cr4, eax + + ; Load lower 32 bits of CR3 from data area + mov eax, [0x8000 + data_cr3 - S3TrampolineStart] + mov cr3, eax + + mov ecx, 0xC0000080 + rdmsr + or eax, (1 << 8) + wrmsr + + mov eax, cr0 + or eax, (1 << 31) + mov cr0, eax + + jmp dword 0x18:(0x8000 + lm64_common - S3TrampolineStart) + +; ── Temporary GDT ────────────────────────────────────────────────────── +align 16 +gdt_start: + dq 0x0000000000000000 ; 0x00: Null + dq 0x00CF9A000000FFFF ; 0x08: 32-bit code + dq 0x00CF92000000FFFF ; 0x10: 32-bit data + dq 0x00209A0000000000 ; 0x18: 64-bit code + dq 0x0000920000000000 ; 0x20: 64-bit data +gdt_end: + +gdt_ptr: + dw gdt_end - gdt_start - 1 + dd 0x8000 + gdt_start - S3TrampolineStart + + +; ═════════════════════════════════════════════════════════════════════════ +; 64-bit entry at offset 0x100 (X_FirmwareWakingVector = 0x8100) +; UEFI firmware jumps here already in long mode with its own page tables. +; ═════════════════════════════════════════════════════════════════════════ +[bits 64] +times (0x100 - ($ - S3TrampolineStart)) db 0x90 + +global S3Trampoline64 +S3Trampoline64: + cli + cld + + ; Load kernel PML4 (full 64-bit) - firmware identity-maps low memory + mov rax, [0x8000 + data_cr3 - S3TrampolineStart] + mov cr3, rax + + ; Fall through to common path + +; ── Common 64-bit path (both entries converge here) ────────────────── +lm64_common: + mov rdi, [0x8000 + data_state_ptr - S3TrampolineStart] + mov rax, [0x8000 + data_resume_addr - S3TrampolineStart] + jmp rax + + +; ═════════════════════════════════════════════════════════════════════════ +; Data area (patched by kernel before S3) +; ═════════════════════════════════════════════════════════════════════════ +align 8 +global S3TrampolineData +S3TrampolineData: +data_cr3: dq 0 ; kernel PML4 physical address (full 64-bit) +data_state_ptr: dq 0 ; virtual address of CpuState +data_resume_addr: dq 0 ; virtual address of AcpiResumeLongMode + +global S3TrampolineEnd +S3TrampolineEnd: diff --git a/kernel/src/ACPI/S3Wake.asm b/kernel/src/ACPI/S3Wake.asm new file mode 100644 index 0000000..8551876 --- /dev/null +++ b/kernel/src/ACPI/S3Wake.asm @@ -0,0 +1,184 @@ +; +; S3Wake.asm +; S3 suspend/resume: CPU state save and wake trampoline +; Copyright (c) 2026 Daniel Hammer +; + +[bits 64] +section .text + +; ─── AcpiSaveAndSuspend ────────────────────────────────────────────────── +; extern "C" int AcpiSaveAndSuspend(CpuState* stateArea) +; rdi = pointer to CpuState structure +; +; Saves all CPU registers into the CpuState structure, then returns 1. +; The caller is expected to enter S3 after this returns. +; When the system resumes, AcpiResumeLongMode jumps to the saved RIP +; with RAX=0, so Suspend() knows it's a resume. +; +; Returns: 1 on initial call (proceed to enter S3) +; 0 on resume from S3 +; +; CpuState layout: +; 0x00 RAX 0x40 R8 0x80 RFLAGS +; 0x08 RBX 0x48 R9 0x88 RIP +; 0x10 RCX 0x50 R10 0x90 CR3 +; 0x18 RDX 0x58 R11 0x98 CR0 +; 0x20 RSI 0x60 R12 0xA0 CR4 +; 0x28 RDI 0x68 R13 0xA8 GDT (10 bytes) +; 0x30 RBP 0x70 R14 0xB2 IDT (10 bytes) +; 0x38 RSP 0x78 R15 +; +global AcpiSaveAndSuspend +AcpiSaveAndSuspend: + ; Save all general-purpose registers + mov [rdi + 0x00], rax + mov [rdi + 0x08], rbx + mov [rdi + 0x10], rcx + mov [rdi + 0x18], rdx + mov [rdi + 0x20], rsi + mov [rdi + 0x28], rdi ; save rdi (pointer to state area) + mov [rdi + 0x30], rbp + mov [rdi + 0x38], rsp + mov [rdi + 0x40], r8 + mov [rdi + 0x48], r9 + mov [rdi + 0x50], r10 + mov [rdi + 0x58], r11 + mov [rdi + 0x60], r12 + mov [rdi + 0x68], r13 + mov [rdi + 0x70], r14 + mov [rdi + 0x78], r15 + + ; Save RFLAGS + pushfq + pop rax + mov [rdi + 0x80], rax + + ; Save return address as resume RIP + mov rax, [rsp] ; return address on stack + mov [rdi + 0x88], rax + + ; Save control registers + mov rax, cr3 + mov [rdi + 0x90], rax + mov rax, cr0 + mov [rdi + 0x98], rax + mov rax, cr4 + mov [rdi + 0xA0], rax + + ; Save GDT pointer (offset 0xA8) + sgdt [rdi + 0xA8] + + ; Save IDT pointer (offset 0xB2) + sidt [rdi + 0xB2] + + ; Return 1 = "initial save, now enter S3" + mov rax, 1 + ret + + +; ─── AcpiWakeEntry ─────────────────────────────────────────────────────── +; This is the actual waking vector target. Firmware jumps here after S3 +; resume. It loads the CpuState pointer from a fixed location (set before +; suspend) and falls through to AcpiResumeLongMode. +; +; For 64-bit waking vector (X_FirmwareWakingVector): firmware resumes in +; long mode and jumps directly here. We load rdi from the saved pointer +; and proceed to restore state. +; +; For QEMU with OVMF and similar UEFI firmware, the 64-bit path works. +; Real-mode (32-bit FirmwareWakingVector) wake is not yet supported. +; +global AcpiWakeEntry +AcpiWakeEntry: + ; Clear direction flag (firmware may have left it set) + cld + + ; Load the CpuState pointer from the well-known location. + ; g_wakeStatePtr is set by the C code before entering S3. + lea rdi, [rel g_wakeStatePtr] + mov rdi, [rdi] + + ; Fall through to restore all state + jmp AcpiResumeLongMode + + +; ─── Well-known pointer to CpuState (set before suspend) ──────────────── +; Placed in .text so AcpiWakeEntry can use RIP-relative addressing +; without a cross-section relocation warning. +; extern "C" void* g_wakeStatePtr; +global g_wakeStatePtr +g_wakeStatePtr: dq 0 + +; ─── AcpiResumeLongMode ────────────────────────────────────────────────── +; extern "C" void AcpiResumeLongMode(CpuState* stateArea) +; rdi = pointer to CpuState structure +; +; Restores all saved registers and returns to the original Suspend() caller +; with RAX=0 to indicate "resumed from S3". +; +global AcpiResumeLongMode +AcpiResumeLongMode: + ; At entry: rdi = CpuState*, RSP = trampoline stack (unmapped junk), + ; CR3 = kernel PML4 (loaded by trampoline), CS = trampoline selector. + ; + ; We must restore the kernel stack FIRST so push/pop work, then reload + ; GDT/CS/IDT before any interrupt can fire. + + ; Restore kernel RSP immediately so we have a valid stack + mov rsp, [rdi + 0x38] + + ; Restore GDT + lgdt [rdi + 0xA8] + + ; Reload CS to kernel code selector (0x08) via far return. + ; Without this, CS still holds the trampoline's selector (0x18) + ; which maps to UserData in the kernel GDT → #GP on first interrupt. + push 0x08 ; kernel code selector + lea rax, [rel .reload_cs] + push rax + retfq +.reload_cs: + + ; Reload data segment registers with kernel data selector + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Restore IDT + lidt [rdi + 0xB2] + + ; Restore saved CR3 (process PML4). The trampoline used the kernel + ; master PML4 to get here; now switch to the actual saved context. + mov rax, [rdi + 0x90] + mov cr3, rax + + ; Restore general-purpose registers (skip RSP — already restored above) + mov rbx, [rdi + 0x08] + mov rcx, [rdi + 0x10] + mov rdx, [rdi + 0x18] + mov rsi, [rdi + 0x20] + mov rbp, [rdi + 0x30] + mov r8, [rdi + 0x40] + mov r9, [rdi + 0x48] + mov r10, [rdi + 0x50] + mov r11, [rdi + 0x58] + mov r12, [rdi + 0x60] + mov r13, [rdi + 0x68] + mov r14, [rdi + 0x70] + mov r15, [rdi + 0x78] + + ; Restore RFLAGS + mov rax, [rdi + 0x80] + push rax + popfq + + ; Restore rdi last + mov rdi, [rdi + 0x28] + + ; Return 0 = "resumed from S3" + xor rax, rax + ret diff --git a/kernel/src/Api/Power.hpp b/kernel/src/Api/Power.hpp index 22f74d6..de9d3e3 100644 --- a/kernel/src/Api/Power.hpp +++ b/kernel/src/Api/Power.hpp @@ -1,6 +1,6 @@ /* * Power.hpp - * SYS_RESET, SYS_SHUTDOWN syscalls + * SYS_RESET, SYS_SHUTDOWN, SYS_SUSPEND syscalls * Copyright (c) 2026 Daniel Hammer */ @@ -9,6 +9,7 @@ #include #include #include +#include namespace Montauk { @@ -41,4 +42,11 @@ namespace Montauk { asm volatile("cli; hlt"); __builtin_unreachable(); } + + static int64_t Sys_Suspend() { + if (!Hal::AcpiSleep::IsS3Available()) { + return -1; // S3 not supported + } + return (int64_t)Hal::AcpiSleep::Suspend(); + } }; diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 45da212..477a873 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -22,7 +22,7 @@ #include "Info.hpp" // SYS_GETINFO #include "Graphics.hpp" // SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE #include "Net.hpp" // SYS_PING, SYS_SOCKET, SYS_CONNECT, SYS_BIND, SYS_LISTEN, SYS_ACCEPT, SYS_SEND, SYS_RECV, SYS_CLOSESOCK, SYS_SENDTO, SYS_RECVFROM, SYS_GETNETCFG, SYS_SETNETCFG, SYS_RESOLVE -#include "Power.hpp" // SYS_RESET, SYS_SHUTDOWN +#include "Power.hpp" // SYS_RESET, SYS_SHUTDOWN, SYS_SUSPEND #include "Mouse.hpp" // SYS_MOUSESTATE, SYS_SETMOUSEBOUNDS #include "IoRedir.hpp" // SYS_SPAWN_REDIR, SYS_CHILDIO_READ, SYS_CHILDIO_WRITE, SYS_CHILDIO_WRITEKEY, SYS_CHILDIO_SETTERMSZ #include "Random.hpp" // SYS_GETRANDOM @@ -331,6 +331,8 @@ namespace Montauk { case SYS_BTINFO: if (!ValidUserPtr(frame->arg1)) return -1; return Sys_BtInfo((BtAdapterInfo*)frame->arg1); + case SYS_SUSPEND: + return Sys_Suspend(); default: return -1; } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 7a865f1..4092d19 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -171,6 +171,9 @@ namespace Montauk { static constexpr uint64_t SYS_BTLIST = 87; static constexpr uint64_t SYS_BTINFO = 88; + /* Power.hpp */ + static constexpr uint64_t SYS_SUSPEND = 89; + static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; diff --git a/kernel/src/Drivers/Graphics/IntelGPU.cpp b/kernel/src/Drivers/Graphics/IntelGPU.cpp index 42a263f..7574b1d 100644 --- a/kernel/src/Drivers/Graphics/IntelGPU.cpp +++ b/kernel/src/Drivers/Graphics/IntelGPU.cpp @@ -640,4 +640,58 @@ namespace Drivers::Graphics::IntelGPU { return g_fbPitch; } + void Reinitialize() { + if (!g_initialized || !g_mmioBase) return; + + KernelLogStream(INFO, "IntelGPU") << "Reinitializing display after S3 resume"; + + // 1. Re-enable PCI memory space and bus mastering. S3 resets the PCI + // command register, so MMIO writes to the GPU are silently dropped + // until we re-enable these bits. + Pci::EnableBusMaster(g_gpuInfo.pciBus, g_gpuInfo.pciDevice, g_gpuInfo.pciFunction); + + KernelLogStream(DEBUG, "IntelGPU") << "PCI memory space and bus mastering re-enabled"; + + // 2. Disable VGA plane (firmware may have re-enabled it during POST) + DisableVga(); + + // 3. Reprogram GTT entries (hardware lost all GTT state during S3) + uint64_t pageCount = (g_fbSize + 0xFFF) / 0x1000; + if (g_gpuGen >= 8) { + volatile uint64_t* gtt64 = (volatile uint64_t*)g_gttBase; + for (uint64_t i = 0; i < pageCount; i++) { + gtt64[i] = MakeGttPte64(g_fbPhysBase + i * 0x1000); + } + // Flush GTT writes by reading back the last entry + (void)gtt64[pageCount - 1]; + } else { + volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase; + for (uint64_t i = 0; i < pageCount; i++) { + gtt32[i] = MakeGttPte32(g_fbPhysBase + i * 0x1000); + } + // Flush GTT writes by reading back the last entry + (void)gtt32[pageCount - 1]; + } + + // 4. Re-enable the display pipe. After S3, the pipe may be off even + // though the firmware lit the backlight. Wait for it to become + // active before programming the display plane. + uint32_t pipeConf = ReadReg(PIPEACONF); + if (!(pipeConf & PIPECONF_ENABLE)) { + WriteReg(PIPEACONF, pipeConf | PIPECONF_ENABLE); + + // Wait for pipe to become active (PIPECONF_STATE bit 30) + for (int i = 0; i < 100000; i++) { + if (ReadReg(PIPEACONF) & PIPECONF_STATE) + break; + asm volatile("pause"); + } + } + + // 5. Reprogram display plane to point at our GTT-mapped framebuffer + ProgramDisplayPlane(); + + KernelLogStream(OK, "IntelGPU") << "Display restored after S3 resume"; + } + }; diff --git a/kernel/src/Drivers/Graphics/IntelGPU.hpp b/kernel/src/Drivers/Graphics/IntelGPU.hpp index d065630..16ee175 100644 --- a/kernel/src/Drivers/Graphics/IntelGPU.hpp +++ b/kernel/src/Drivers/Graphics/IntelGPU.hpp @@ -401,6 +401,9 @@ namespace Drivers::Graphics::IntelGPU { // Check if an Intel GPU was found and initialized bool IsInitialized(); + // Restore display state after S3 resume (GTT entries, pipe, display plane) + void Reinitialize(); + // Get detected GPU information const GpuInfo* GetGpuInfo(); diff --git a/kernel/src/Drivers/PS2/PS2Controller.cpp b/kernel/src/Drivers/PS2/PS2Controller.cpp index 34ef415..c84d80b 100644 --- a/kernel/src/Drivers/PS2/PS2Controller.cpp +++ b/kernel/src/Drivers/PS2/PS2Controller.cpp @@ -63,6 +63,31 @@ namespace Drivers::PS2 { return g_DualChannel; } + void Reinitialize() { + // Flush any stale data from the output buffer + FlushOutputBuffer(); + + // Re-enable both ports + SendCommand(CmdEnablePort1); + if (g_DualChannel) { + SendCommand(CmdEnablePort2); + } + + // Re-enable interrupts and translation in the config byte + SendCommand(CmdReadConfig); + uint8_t config = ReadData(); + + config |= ConfigPort1Interrupt | ConfigPort1Translation; + if (g_DualChannel) { + config |= ConfigPort2Interrupt; + } + + SendCommand(CmdWriteConfig); + SendData(config); + + Kt::KernelLogStream(Kt::OK, "PS2") << "Controller re-enabled after S3 resume"; + } + void Initialize() { Kt::KernelLogStream(Kt::INFO, "PS2") << "Initializing PS/2 controller"; diff --git a/kernel/src/Drivers/PS2/PS2Controller.hpp b/kernel/src/Drivers/PS2/PS2Controller.hpp index 3cf48fe..1200ab5 100644 --- a/kernel/src/Drivers/PS2/PS2Controller.hpp +++ b/kernel/src/Drivers/PS2/PS2Controller.hpp @@ -43,6 +43,11 @@ namespace Drivers::PS2 { void Initialize(); + // Lightweight re-enable after S3 resume. Skips the full self-test + // and port-test sequence (which can reset attached devices); just + // re-enables ports and interrupts so keyboard/mouse work again. + void Reinitialize(); + void SendCommand(uint8_t command); void SendData(uint8_t data); uint8_t ReadData(); diff --git a/kernel/src/Hal/Apic/Apic.cpp b/kernel/src/Hal/Apic/Apic.cpp index 8b44033..d731e47 100644 --- a/kernel/src/Hal/Apic/Apic.cpp +++ b/kernel/src/Hal/Apic/Apic.cpp @@ -64,6 +64,30 @@ namespace Hal { << " max LVT=" << base::dec << (uint64_t)((version >> 16) & 0xFF); } + void Reinitialize() { + if (!g_apicBase) return; + + // Re-assert the APIC Global Enable in the IA32_APIC_BASE MSR. + // Some firmware clears bit 11 during S3 resume. If the global + // enable is off, all subsequent MMIO register writes (SVR, TPR, + // timer LVT, etc.) are silently ignored - meaning no interrupts + // are delivered, no timer fires, and the system appears frozen. + uint64_t msrValue = ReadMSR(MSR_APIC_BASE); + if (!(msrValue & (1ULL << 11))) { + msrValue |= (1ULL << 11); + WriteMSR(MSR_APIC_BASE, msrValue); + } + + // Re-enable APIC software enable bit in SVR + set spurious vector + uint32_t svr = ReadRegister(REG_SPURIOUS); + svr |= (1 << 8); + svr = (svr & 0xFFFFFF00) | SPURIOUS_VECTOR; + WriteRegister(REG_SPURIOUS, svr); + + // Accept all interrupts + WriteRegister(REG_TPR, 0); + } + void SendEOI() { WriteRegister(REG_EOI, 0); } diff --git a/kernel/src/Hal/Apic/Apic.hpp b/kernel/src/Hal/Apic/Apic.hpp index 72086eb..e05bd13 100644 --- a/kernel/src/Hal/Apic/Apic.hpp +++ b/kernel/src/Hal/Apic/Apic.hpp @@ -32,6 +32,12 @@ namespace Hal { constexpr uint32_t MSR_APIC_BASE = 0x1B; void Initialize(uint64_t apicBasePhys); + + // Re-enable the Local APIC after S3 resume. + // The MMIO mapping and base address survive (they're in page tables / RAM). + // Only the SVR and TPR hardware registers need reprogramming. + void Reinitialize(); + void SendEOI(); uint32_t GetId(); diff --git a/kernel/src/Hal/Apic/IoApic.cpp b/kernel/src/Hal/Apic/IoApic.cpp index a31dbc4..26c6cd8 100644 --- a/kernel/src/Hal/Apic/IoApic.cpp +++ b/kernel/src/Hal/Apic/IoApic.cpp @@ -21,6 +21,11 @@ namespace Hal { static MADT::InterruptSourceOverride g_overrides[MADT::ParsedMADT::MaxOverrides]; static int g_overrideCount = 0; + // Shadow copy of redirection entries for S3 resume replay + static constexpr uint32_t MAX_REDIR_ENTRIES = 24; + static uint64_t g_savedRedirEntries[MAX_REDIR_ENTRIES]; + static uint32_t g_savedEntryCount = 0; + static uint32_t ReadRegister(uint32_t reg) { // Write the register index to IOREGSEL (offset 0x00) g_ioApicBase[0] = reg; @@ -39,6 +44,13 @@ namespace Hal { WriteRegister(regHigh, (uint32_t)(entry >> 32)); WriteRegister(regLow, (uint32_t)(entry & 0xFFFFFFFF)); + + // Shadow the entry for S3 resume replay + if (index < MAX_REDIR_ENTRIES) { + g_savedRedirEntries[index] = entry; + if (index >= g_savedEntryCount) + g_savedEntryCount = index + 1; + } } uint64_t GetRedirectionEntry(uint8_t index) { @@ -106,6 +118,24 @@ namespace Hal { << " -> APIC " << (uint64_t)destinationApicId; } + void Reinitialize() { + if (!g_ioApicBase || g_savedEntryCount == 0) return; + + // Replay all saved redirection entries into the I/O APIC hardware. + // The hardware registers are lost during S3 but our shadow copy in + // RAM survives. + for (uint32_t i = 0; i < g_savedEntryCount; i++) { + uint32_t regLow = IOREDTBL_BASE + (i * 2); + uint32_t regHigh = IOREDTBL_BASE + (i * 2) + 1; + + WriteRegister(regHigh, (uint32_t)(g_savedRedirEntries[i] >> 32)); + WriteRegister(regLow, (uint32_t)(g_savedRedirEntries[i] & 0xFFFFFFFF)); + } + + KernelLogStream(DEBUG, "IOAPIC") << "Restored " << base::dec + << (uint64_t)g_savedEntryCount << " redirection entries after S3 resume"; + } + void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase, MADT::InterruptSourceOverride* overrides, int overrideCount) { g_ioApicBase = (volatile uint32_t*)Memory::HHDM(ioApicBasePhys); diff --git a/kernel/src/Hal/Apic/IoApic.hpp b/kernel/src/Hal/Apic/IoApic.hpp index f6a96e5..54910d5 100644 --- a/kernel/src/Hal/Apic/IoApic.hpp +++ b/kernel/src/Hal/Apic/IoApic.hpp @@ -29,6 +29,11 @@ namespace Hal { void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase, MADT::InterruptSourceOverride* overrides, int overrideCount); + // Re-apply all I/O APIC redirection entries after S3 resume. + // The I/O APIC hardware state is lost during S3; the saved routing + // table (in RAM) is replayed into the redirection registers. + void Reinitialize(); + void SetRedirectionEntry(uint8_t irq, uint64_t entry); uint64_t GetRedirectionEntry(uint8_t irq); diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index b09ac45..108b6e3 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -136,6 +136,23 @@ namespace Timekeeping { << " Hz periodic, initial count=" << (uint64_t)initialCount; } + void ApicTimerReinitialize() { + if (g_ticksPerMs == 0) { + KernelLogStream(ERROR, "Timer") << "Cannot reinit APIC timer - not calibrated"; + return; + } + + // Reprogram the APIC timer registers (they were lost during S3). + // The calibrated g_ticksPerMs value is still valid (it's in RAM). + // The IRQ handler registration also survives (it's a function pointer array in RAM). + uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC; + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16); + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt); + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs); + + KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume"; + } + uint64_t GetTicks() { return g_tickCount; } diff --git a/kernel/src/Timekeeping/ApicTimer.hpp b/kernel/src/Timekeeping/ApicTimer.hpp index 94242fd..c6a291f 100644 --- a/kernel/src/Timekeeping/ApicTimer.hpp +++ b/kernel/src/Timekeeping/ApicTimer.hpp @@ -11,6 +11,11 @@ namespace Timekeeping { // Initialize the APIC timer: calibrate against PIT, start periodic interrupts void ApicTimerInitialize(); + // Reinitialize the APIC timer after S3 resume using the previously + // calibrated tick rate. Skips PIT calibration and IRQ registration + // (both survive in RAM). Only reprograms the timer hardware registers. + void ApicTimerReinitialize(); + // Get the monotonic tick count (increments on each timer interrupt) uint64_t GetTicks(); diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 7dc1435..585557a 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -113,6 +113,9 @@ namespace Montauk { static constexpr uint64_t SYS_BTLIST = 87; static constexpr uint64_t SYS_BTINFO = 88; + // Power management + static constexpr uint64_t SYS_SUSPEND = 89; + // Audio control commands (for SYS_AUDIOCTL) static constexpr int AUDIO_CTL_SET_VOLUME = 0; static constexpr int AUDIO_CTL_GET_VOLUME = 1; diff --git a/programs/include/gui/desktop.hpp b/programs/include/gui/desktop.hpp index 098b678..c37b892 100644 --- a/programs/include/gui/desktop.hpp +++ b/programs/include/gui/desktop.hpp @@ -99,6 +99,7 @@ struct DesktopState { SvgIcon icon_settings; SvgIcon icon_reboot; SvgIcon icon_shutdown; + SvgIcon icon_sleep; SvgIcon icon_logout; SvgIcon icon_procmgr; diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 148a92b..f9325ea 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -265,6 +265,10 @@ namespace montauk { __builtin_unreachable(); } + inline int suspend() { + return (int)syscall0(Montauk::SYS_SUSPEND); + } + // Mouse inline void mouse_state(Montauk::MouseState* out) { syscall1(Montauk::SYS_MOUSESTATE, (uint64_t)out); } inline void set_mouse_bounds(int32_t maxX, int32_t maxY) { diff --git a/programs/src/desktop/apps/apps_common.hpp b/programs/src/desktop/apps/apps_common.hpp index a46199c..647bb50 100644 --- a/programs/src/desktop/apps/apps_common.hpp +++ b/programs/src/desktop/apps/apps_common.hpp @@ -172,4 +172,5 @@ void open_settings(DesktopState* ds); void open_reboot_dialog(DesktopState* ds); void open_wordprocessor(DesktopState* ds); void open_shutdown_dialog(DesktopState* ds); +void open_sleep_dialog(DesktopState* ds); void desktop_poll_external_windows(DesktopState* ds); diff --git a/programs/src/desktop/compose.cpp b/programs/src/desktop/compose.cpp index 15933c5..b868a1b 100644 --- a/programs/src/desktop/compose.cpp +++ b/programs/src/desktop/compose.cpp @@ -224,7 +224,7 @@ void gui::desktop_compose(DesktopState* ds) { if (ds->ctx_menu_open) { static constexpr int CTX_MENU_W = 180; static constexpr int CTX_ITEM_H = 36; - static constexpr int CTX_ITEM_COUNT = 5; + static constexpr int CTX_ITEM_COUNT = 6; int cmx = ds->ctx_menu_x; int cmy = ds->ctx_menu_y; int cmh = CTX_ITEM_H * CTX_ITEM_COUNT + 8; @@ -242,6 +242,7 @@ void gui::desktop_compose(DesktopState* ds) { { "Terminal", &ds->icon_terminal }, { "Files", &ds->icon_filemanager }, { "About", &ds->icon_settings }, + { "Sleep", &ds->icon_sleep }, { "Reboot", &ds->icon_reboot }, { "Shutdown", &ds->icon_shutdown }, }; diff --git a/programs/src/desktop/dialogs.cpp b/programs/src/desktop/dialogs.cpp index d234080..169c13f 100644 --- a/programs/src/desktop/dialogs.cpp +++ b/programs/src/desktop/dialogs.cpp @@ -1,6 +1,6 @@ /* * dialogs.cpp - * Reboot and shutdown confirmation dialogs + * Reboot, shutdown, and sleep confirmation dialogs * Copyright (c) 2026 Daniel Hammer */ @@ -234,3 +234,131 @@ void open_shutdown_dialog(DesktopState* ds) { win->on_key = shutdown_dialog_on_key; win->on_close = shutdown_dialog_on_close; } + +// ============================================================================ +// Sleep (S3 Suspend) Dialog +// ============================================================================ + +struct SleepDialogState { + DesktopState* ds; + int btn_w, btn_h, btn_y, sleep_x, cancel_x; + bool hover_sleep, hover_cancel; +}; + +static void sleep_dialog_on_draw(Window* win, Framebuffer& fb) { + SleepDialogState* ss = (SleepDialogState*)win->app_data; + if (!ss) return; + + Canvas c(win); + c.fill(colors::WINDOW_BG); + + const char* msg = "Suspend the system?"; + int tw = text_width(msg); + c.text((c.w - tw) / 2, 30, msg, colors::TEXT_COLOR); + + int btn_w = 100; + int btn_h = 32; + int btn_y = c.h - btn_h - 20; + int gap = 20; + int total_w = btn_w * 2 + gap; + int bx = (c.w - total_w) / 2; + ss->btn_w = btn_w; + ss->btn_h = btn_h; + ss->btn_y = btn_y; + ss->sleep_x = bx; + ss->cancel_x = bx + btn_w + gap; + + Color sleep_bg = ss->hover_sleep + ? Color::from_rgb(0x44, 0x77, 0xDD) + : Color::from_rgb(0x33, 0x66, 0xCC); + c.button(ss->sleep_x, btn_y, btn_w, btn_h, "Sleep", sleep_bg, colors::WHITE, 4); + + Color cancel_bg = ss->hover_cancel + ? Color::from_rgb(0x99, 0x99, 0x99) + : Color::from_rgb(0x88, 0x88, 0x88); + c.button(ss->cancel_x, btn_y, btn_w, btn_h, "Cancel", cancel_bg, colors::WHITE, 4); +} + +static void sleep_dialog_on_mouse(Window* win, MouseEvent& ev) { + SleepDialogState* ss = (SleepDialogState*)win->app_data; + if (!ss) return; + + Rect cr = win->content_rect(); + int lx = ev.x - cr.x; + int ly = ev.y - cr.y; + + Rect sb = {ss->sleep_x, ss->btn_y, ss->btn_w, ss->btn_h}; + Rect cb = {ss->cancel_x, ss->btn_y, ss->btn_w, ss->btn_h}; + ss->hover_sleep = sb.contains(lx, ly); + ss->hover_cancel = cb.contains(lx, ly); + + if (ev.left_pressed()) { + if (ss->hover_sleep) { + montauk::suspend(); + // If suspend returns (resume from S3), close the dialog + for (int i = 0; i < ss->ds->window_count; i++) { + if (ss->ds->windows[i].app_data == ss) { + desktop_close_window(ss->ds, i); + return; + } + } + } + if (ss->hover_cancel) { + for (int i = 0; i < ss->ds->window_count; i++) { + if (ss->ds->windows[i].app_data == ss) { + desktop_close_window(ss->ds, i); + return; + } + } + } + } +} + +static void sleep_dialog_on_key(Window* win, const Montauk::KeyEvent& key) { + SleepDialogState* ss = (SleepDialogState*)win->app_data; + if (!ss || !key.pressed) return; + + if (key.ascii == '\n' || key.ascii == '\r') { + montauk::suspend(); + // If suspend returns, close the dialog + for (int i = 0; i < ss->ds->window_count; i++) { + if (ss->ds->windows[i].app_data == ss) { + desktop_close_window(ss->ds, i); + return; + } + } + } + if (key.scancode == 0x01) { // Escape + for (int i = 0; i < ss->ds->window_count; i++) { + if (ss->ds->windows[i].app_data == ss) { + desktop_close_window(ss->ds, i); + return; + } + } + } +} + +static void sleep_dialog_on_close(Window* win) { + if (win->app_data) { + montauk::mfree(win->app_data); + win->app_data = nullptr; + } +} + +void open_sleep_dialog(DesktopState* ds) { + int wx = (ds->screen_w - 300) / 2; + int wy = (ds->screen_h - 150) / 2; + int idx = desktop_create_window(ds, "Sleep", wx, wy, 300, 150); + if (idx < 0) return; + + Window* win = &ds->windows[idx]; + SleepDialogState* ss = (SleepDialogState*)montauk::malloc(sizeof(SleepDialogState)); + montauk::memset(ss, 0, sizeof(SleepDialogState)); + ss->ds = ds; + + win->app_data = ss; + win->on_draw = sleep_dialog_on_draw; + win->on_mouse = sleep_dialog_on_mouse; + win->on_key = sleep_dialog_on_key; + win->on_close = sleep_dialog_on_close; +} diff --git a/programs/src/desktop/input.cpp b/programs/src/desktop/input.cpp index 197d070..eee0f06 100644 --- a/programs/src/desktop/input.cpp +++ b/programs/src/desktop/input.cpp @@ -153,7 +153,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) { if (left_pressed) { static constexpr int CTX_MENU_W = 180; static constexpr int CTX_ITEM_H = 36; - static constexpr int CTX_ITEM_COUNT = 5; + static constexpr int CTX_ITEM_COUNT = 6; int cmx = ds->ctx_menu_x; int cmy = ds->ctx_menu_y; int cmh = CTX_ITEM_H * CTX_ITEM_COUNT + 8; @@ -170,8 +170,9 @@ void gui::desktop_handle_mouse(DesktopState* ds) { case 0: open_terminal(ds); break; case 1: open_filemanager(ds); break; case 2: open_settings(ds); break; - case 3: open_reboot_dialog(ds); break; - case 4: open_shutdown_dialog(ds); break; + case 3: open_sleep_dialog(ds); break; + case 4: open_reboot_dialog(ds); break; + case 5: open_shutdown_dialog(ds); break; } return; } @@ -357,6 +358,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) { case 15: open_wordprocessor(ds); break; case 16: montauk::exit(0); break; // Log Out case 17: lock_screen(ds); break; // Lock Screen + case 18: open_sleep_dialog(ds); break; // Sleep } } ds->app_menu_open = false; diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 663115d..3e3b99c 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -173,6 +173,7 @@ void desktop_build_menu(DesktopState* ds) { menu_add_embedded("Settings", 11, &ds->icon_settings); menu_add_embedded("Lock Screen", 17, &ds->icon_lock); menu_add_embedded("Log Out", 16, &ds->icon_logout); + menu_add_embedded("Sleep", 18, &ds->icon_sleep); menu_add_embedded("Reboot", 12, &ds->icon_reboot); menu_add_embedded("Shutdown", 14, &ds->icon_shutdown); } @@ -228,6 +229,7 @@ void gui::desktop_init(DesktopState* ds) { ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor); ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor); ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor); + ds->icon_sleep = svg_load("0:/icons/sleep.svg", 20, 20, defColor); ds->icon_logout = svg_load("0:/icons/gnome-logout.svg", 20, 20, defColor); ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor); @@ -508,8 +510,8 @@ void gui::desktop_run(DesktopState* ds) { desktop_compose(ds); ds->fb.flip(); - // Target ~60fps - montauk::sleep_ms(16); + // Yield to scheduler without artificial frame cap + montauk::sleep_ms(1); } } diff --git a/scripts/copy_icons.sh b/scripts/copy_icons.sh index 3a7db59..74ed889 100755 --- a/scripts/copy_icons.sh +++ b/scripts/copy_icons.sh @@ -94,6 +94,7 @@ ICONS=( "status/symbolic/audio-volume-high-symbolic.svg" "apps/scalable/gnome-logout.svg" "apps/scalable/lock.svg" + "apps/scalable/sleep.svg" ) copied=0