From 24f88ec4c6fc6d82ad8c11f254b28ae74845313c Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Thu, 1 Oct 2026 13:09:26 +0200 Subject: [PATCH] feat: power down dGPUs on hybrid graphics laptops --- kernel/src/ACPI/AML/AmlInterpreter.cpp | 3726 +++++++++++++++------ kernel/src/ACPI/AML/AmlInterpreter.hpp | 234 +- kernel/src/ACPI/AML/AmlNamespace.cpp | 10 + kernel/src/ACPI/AML/AmlNamespace.hpp | 69 +- kernel/src/ACPI/AML/AmlParser.cpp | 133 +- kernel/src/ACPI/AML/AmlParser.hpp | 16 +- kernel/src/ACPI/AcpiDevices.cpp | 15 +- kernel/src/ACPI/AcpiPower.cpp | 336 ++ kernel/src/ACPI/AcpiPower.hpp | 92 + kernel/src/ACPI/AcpiShutdown.cpp | 2 +- kernel/src/ACPI/AcpiSleep.cpp | 9 +- kernel/src/ACPI/CpuIdle.cpp | 25 +- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Boot/Main.cpp | 11 + kernel/src/Drivers/Graphics/DgpuPower.cpp | 560 ++++ kernel/src/Drivers/Graphics/DgpuPower.hpp | 33 + kernel/src/Drivers/Init.cpp | 5 + kernel/src/Drivers/Init.hpp | 5 + kernel/src/Memory/Paging.cpp | 4 + kernel/src/Pci/Pci.cpp | 94 +- kernel/src/Pci/Pci.hpp | 37 + kernel/src/Timekeeping/ApicTimer.cpp | 29 + kernel/src/Timekeeping/ApicTimer.hpp | 8 + 23 files changed, 4325 insertions(+), 1130 deletions(-) create mode 100644 kernel/src/ACPI/AcpiPower.cpp create mode 100644 kernel/src/ACPI/AcpiPower.hpp create mode 100644 kernel/src/Drivers/Graphics/DgpuPower.cpp create mode 100644 kernel/src/Drivers/Graphics/DgpuPower.hpp diff --git a/kernel/src/ACPI/AML/AmlInterpreter.cpp b/kernel/src/ACPI/AML/AmlInterpreter.cpp index 4641c6d..e88ec55 100644 --- a/kernel/src/ACPI/AML/AmlInterpreter.cpp +++ b/kernel/src/ACPI/AML/AmlInterpreter.cpp @@ -7,6 +7,8 @@ #include "AmlInterpreter.hpp" #include #include +#include +#include #include #include #include @@ -14,6 +16,7 @@ #include #include #include +#include using namespace Kt; @@ -40,8 +43,46 @@ namespace Hal { return (c >= 'A' && c <= 'Z') || c == '_'; } - static bool IsNameChar(uint8_t c) { - return IsLeadNameChar(c) || (c >= '0' && c <= '9'); + + // First byte of a NameString in any of its forms. + static bool IsNameStart(uint8_t c) { + return IsLeadNameChar(c) || c == '\\' || c == '^' || + c == DualNamePrefix || c == MultiNamePrefix; + } + + static bool IsLocalOp(uint8_t c) { return c >= LocalPrefix && c <= LocalPrefix + 7; } + static bool IsArgOp(uint8_t c) { return c >= ArgPrefix && c <= ArgPrefix + 6; } + + static bool IsDataType(ObjectType t) { + return t == ObjectType::Integer || t == ObjectType::String || + t == ObjectType::Buffer || t == ObjectType::Package; + } + + static Object MakeInteger(uint64_t value) { + Object o{}; + o.Type = ObjectType::Integer; + o.Integer = value; + return o; + } + + static uint32_t MinU32(uint32_t a, uint32_t b) { return a < b ? a : b; } + + // ============================================================================ + + // One-shot diagnostics + + // ============================================================================ + // Firmware can hit the same unsupported construct on every poll of a + // battery, so each distinct complaint is logged once. + static bool FirstTime(uint32_t key) { + static constexpr int Slots = 64; + static uint32_t seen[Slots]; + static int count = 0; + for (int i = 0; i < count; i++) + if (seen[i] == key) return false; + if (count >= Slots) return false; // stop talking altogether + seen[count++] = key; + return true; } // The interpreter runs on whatever stack its caller has -- the boot @@ -75,6 +116,28 @@ namespace Hal { } } + bool Interpreter::Abort(ExecContext& ctx, uint32_t pos, const char* why) { + ctx.Aborted = true; + uint8_t op = (pos < ctx.AmlBase + ctx.AmlLength) ? ctx.Aml[pos] : 0; + uint8_t next = (pos + 1 < ctx.AmlBase + ctx.AmlLength) ? ctx.Aml[pos + 1] : 0; + if (FirstTime(0x10000u | ((uint32_t)op << 8) | (op == ExtOpPrefix ? next : 0))) { + char path[128]; + m_ns.GetNodePath(ctx.ScopeNode, path, sizeof(path)); + KernelLogStream(WARNING, "AML") << why << ": op " << base::hex + << (uint64_t)op; + if (op == ExtOpPrefix) + KernelLogStream(WARNING, "AML") << " ext op " << base::hex << (uint64_t)next; + KernelLogStream(WARNING, "AML") << " in " << path << " at offset " + << base::hex << (uint64_t)pos << "; method abandoned"; + } + return false; + } + + // ============================================================================ + + // SystemMemory access + + // ============================================================================ // True when [address, address+bytes) is inside the contiguous HHDM // mapping and so safe to touch at phys+hhdmBase. static bool HhdmReadable(uint64_t address, uint32_t bytes) { @@ -84,15 +147,273 @@ namespace Hal { return (limit - address) >= bytes; } + enum class MemClass { Ram, Firmware, Mmio }; + + // What the boot memory map says is at a physical address. Anything it + // does not list is a hole, and holes are device space. + static MemClass ClassifyPhys(uint64_t address) { + const auto& map = montauk::boot::Info().memoryMap; + for (uint64_t i = 0; i < map.count; i++) { + const auto& r = map.regions[i]; + if (address < r.base || address - r.base >= r.length) continue; + switch (r.kind) { + case montauk::boot::MemoryKind::AcpiReclaimable: + case montauk::boot::MemoryKind::AcpiNvs: + return MemClass::Firmware; + case montauk::boot::MemoryKind::Reserved: + case montauk::boot::MemoryKind::Unknown: + return MemClass::Mmio; + default: + return MemClass::Ram; + } + } + return MemClass::Mmio; + } + + // Device registers have to be reached through an uncached mapping. + // The HHDM maps the low MMIO hole write-back (MTRRs usually rescue + // that, but nothing guarantees it) and does not map anything above + // the RAM bound at all, which is where a GPIO community or a PCIe + // root port's MMIO config lands on most machines. Pages are remapped + // UC in place at their HHDM address, so there is never a second, + // differently-cached alias. + static bool EnsureUncached(uint64_t address, uint32_t bytes) { + if (!Memory::VMM::g_paging) return false; + + static constexpr int CacheSize = 64; + static uint64_t s_pages[CacheSize]; + static int s_count = 0; + static int s_next = 0; + + uint64_t first = address & ~0xFFFULL; + uint64_t last = (address + bytes - 1) & ~0xFFFULL; + for (uint64_t page = first; page <= last; page += 0x1000) { + bool known = false; + for (int i = 0; i < s_count; i++) + if (s_pages[i] == page) { known = true; break; } + if (known) continue; + + Memory::VMM::g_paging->MapMMIO(page, Memory::HHDM(page)); + + if (s_count < CacheSize) s_pages[s_count++] = page; + else { s_pages[s_next] = page; s_next = (s_next + 1) % CacheSize; } + } + return true; + } + + static bool MemoryIo(uint64_t address, uint32_t width, bool write, uint64_t& value) { + if (width == 0 || width > 8) return false; + if (address >= (1ULL << 52) || address + width < address) return false; + + MemClass cls = ClassifyPhys(address); + if (cls == MemClass::Ram) { + // AML has no business in memory the OS owns. A region whose + // base we computed wrong -- or that firmware computed from a + // variable we never set -- lands exactly here, and writing it + // would corrupt the kernel. Reading is harmless. + if (write) { + if (FirstTime(0x20000u ^ (uint32_t)(address >> 12))) { + KernelLogStream(WARNING, "AML") << "Refused AML write to RAM at " + << base::hex << address; + } + return false; + } + if (!HhdmReadable(address, width)) return false; + } else if (cls == MemClass::Firmware && HhdmReadable(address, width)) { + // ACPI NVS / tables: ordinary RAM, already mapped. + } else { + if (!EnsureUncached(address, width)) return false; + } + + volatile uint8_t* p = (volatile uint8_t*)Memory::HHDM(address); + if (write) { + switch (width) { + case 1: *p = (uint8_t)value; break; + case 2: *(volatile uint16_t*)p = (uint16_t)value; break; + case 4: *(volatile uint32_t*)p = (uint32_t)value; break; + case 8: *(volatile uint64_t*)p = value; break; + default: return false; + } + } else { + switch (width) { + case 1: value = *p; break; + case 2: value = *(volatile uint16_t*)p; break; + case 4: value = *(volatile uint32_t*)p; break; + case 8: value = *(volatile uint64_t*)p; break; + default: return false; + } + } + return true; + } + + // ============================================================================ + + // Data conversion + + // ============================================================================ + static uint64_t ParseIntegerString(const char* s, uint32_t len, bool decimalDefault) { + uint32_t i = 0; + while (i < len && (s[i] == ' ' || s[i] == '\t')) i++; + bool hex = !decimalDefault; + if (i + 1 < len && s[i] == '0' && (s[i + 1] == 'x' || s[i + 1] == 'X')) { + hex = true; + i += 2; + } + uint64_t v = 0; + for (; i < len; i++) { + char c = s[i]; + uint64_t d; + if (c >= '0' && c <= '9') d = (uint64_t)(c - '0'); + else if (hex && c >= 'a' && c <= 'f') d = (uint64_t)(c - 'a' + 10); + else if (hex && c >= 'A' && c <= 'F') d = (uint64_t)(c - 'A' + 10); + else break; + v = v * (hex ? 16 : 10) + d; + } + return v; + } + + // Implicit operand conversion to Integer (buffers are little-endian, + // strings are hex). + static uint64_t ToInt(const Object& o) { + switch (o.Type) { + case ObjectType::Integer: + return o.Integer; + case ObjectType::Buffer: { + uint64_t v = 0; + uint32_t n = MinU32(o.Buffer.Length, 8); + for (uint32_t i = 0; i < n; i++) + v |= (uint64_t)o.Buffer.Data[i] << (8 * i); + return v; + } + case ObjectType::String: + return ParseIntegerString(o.String.Data, o.String.Length, false); + default: + return 0; + } + } + + // The bytes of an object as a buffer: integers little-endian, strings + // without their terminator. Returns the length written. + static uint32_t ObjectBytes(const Object& o, uint8_t* out, uint32_t max) { + switch (o.Type) { + case ObjectType::Integer: { + uint32_t n = MinU32(8, max); + for (uint32_t i = 0; i < n; i++) + out[i] = (uint8_t)(o.Integer >> (8 * i)); + return n; + } + case ObjectType::Buffer: { + uint32_t n = MinU32(o.Buffer.Length, max); + memcpy(out, o.Buffer.Data, n); + return n; + } + case ObjectType::String: { + uint32_t n = MinU32(o.String.Length, max); + memcpy(out, o.String.Data, n); + return n; + } + default: + return 0; + } + } + + static void AppendHex(char* out, uint32_t& n, uint32_t max, uint64_t v, int digits) { + static const char* hexd = "0123456789ABCDEF"; + for (int i = digits - 1; i >= 0 && n < max; i--) + out[n++] = hexd[(v >> (i * 4)) & 0xF]; + } + + static void AppendDecimal(char* out, uint32_t& n, uint32_t max, uint64_t v) { + char tmp[21]; + int t = 0; + do { tmp[t++] = (char)('0' + v % 10); v /= 10; } while (v); + while (t > 0 && n < max) out[n++] = tmp[--t]; + } + + static void SetString(Object& o, const char* s, uint32_t len) { + o = Object{}; + o.Type = ObjectType::String; + if (len > MaxStringLen - 1) len = MaxStringLen - 1; + memcpy(o.String.Data, s, len); + o.String.Data[len] = '\0'; + o.String.Length = (uint16_t)len; + } + + static void SetBuffer(Object& o, const uint8_t* data, uint32_t len) { + o = Object{}; + o.Type = ObjectType::Buffer; + if (len > MaxBufferLen) len = MaxBufferLen; + if (len) memcpy(o.Buffer.Data, data, len); + o.Buffer.Length = len; + } + + // Implicit conversion to String for Concatenate and comparisons. + static void ToStringObject(const Object& in, Object& out) { + char buf[MaxStringLen]; + uint32_t n = 0; + if (in.Type == ObjectType::String) { out = in; return; } + if (in.Type == ObjectType::Integer) { + AppendHex(buf, n, MaxStringLen - 1, in.Integer, 16); + } else if (in.Type == ObjectType::Buffer) { + for (uint32_t i = 0; i < in.Buffer.Length && n + 3 < MaxStringLen - 1; i++) { + if (i) buf[n++] = ' '; + AppendHex(buf, n, MaxStringLen - 1, in.Buffer.Data[i], 2); + } + } + SetString(out, buf, n); + } + + // ACPI comparison: the second operand converts to the type of the first. + static int CompareObjects(const Object& a, const Object& b) { + if (a.Type == ObjectType::String || a.Type == ObjectType::Buffer) { + uint8_t lhs[MaxBufferLen], rhs[MaxBufferLen]; + uint32_t la = ObjectBytes(a, lhs, MaxBufferLen); + uint32_t lb; + if (a.Type == ObjectType::String && b.Type != ObjectType::String) { + Object s{}; + ToStringObject(b, s); + lb = ObjectBytes(s, rhs, MaxBufferLen); + } else { + lb = ObjectBytes(b, rhs, MaxBufferLen); + } + uint32_t n = MinU32(la, lb); + for (uint32_t i = 0; i < n; i++) + if (lhs[i] != rhs[i]) return lhs[i] < rhs[i] ? -1 : 1; + if (la == lb) return 0; + return la < lb ? -1 : 1; + } + uint64_t x = ToInt(a), y = ToInt(b); + if (x == y) return 0; + return x < y ? -1 : 1; + } + + // Bit copy between little-endian bit arrays. + static uint64_t GetBits(const uint8_t* src, uint32_t bitPos, uint32_t count) { + uint64_t v = 0; + for (uint32_t i = 0; i < count; i++) { + uint32_t b = bitPos + i; + if (src[b / 8] & (1u << (b % 8))) v |= 1ULL << i; + } + return v; + } + + static void PutBits(uint8_t* dst, uint32_t bitPos, uint32_t count, uint64_t v) { + for (uint32_t i = 0; i < count; i++) { + uint32_t b = bitPos + i; + if (v & (1ULL << i)) dst[b / 8] |= (uint8_t)(1u << (b % 8)); + else dst[b / 8] &= (uint8_t)~(1u << (b % 8)); + } + } + // ============================================================================ // Constructor // ============================================================================ Interpreter::Interpreter() - : m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false), + : m_initialized(false), m_predefined(false), m_dsdtNodeCount(0), m_ctxPool(nullptr), m_ctxInUse(0), m_evalNesting(0), m_stackFloor(0), - m_traceBudget(0) {} + m_traceBudget(0), m_loadCtx(nullptr), m_delayedUs(0), m_methodScan(0) {} // ============================================================================ @@ -117,12 +438,49 @@ namespace Hal { if (m_ctxInUse > 0) m_ctxInUse--; } + // Every entry point from the rest of the kernel comes through here. + // Packages built inside the method come out of the transient ring. + // Rewinding it here bounds the store to one evaluation's worth, + // which is what makes repeated polling (_BST every few seconds) + // safe. The caller must read the returned package before the next + // evaluation, which every caller in the kernel does. + // Evaluation is serialized: the execution contexts come from a + // shared pool, and the battery indicator polls from a user thread + // while other subsystems may evaluate AML of their own. + template + bool Interpreter::TopLevel(Fn body) { + m_evalLock.Acquire(); + bool topLevel = (m_evalNesting == 0); + if (topLevel) { + PackageStore::ResetTransient(); + + // Anchor the stack budget to this frame. Everything below + // measures against it, so the same limit works whether we were + // called on the large boot stack or a thread's 16 KiB one. + char stackMarker = 0; + m_stackFloor = (uint64_t)&stackMarker - StackBudget; + } + m_evalNesting++; + + bool ok = body(); + + m_evalNesting--; + + // The floor refers to a frame on this call's stack. Leaving it set + // would have the next evaluation -- possibly on a different thread's + // stack entirely -- compare against an address that no longer means + // anything, and bail out for no reason. + if (topLevel) m_stackFloor = 0; + m_evalLock.Release(); + return ok; + } + // ============================================================================ // PkgLength decoding // ============================================================================ - uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) { + static uint32_t PkgLength(const uint8_t* aml, uint32_t* pos) { uint8_t lead = aml[*pos]; uint32_t byteCount = (lead >> 6) & 0x03; @@ -142,12 +500,16 @@ namespace Hal { return length; } + uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) { + return PkgLength(aml, pos); + } + // ============================================================================ // Integer decoding (data objects) // ============================================================================ - uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* pos) { + static uint64_t ConstInteger(const uint8_t* aml, uint32_t* pos) { uint8_t op = aml[*pos]; switch (op) { @@ -195,6 +557,10 @@ namespace Hal { } } + uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* pos) { + return ConstInteger(aml, pos); + } + // ============================================================================ // NameSeg reading @@ -208,6 +574,37 @@ namespace Hal { *pos += 4; } + // Copy a NameString exactly as written ("\_SB_.PCI0", "^^PG00", + // "PG00") and advance past it. Used where the scope the name should be + // resolved in is not known yet. + static void DecodeNameText(const uint8_t* data, uint32_t length, uint32_t* pos, + char* out, int maxLen) { + int n = 0; + uint32_t p = *pos; + while (p < length && (data[p] == '\\' || data[p] == '^')) { + if (n < maxLen - 1) out[n++] = (char)data[p]; + p++; + } + + int segs = 0; + if (p < length && data[p] == DualNamePrefix) { segs = 2; p++; } + else if (p < length && data[p] == MultiNamePrefix) { + segs = (p + 1 < length) ? data[p + 1] : 0; + p += 2; + } else if (p < length && data[p] == 0x00) { segs = 0; p++; } + else segs = 1; + + for (int s = 0; s < segs && p + 4 <= length; s++) { + if (s > 0 && n < maxLen - 1) out[n++] = '.'; + for (int j = 0; j < 4; j++) { + if (n < maxLen - 1) out[n++] = (char)data[p]; + p++; + } + } + out[n] = '\0'; + *pos = (p > length) ? length : p; + } + // ============================================================================ // NameString reading @@ -250,10 +647,13 @@ namespace Hal { ReadNameSeg(aml, pos, segments[1]); } else if (aml[*pos] == MultiNamePrefix) { (*pos)++; - segCount = aml[*pos]; + int declared = aml[*pos]; (*pos)++; - for (int i = 0; i < segCount && i < MaxPathDepth; i++) { - ReadNameSeg(aml, pos, segments[i]); + segCount = declared < MaxPathDepth ? declared : MaxPathDepth; + for (int i = 0; i < declared; i++) { + char seg[5]; + ReadNameSeg(aml, pos, seg); + if (i < MaxPathDepth) memcpy(segments[i], seg, 5); } } else if (IsLeadNameChar(aml[*pos])) { segCount = 1; @@ -309,6 +709,100 @@ namespace Hal { return pathPos; } + int32_t Interpreter::ReadAndResolveName(const uint8_t* aml, uint32_t* pos, + int32_t scopeNode) { + char path[256]; + char relative[64]; + bool searchable = false; + ReadNameString(aml, pos, scopeNode, path, 256, + relative, sizeof(relative), &searchable); + int32_t node = m_ns.FindNode(path); + if (node < 0 && searchable) + node = m_ns.SearchUpward(relative, scopeNode); + return node; + } + + // ============================================================================ + + // Predefined objects + + // ============================================================================ + void Interpreter::InstallPredefined() { + if (m_predefined) return; + m_predefined = true; + + // Standard predefined scopes + m_ns.CreateNode("\\_GPE"); + m_ns.CreateNode("\\_PR_"); + m_ns.CreateNode("\\_SB_"); + m_ns.CreateNode("\\_SI_"); + m_ns.CreateNode("\\_TZ_"); + + // \_OSI is how firmware asks which OS it is running under, and the + // answer picks its code paths: the ones it was tested on are the + // ones it takes for current Windows. Without it every _OSI() call + // also failed to consume its argument. + int32_t osi = m_ns.CreateNode("\\_OSI"); + if (auto* n = m_ns.GetNode(osi)) { + n->Obj = Object{}; + n->Obj.Type = ObjectType::Method; + n->Obj.Method.ArgCount = 1; + n->Obj.Method.Native = (uint8_t)NativeMethod::Osi; + } + + // _REV 2 is what Windows (and Linux) report; firmware keys + // "is this Linux" quirks off anything else. + if (auto* n = m_ns.GetNode(m_ns.CreateNode("\\_REV"))) + n->Obj = MakeInteger(2); + + if (auto* n = m_ns.GetNode(m_ns.CreateNode("\\_OS_"))) + SetString(n->Obj, "Microsoft Windows NT", 20); + + if (auto* n = m_ns.GetNode(m_ns.CreateNode("\\_GL_"))) { + n->Obj = Object{}; + n->Obj.Type = ObjectType::Mutex; + } + } + + bool Interpreter::RunNativeMethod(uint8_t which, const Object* args, int argCount, + Object& result) { + result = MakeInteger(0); + + if (which == (uint8_t)NativeMethod::Osi) { + // The interfaces Linux answers yes to: every Windows release + // plus the feature strings. "Linux" and "Darwin" stay no, as + // firmware uses them to select untested paths. + static const char* const supported[] = { + "Windows 2000", "Windows 2001", "Windows 2001 SP1", + "Windows 2001.1", "Windows 2001 SP2", "Windows 2001.1 SP1", + "Windows 2006", "Windows 2006.1", "Windows 2006 SP1", + "Windows 2006 SP2", "Windows 2009", "Windows 2012", + "Windows 2013", "Windows 2015", "Windows 2016", + "Windows 2017", "Windows 2017.2", "Windows 2018", + "Windows 2018.2", "Windows 2019", "Windows 2020", + "Windows 2021", "Windows 2022", + "Module Device", "Processor Device", "3.0 Thermal Model", + "3.0 _SCP Extensions", "Processor Aggregator Device", + "Extended Address Space Descriptor", + }; + + if (argCount < 1 || args[0].Type != ObjectType::String) return true; + const Object& q = args[0]; + + bool yes = false; + for (const char* s : supported) { + uint32_t len = 0; + while (s[len]) len++; + if (len != q.String.Length) continue; + if (memcmp(s, q.String.Data, len) == 0) { yes = true; break; } + } + if (yes) result.Integer = ~0ULL; + return true; + } + + return false; + } + // ============================================================================ // LoadTable @@ -324,22 +818,40 @@ namespace Hal { char sig[5] = {}; memcpy(sig, header->Signature, 4); + char oemTable[9] = {}; + memcpy(oemTable, header->OEMTableID, 8); - m_dsdt = (const uint8_t*)tableData; - m_dsdtLength = header->Length; + const uint8_t* aml = (const uint8_t*)tableData; + uint32_t length = header->Length; uint32_t dataStart = sizeof(ACPI::CommonSDTHeader); - KernelLogStream(OK, "AML") << "Loading " << sig << " table (" - << base::dec << (uint64_t)(m_dsdtLength - dataStart) << " bytes of AML)"; + KernelLogStream(OK, "AML") << "Loading " << sig << " \"" << oemTable << "\" (" + << base::dec << (uint64_t)(length - 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_"); + InstallPredefined(); - bool result = ParseBlock(m_dsdt, dataStart, m_dsdtLength, m_ns.RootIndex()); + bool result = TopLevel([&]() { + // Definitions with TermArg operands (region offsets, buffer + // sizes) evaluate them in this context. + ExecContext* ctx = AcquireContext(); + if (!ctx) return false; + ctx->Aml = aml; + ctx->AmlBase = 0; + ctx->AmlLength = length; + ctx->ScopeNode = m_ns.RootIndex(); + for (int i = 0; i < MaxMethodLocals; i++) + ctx->Locals[i] = MakeInteger(0); + + m_loadCtx = ctx; + bool ok = ParseBlock(aml, dataStart, length, m_ns.RootIndex()); + m_loadCtx = nullptr; + ReleaseContext(); + return ok; + }); + + if (sig[0] == 'D' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T' && + m_dsdtNodeCount == 0) + m_dsdtNodeCount = m_ns.NodeCount(); m_initialized = true; @@ -372,12 +884,51 @@ namespace Hal { // Named object opcodes if (op == NameOp || op == ScopeOp || op == MethodOp || - op == BufferOp || op == PackageOp) { + op == BufferOp || op == PackageOp || op == VarPackageOp) { if (!ParseNamedObject(aml, &pos, endOffset, scopeNode)) return false; continue; } + // External(Name, ObjectType, ArgCount) -- a declaration only, + // but its ObjectType byte (8 for a method) is NameOp's opcode, + // so skipping it a byte at a time would define garbage. + if (op == ExternalOp) { + pos++; + char dummy[256]; + ReadNameString(aml, &pos, scopeNode, dummy, 256); + pos += 2; + continue; + } + + // Alias(Source, Alias) + if (op == AliasOp) { + pos++; + int32_t source = ReadAndResolveName(aml, &pos, scopeNode); + char path[256]; + ReadNameString(aml, &pos, scopeNode, path, 256); + int32_t alias = m_ns.CreateNode(path); + auto* src = m_ns.GetNode(source); + auto* dst = m_ns.GetNode(alias); + if (src && dst && alias != source) dst->Obj = src->Obj; + continue; + } + + // CreateXxxField at scope level, over a named buffer. + if ((op >= CreateDWordFieldOp && op <= CreateBitFieldOp) || + op == CreateQWordFieldOp) { + if (m_loadCtx && m_loadCtx->Aml == aml && !m_methodScan) { + pos++; + int32_t saved = m_loadCtx->ScopeNode; + m_loadCtx->ScopeNode = scopeNode; + CreateBufferField(*m_loadCtx, &pos, endOffset, op); + m_loadCtx->ScopeNode = saved; + continue; + } + pos++; + continue; + } + // Skip opcodes we don't handle during loading // We need to advance past them correctly if (op == BytePrefix) { pos += 2; continue; } @@ -426,8 +977,7 @@ namespace Hal { } // Name characters (part of a name reference we don't need during load) - if (IsLeadNameChar(op) || op == '\\' || op == '^' || - op == DualNamePrefix || op == MultiNamePrefix) { + if (IsNameStart(op)) { // Skip the name if (op == '\\' || op == '^') { pos++; continue; } if (op == DualNamePrefix) { pos += 9; continue; } // prefix + 2*4 @@ -442,7 +992,7 @@ namespace Hal { } // Locals and args - if ((op >= 0x60 && op <= 0x67) || (op >= 0x68 && op <= 0x6E)) { + if (IsLocalOp(op) || IsArgOp(op)) { pos++; continue; } @@ -456,11 +1006,35 @@ namespace Hal { // ============================================================================ + // Definition operands + + // ============================================================================ + bool Interpreter::EvalOperand(ExecContext* ctx, const uint8_t* aml, uint32_t* pos, + uint32_t endOffset, int32_t scopeNode, Object& result) { + // Inside a method body scanned at load, operands are the method's + // own locals and calls: evaluating them now, outside the method, + // would run firmware code early and place regions at garbage + // addresses. The definition is re-made when the method runs. + ExecContext* c = ctx ? ctx : (m_methodScan ? nullptr : m_loadCtx); + if (!c || c->Aml != aml) { + // No context to evaluate in, so constants only. + return DecodeConstElement(aml, endOffset, pos, result); + } + + int32_t saved = c->ScopeNode; + c->ScopeNode = scopeNode; + bool ok = EvalTerm(*c, pos, endOffset, result); + c->ScopeNode = saved; + return ok; + } + + // ============================================================================ + // ParseNamedObject // ============================================================================ bool Interpreter::ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, - int32_t scopeNode) { + int32_t scopeNode, ExecContext* ctx) { uint8_t op = aml[*pos]; (*pos)++; @@ -489,75 +1063,56 @@ namespace Hal { 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]; + Object value{}; - 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; + if (dataOp == PackageOp || dataOp == VarPackageOp) { + // Keep the constant form, pointing into the table. The + // elements are decoded on demand, and materialized if + // Index() ever writes one. (*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); + uint32_t count; + if (dataOp == PackageOp) { + count = aml[*pos]; + (*pos)++; + } else { + Object n{}; + EvalOperand(ctx, aml, pos, pkgEnd, scopeNode, n); + count = (uint32_t)ToInt(n); + } + value.Type = ObjectType::Package; + value.Package.Data = &aml[*pos]; + value.Package.Length = (*pos <= pkgEnd) ? pkgEnd - *pos : 0; + value.Package.Count = count; *pos = pkgEnd; + } else if (dataOp == ZeroOp || dataOp == OneOp || dataOp == OnesOp || + dataOp == BytePrefix || dataOp == WordPrefix || + dataOp == DWordPrefix || dataOp == QWordPrefix || + dataOp == StringPrefix || dataOp == BufferOp) { + EvalOperand(ctx, aml, pos, endOffset, scopeNode, value); } 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 + // Not a data object (a reference to another name, at + // most). Integer 0, as before, consuming a NameString. + value = MakeInteger(0); + if (IsNameStart(dataOp)) { + char dummy[256]; + ReadNameString(aml, pos, scopeNode, dummy, 256); + } else { + (*pos)++; } } + if (auto* nsNode = m_ns.GetNode(node)) + nsNode->Obj = value; + return true; } @@ -583,22 +1138,31 @@ namespace Hal { uint8_t flags = aml[*pos]; (*pos)++; + nsNode->Obj = Object{}; nsNode->Obj.Type = ObjectType::Method; nsNode->Obj.Method.ArgCount = flags & 0x07; nsNode->Obj.Method.Serialized = (flags >> 3) & 1; + nsNode->Obj.Method.Native = 0; + nsNode->Obj.Method.Table = aml; 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); + // Not when the method is being defined BY a running method: + // its body gets parsed when it runs. + if (!ctx) { + m_methodScan++; + ParseBlock(aml, *pos, methodEnd, node); + m_methodScan--; + } *pos = methodEnd; return true; } // Buffer/Package at top level (not as part of a Name) - if (op == BufferOp || op == PackageOp) { + if (op == BufferOp || op == PackageOp || op == VarPackageOp) { uint32_t pkgStart = *pos; uint32_t pkgLen = DecodePkgLength(aml, pos); uint32_t blockEnd = pkgStart + pkgLen; @@ -612,11 +1176,94 @@ namespace Hal { // ============================================================================ + // Field lists + + // ============================================================================ + void Interpreter::ParseFieldList(const uint8_t* aml, uint32_t* pos, uint32_t fieldEnd, + int32_t scopeNode, uint8_t fieldFlags, + const Object& proto) { + uint8_t accessType = fieldFlags & 0x0F; + uint8_t updateRule = (fieldFlags >> 5) & 0x03; + + 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: AccessType, AccessAttrib. Applies to the + // fields that follow it. + (*pos)++; + accessType = aml[*pos] & 0x0F; + *pos += 2; + } else if (fb == 0x02) { + // ConnectField: a NameString or a BufferData naming the + // GPIO/serial connection. Neither is supported. + (*pos)++; + if (aml[*pos] == BufferOp) { + (*pos)++; + uint32_t start = *pos; + uint32_t len = DecodePkgLength(aml, pos); + *pos = start + len; + } else { + char dummy[256]; + ReadNameString(aml, pos, scopeNode, dummy, 256); + } + } else if (fb == 0x03) { + // ExtendedAccessField: AccessType, AccessAttrib, AccessLength + (*pos)++; + accessType = aml[*pos] & 0x0F; + *pos += 3; + } 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++]; + if (fp > 1) 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 (auto* fn = m_ns.GetNode(fieldNode)) { + fn->Obj = proto; + fn->Obj.Type = ObjectType::Field; + fn->Obj.Field.BitOffset = bitOffset; + fn->Obj.Field.BitLength = bitLen; + fn->Obj.Field.AccessType = accessType; + fn->Obj.Field.UpdateRule = updateRule; + } + + bitOffset += bitLen; + } else { + // Unknown field element, skip + (*pos)++; + } + } + + *pos = fieldEnd; + } + + // ============================================================================ + // ParseExtendedOp // ============================================================================ bool Interpreter::ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, - int32_t scopeNode) { + int32_t scopeNode, ExecContext* ctx) { (*pos)++; // skip ExtOpPrefix if (*pos >= endOffset) return true; @@ -647,17 +1294,43 @@ namespace Hal { 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); - } + + RegionSpace space = (RegionSpace)aml[*pos]; + (*pos)++; + + // Offset and length are TermArgs. A constant is the common + // case, but GPIO pad registers and root port config space + // are reached through regions whose base a method computes + // into a local first. + Object offset{}, length{}; + EvalOperand(ctx, aml, pos, endOffset, scopeNode, offset); + EvalOperand(ctx, aml, pos, endOffset, scopeNode, length); + + if (auto* nsNode = m_ns.GetNode(node)) { + nsNode->Obj = Object{}; + nsNode->Obj.Type = ObjectType::OperationRegion; + nsNode->Obj.Region.Space = space; + nsNode->Obj.Region.Offset = ToInt(offset); + nsNode->Obj.Region.Length = ToInt(length); + } + return true; + } + + case DataRegionOp: { + // A region over a table found by signature. No table is + // located; the region stays zero-length and inaccessible. + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + int32_t node = m_ns.CreateNode(path); + Object a{}, b{}, c{}; + EvalOperand(ctx, aml, pos, endOffset, scopeNode, a); + EvalOperand(ctx, aml, pos, endOffset, scopeNode, b); + EvalOperand(ctx, aml, pos, endOffset, scopeNode, c); + if (auto* nsNode = m_ns.GetNode(node)) { + nsNode->Obj = Object{}; + nsNode->Obj.Type = ObjectType::OperationRegion; + nsNode->Obj.Region.Space = RegionSpace::SystemMemory; } return true; } @@ -671,80 +1344,63 @@ namespace Hal { // Region name. A Field almost always names a region // declared in an enclosing scope, so the search rule is not // optional here. - char regionPath[256]; - char regionRel[64]; - bool regionSearchable = false; - ReadNameString(aml, pos, scopeNode, regionPath, 256, - regionRel, sizeof(regionRel), ®ionSearchable); - int32_t regionNode = m_ns.FindNode(regionPath); - if (regionNode < 0 && regionSearchable) - regionNode = m_ns.SearchUpward(regionRel, scopeNode); + int32_t regionNode = ReadAndResolveName(aml, pos, 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]; + Object proto{}; + proto.Field.RegionNodeIndex = (uint32_t)regionNode; + proto.Field.Kind = (uint8_t)FieldKind::Normal; + proto.Field.DataNodeIndex = -1; + proto.Field.BankNodeIndex = -1; + ParseFieldList(aml, pos, fieldEnd, scopeNode, fieldFlags, proto); + return true; + } - 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); + case IndexFieldOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t fieldEnd = pkgStart + pkgLen; + if (fieldEnd > endOffset) fieldEnd = endOffset; - // 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 indexNode = ReadAndResolveName(aml, pos, scopeNode); + int32_t dataNode = ReadAndResolveName(aml, pos, scopeNode); - 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; - } - } + uint8_t fieldFlags = aml[*pos]; + (*pos)++; - bitOffset += bitLen; - } else { - // Unknown field element, skip - (*pos)++; - } - } + Object proto{}; + proto.Field.RegionNodeIndex = (uint32_t)indexNode; + proto.Field.Kind = (uint8_t)FieldKind::Index; + proto.Field.DataNodeIndex = dataNode; + proto.Field.BankNodeIndex = -1; + ParseFieldList(aml, pos, fieldEnd, scopeNode, fieldFlags, proto); + return true; + } - *pos = fieldEnd; + case BankFieldOp: { + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(aml, pos); + uint32_t fieldEnd = pkgStart + pkgLen; + if (fieldEnd > endOffset) fieldEnd = endOffset; + + int32_t regionNode = ReadAndResolveName(aml, pos, scopeNode); + int32_t bankNode = ReadAndResolveName(aml, pos, scopeNode); + + Object bankValue{}; + EvalOperand(ctx, aml, pos, fieldEnd, scopeNode, bankValue); + + uint8_t fieldFlags = aml[*pos]; + (*pos)++; + + Object proto{}; + proto.Field.RegionNodeIndex = (uint32_t)regionNode; + proto.Field.Kind = (uint8_t)FieldKind::Bank; + proto.Field.DataNodeIndex = -1; + proto.Field.BankNodeIndex = bankNode; + proto.Field.BankValue = ToInt(bankValue); + ParseFieldList(aml, pos, fieldEnd, scopeNode, fieldFlags, proto); return true; } @@ -832,30 +1488,32 @@ namespace Hal { 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 + case EventOp: { + char path[256]; + ReadNameString(aml, pos, scopeNode, path, 256); + if (auto* nsNode = m_ns.GetNode(m_ns.CreateNode(path))) + nsNode->Obj.Type = ObjectType::Event; 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; + case CreateFieldOp: { + ExecContext* c = ctx ? ctx : (m_methodScan ? nullptr : m_loadCtx); + if (c && c->Aml == aml) { + int32_t saved = c->ScopeNode; + c->ScopeNode = scopeNode; + CreateBufferField(*c, pos, endOffset, CreateFieldOp); + c->ScopeNode = saved; + } return true; } // Stall/Sleep at top level (rare) case StallOp: - case SleepOp: - DecodeInteger(aml, pos); + case SleepOp: { + Object dummy{}; + EvalOperand(ctx, aml, pos, endOffset, scopeNode, dummy); return true; + } case AcquireOp: { // NameString + Timeout(word) @@ -879,42 +1537,111 @@ namespace Hal { // ============================================================================ - // EvaluateObject + // Buffer fields // ============================================================================ - 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); + bool Interpreter::CreateBufferField(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + uint8_t op) { + // The source has to be named by location, not copied: the field + // reads and writes through to it. _DSM's CreateDWordField(Arg3,..) + // is the case that matters most. + Object* source = nullptr; + uint8_t sop = ctx.Aml[*pos]; + if (IsLocalOp(sop)) { + source = &ctx.Locals[sop - LocalPrefix]; + (*pos)++; + } else if (IsArgOp(sop)) { + source = &ctx.Args[sop - ArgPrefix]; + (*pos)++; + } else if (IsNameStart(sop)) { + int32_t node = ReadAndResolveName(ctx.Aml, pos, ctx.ScopeNode); + if (auto* n = m_ns.GetNode(node)) source = &n->Obj; + } else { + Object discard{}; + EvalTerm(ctx, pos, endOffset, discard); } - // For non-method objects, just return the stored value - result = nsNode->Obj; + Object index{}; + EvalTerm(ctx, pos, endOffset, index); - // 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; + uint32_t bitOffset = 0, bitLength = 0; + switch (op) { + case CreateBitFieldOp: bitOffset = (uint32_t)ToInt(index); bitLength = 1; break; + case CreateByteFieldOp: bitOffset = (uint32_t)ToInt(index) * 8; bitLength = 8; break; + case CreateWordFieldOp: bitOffset = (uint32_t)ToInt(index) * 8; bitLength = 16; break; + case CreateDWordFieldOp: bitOffset = (uint32_t)ToInt(index) * 8; bitLength = 32; break; + case CreateQWordFieldOp: bitOffset = (uint32_t)ToInt(index) * 8; bitLength = 64; break; + case CreateFieldOp: { + Object bits{}; + EvalTerm(ctx, pos, endOffset, bits); + bitOffset = (uint32_t)ToInt(index); + bitLength = (uint32_t)ToInt(bits); + break; } - return false; + default: break; } + char path[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256); + if (auto* n = m_ns.GetNode(m_ns.CreateNode(path))) { + n->Obj = Object{}; + n->Obj.Type = ObjectType::BufferField; + n->Obj.BufferField.Source = source; + n->Obj.BufferField.BitOffset = bitOffset; + n->Obj.BufferField.BitLength = bitLength; + } + return true; + } + + bool Interpreter::ReadBufferField(const Object& field, Object& value) { + const Object* src = field.BufferField.Source; + uint32_t off = field.BufferField.BitOffset; + uint32_t len = field.BufferField.BitLength; + if (!src || len == 0) { value = MakeInteger(0); return false; } + + uint8_t bytes[MaxBufferLen] = {}; + uint32_t have = ObjectBytes(*src, bytes, MaxBufferLen); + if ((uint64_t)off + len > (uint64_t)have * 8) { value = MakeInteger(0); return false; } + + if (len <= 64) { + value = MakeInteger(GetBits(bytes, off, len)); + } else { + uint8_t out[MaxBufferLen] = {}; + uint32_t outLen = MinU32((len + 7) / 8, MaxBufferLen); + for (uint32_t i = 0; i < outLen * 8 && i < len; i += 8) + out[i / 8] = (uint8_t)GetBits(bytes, off + i, MinU32(8, len - i)); + SetBuffer(value, out, outLen); + } + return true; + } + + bool Interpreter::WriteBufferField(const Object& field, const Object& value) { + Object* src = field.BufferField.Source; + uint32_t off = field.BufferField.BitOffset; + uint32_t len = field.BufferField.BitLength; + if (!src || src->Type != ObjectType::Buffer) return false; + if ((uint64_t)off + len > (uint64_t)src->Buffer.Length * 8) return false; + + uint8_t in[MaxBufferLen] = {}; + ObjectBytes(value, in, MaxBufferLen); + for (uint32_t i = 0; i < len; i += 8) { + uint32_t n = MinU32(8, len - i); + PutBits(src->Buffer.Data, off + i, n, GetBits(in, i, n)); + } return true; } // ============================================================================ - // EvaluateMethod + // Public evaluation entry points // ============================================================================ + bool Interpreter::EvaluateObject(const char* path, Object& result) { + int32_t node = m_ns.FindNode(path); + if (node < 0) return false; + return EvaluateNode(node, nullptr, 0, result); + } + bool Interpreter::EvaluateMethod(const char* path, const Object* args, int argCount, Object& result) { int32_t node = m_ns.FindNode(path); @@ -923,77 +1650,167 @@ namespace Hal { auto* nsNode = m_ns.GetNode(node); if (!nsNode || nsNode->Obj.Type != ObjectType::Method) return false; - // Packages built inside the method come out of the transient ring. - // Rewinding it here bounds the store to one evaluation's worth, - // which is what makes repeated polling (_BST every few seconds) - // safe. The caller must read the returned package before the next - // evaluation, which every caller in the kernel does. - // Evaluation is serialized: the execution contexts come from a - // shared pool, and the battery indicator now polls from a user - // thread while other subsystems may evaluate AML of their own. - bool topLevel = false; - m_evalLock.Acquire(); - if (m_evalNesting == 0) { - topLevel = true; - PackageStore::ResetTransient(); + return EvaluateNode(node, args, argCount, result); + } - // Anchor the stack budget to this frame. Everything below - // measures against it, so the same limit works whether we were - // called on the large boot stack or a thread's 16 KiB one. - char stackMarker = 0; - m_stackFloor = (uint64_t)&stackMarker - StackBudget; + bool Interpreter::EvaluateNode(int32_t node, const Object* args, int argCount, + Object& result) { + auto* nsNode = m_ns.GetNode(node); + if (!nsNode) return false; + + return TopLevel([&]() { + if (nsNode->Obj.Type == ObjectType::Method) + return InvokeMethodNode(node, args, argCount, result, 0, 0); + return NodeValue(node, result, 0, 0); + }); + } + + int32_t Interpreter::FindChild(int32_t parent, const char* seg) const { + return m_ns.FindChild(parent, seg); + } + + bool Interpreter::EvaluateChild(int32_t parent, const char* seg, const Object* args, + int argCount, Object& result) { + int32_t child = m_ns.FindChild(parent, seg); + if (child < 0) return false; + return EvaluateNode(child, args, argCount, result); + } + + bool Interpreter::GetPciAddress(int32_t deviceNode, uint8_t& bus, uint8_t& dev, + uint8_t& fn) { + auto* n = m_ns.GetNode(deviceNode); + if (!n || n->Obj.Type != ObjectType::Device) return false; + return TopLevel([&]() { + return ResolvePciAddress(deviceNode, bus, dev, fn, 0); + }); + } + + int32_t Interpreter::ResolveReference(const Object& obj, int32_t scopeNode) const { + if (obj.Type == ObjectType::Reference) + return obj.Reference.Node; + if (obj.Type != ObjectType::NameRef && obj.Type != ObjectType::String) + return -1; + + const char* text = obj.String.Data; + if (text[0] == '\0') return -1; + if (text[0] == '\\') return m_ns.FindNode(text); + + int32_t base = scopeNode < 0 ? m_ns.RootIndex() : scopeNode; + int carets = 0; + while (text[carets] == '^') carets++; + + if (carets == 0) { + // The search rule, after trying the scope itself. + int32_t found = m_ns.SearchUpward(text, base); + return found; } - m_evalNesting++; + + for (int i = 0; i < carets && base > 0; i++) { + auto* n = m_ns.GetNode(base); + base = n ? n->ParentIndex : -1; + } + if (base < 0) return -1; + if (text[carets] == '\0') return base; + + char path[256]; + m_ns.GetNodePath(base, path, sizeof(path)); + int p = 0; + while (path[p]) p++; + if (p > 1 && p < 255) path[p++] = '.'; + for (int i = carets; text[i] && p < 255; i++) path[p++] = text[i]; + path[p] = '\0'; + return m_ns.FindNode(path); + } + + // ============================================================================ + + // Method invocation + + // ============================================================================ + bool Interpreter::InvokeMethodNode(int32_t node, const Object* args, int argCount, + Object& result, int depth, int exprDepth) { + result = MakeInteger(0); + + auto* nsNode = m_ns.GetNode(node); + if (!nsNode || nsNode->Obj.Type != ObjectType::Method) return false; ExecContext* ctx = AcquireContext(); - if (!ctx) { - m_evalNesting--; - m_evalLock.Release(); + if (!ctx) return false; + for (int i = 0; i < argCount && i < MaxMethodArgs; i++) + ctx->Args[i] = args[i]; + + return RunMethodIn(ctx, node, depth, exprDepth, result); + } + + // Run a method in a context the caller acquired and filled with the + // arguments. Releases the context. + bool Interpreter::RunMethodIn(ExecContext* ctx, int32_t node, int depth, int exprDepth, + Object& result) { + result = MakeInteger(0); + auto* nsNode = m_ns.GetNode(node); + + if (nsNode->Obj.Method.Native) { + bool ok = RunNativeMethod(nsNode->Obj.Method.Native, ctx->Args, + nsNode->Obj.Method.ArgCount, result); + ReleaseContext(); + return ok; + } + + if (!nsNode->Obj.Method.Table || depth >= MaxCallDepth) { + ReleaseContext(); return false; } - ctx->Aml = m_dsdt; + ctx->Aml = nsNode->Obj.Method.Table; ctx->AmlBase = nsNode->Obj.Method.AmlOffset; ctx->AmlLength = nsNode->Obj.Method.AmlLength; ctx->ScopeNode = node; - ctx->Returned = false; - ctx->Broken = false; - ctx->Depth = 0; - ctx->ExprDepth = 0; + ctx->Depth = depth; + ctx->ExprDepth = exprDepth; + for (int i = 0; i < MaxMethodLocals; i++) + ctx->Locals[i] = MakeInteger(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); + ExecuteBlock(*ctx, ctx->AmlBase, ctx->AmlBase + ctx->AmlLength); result = ctx->ReturnValue; - if (result.Type == ObjectType::None) { - result.Type = ObjectType::Integer; - result.Integer = 0; - } + if (result.Type == ObjectType::None) + result = MakeInteger(0); + bool ok = !ctx->Aborted; ReleaseContext(); - m_evalNesting--; - - // The floor refers to a frame on this call's stack. Leaving it set - // would have the next evaluation -- possibly on a different thread's - // stack entirely -- compare against an address that no longer means - // anything, and bail out for no reason. - if (topLevel) m_stackFloor = 0; - - m_evalLock.Release(); - return ok; } + bool Interpreter::NodeValue(int32_t node, Object& result, int depth, int exprDepth) { + auto* nsNode = m_ns.GetNode(node); + if (!nsNode) { result = MakeInteger(0); return false; } + + switch (nsNode->Obj.Type) { + case ObjectType::Method: + return InvokeMethodNode(node, nullptr, 0, result, depth, exprDepth); + case ObjectType::Field: + return ReadFieldObject(node, result); + case ObjectType::BufferField: + return ReadBufferField(nsNode->Obj, result); + case ObjectType::None: + case ObjectType::Integer: + case ObjectType::String: + case ObjectType::Buffer: + case ObjectType::Package: + case ObjectType::Reference: + case ObjectType::NameRef: + result = nsNode->Obj; + return true; + default: + // Devices, power resources, regions and the like are not + // values; an operand naming one is a reference to it. + result = Object{}; + result.Type = ObjectType::Reference; + result.Reference.Node = node; + return true; + } + } + // ============================================================================ // ExecuteBlock @@ -1002,18 +1819,23 @@ namespace Hal { bool Interpreter::ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset) { char stackMarker = 0; if (ctx.ExprDepth >= MaxExprDepth) ReportDepthLimit(); - if (ctx.ExprDepth >= MaxExprDepth || !HaveStackHeadroom(&stackMarker)) + if (ctx.ExprDepth >= MaxExprDepth || !HaveStackHeadroom(&stackMarker)) { + ctx.Aborted = true; return false; + } DepthGuard depthGuard(ctx.ExprDepth); uint32_t pos = offset; - while (pos < endOffset && !ctx.Returned && !ctx.Broken) { + while (pos < endOffset && !ctx.Returned && !ctx.Broken && + !ctx.Continued && !ctx.Aborted) { + uint32_t before = pos; if (!ExecuteOpcode(ctx, &pos, endOffset)) return false; + if (pos <= before) return Abort(ctx, before, "AML decode made no progress"); } - return true; + return !ctx.Aborted; } // ============================================================================ @@ -1028,8 +1850,8 @@ namespace Hal { if (m_traceBudget > 0) { m_traceBudget--; - // Offsets are absolute into the DSDT, so they line up with a - // disassembly of the same table. + // Offsets are absolute into the method's table, so they line + // up with a disassembly of it. if (op == ExtOpPrefix && *pos + 1 < endOffset) { KernelLogStream(INFO, "AML") << " @" << base::hex << (uint64_t)*pos << " ext op " << (uint64_t)ctx.Aml[*pos + 1]; @@ -1056,8 +1878,14 @@ namespace Hal { return true; } + if (op == ContinueOp) { + (*pos)++; + ctx.Continued = true; + return true; + } + // Noop - if (op == NoopOp) { + if (op == NoopOp || op == BreakPointOp) { (*pos)++; return true; } @@ -1072,8 +1900,9 @@ namespace Hal { Object predicate{}; EvalTerm(ctx, pos, ifEnd, predicate); + if (ctx.Aborted) return false; - bool condition = (predicate.Type == ObjectType::Integer && predicate.Integer != 0); + bool condition = ToInt(predicate) != 0; if (condition) { ExecuteBlock(ctx, *pos, ifEnd); @@ -1089,13 +1918,24 @@ namespace Hal { uint32_t elseEnd = elsePkgStart + elsePkgLen; if (elseEnd > endOffset) elseEnd = endOffset; - if (!condition) { + if (!condition && !ctx.Returned && !ctx.Broken && + !ctx.Continued && !ctx.Aborted) { ExecuteBlock(ctx, *pos, elseEnd); } *pos = elseEnd; } + return !ctx.Aborted; + } + + // An Else with no If in front of it: skip the block. + if (op == ElseOp) { + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + *pos = pkgStart + pkgLen; + if (*pos > endOffset) *pos = endOffset; return true; } @@ -1109,125 +1949,159 @@ namespace Hal { uint32_t bodyStart = *pos; - for (int iter = 0; iter < MaxLoopIterations; iter++) { + // Firmware polls hardware here, sleeping between reads. When + // the bit never comes (hardware we left in another state, or + // a register we cannot reach) the iteration cap alone would + // let a loop with a Sleep(10) in it run for ten minutes, so + // the time it spends sleeping is capped too -- ACPICA gives up + // after 30 s; device power sequences finish in well under 2. + static constexpr uint64_t MaxLoopDelayUs = 2000000; + uint64_t delayedAtStart = m_delayedUs; + bool timedOut = false; + + int iter = 0; + for (; iter < MaxLoopIterations; iter++) { + if (m_delayedUs - delayedAtStart > MaxLoopDelayUs) { timedOut = true; break; } + uint32_t condPos = bodyStart; Object predicate{}; EvalTerm(ctx, &condPos, whileEnd, predicate); + if (ctx.Aborted) break; - if (predicate.Type != ObjectType::Integer || predicate.Integer == 0) + if (ToInt(predicate) == 0) break; - ctx.Broken = false; ExecuteBlock(ctx, condPos, whileEnd); + ctx.Continued = false; - if (ctx.Returned) break; + if (ctx.Returned || ctx.Aborted) break; if (ctx.Broken) { ctx.Broken = false; break; } } + if ((iter >= MaxLoopIterations || timedOut) && FirstTime(0x30000u ^ bodyStart)) { + char path[128]; + m_ns.GetNodePath(ctx.ScopeNode, path, sizeof(path)); + KernelLogStream(WARNING, "AML") << "While loop in " << path + << " at offset " << base::hex << (uint64_t)bodyStart + << (timedOut ? " timed out" : " hit the iteration cap"); + } + *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; - int32_t pkgStore = -1; int pkgElement = 0; - EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx, - pkgStore, pkgElement); - StoreToTarget(ctx, value, targetNode, isLocal, localIdx, isArg, argIdx, - pkgStore, pkgElement); - return true; - } - - // Increment/Decrement - if (op == IncrementOp || op == DecrementOp) { - (*pos)++; - int32_t targetNode = -1; - bool isLocal = false, isArg = false; - int localIdx = 0, argIdx = 0; - int32_t pkgStore = -1; int pkgElement = 0; - EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx, - pkgStore, pkgElement); - - 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, - pkgStore, pkgElement); - return true; + return !ctx.Aborted; } // Extended opcodes during execution if (op == ExtOpPrefix) { - (*pos)++; - if (*pos >= endOffset) return true; - uint8_t extOp = ctx.Aml[*pos]; - (*pos)++; + if (*pos + 1 >= endOffset) return Abort(ctx, *pos, "Truncated extended opcode"); + uint8_t extOp = ctx.Aml[*pos + 1]; - // 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 + // Definitions inside a method body 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); + extOp == MutexOp || extOp == EventOp || extOp == IndexFieldOp || + extOp == BankFieldOp || extOp == DataRegionOp || extOp == CreateFieldOp) { + return ParseExtendedOp(ctx.Aml, pos, endOffset, ctx.ScopeNode, &ctx); } - return true; + Object discard{}; + EvalExtOp(ctx, pos, endOffset, discard); + return !ctx.Aborted; } // Name/Scope/Method definitions can appear inside methods if (op == NameOp || op == ScopeOp || op == MethodOp) { - return ParseNamedObject(ctx.Aml, pos, endOffset, ctx.ScopeNode); + return ParseNamedObject(ctx.Aml, pos, endOffset, ctx.ScopeNode, &ctx); } - // Anything else — try to evaluate as an expression and discard the result + if (op == ExternalOp) { + (*pos)++; + char dummy[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, dummy, 256); + *pos += 2; + return true; + } + + // Anything else — evaluate as an expression and discard the result Object discard{}; - return EvalTerm(ctx, pos, endOffset, discard); + EvalTerm(ctx, pos, endOffset, discard); + return !ctx.Aborted; + } + + // ============================================================================ + + // Expression helpers + + // ============================================================================ + static void Concatenate(const Object& a, const Object& b, Object& out) { + if (a.Type == ObjectType::String) { + Object bs{}; + ToStringObject(b, bs); + char buf[MaxStringLen]; + uint32_t n = MinU32(a.String.Length, MaxStringLen - 1); + memcpy(buf, a.String.Data, n); + uint32_t m = MinU32(bs.String.Length, MaxStringLen - 1 - n); + memcpy(buf + n, bs.String.Data, m); + SetString(out, buf, n + m); + return; + } + + uint8_t buf[MaxBufferLen]; + uint32_t n; + if (a.Type == ObjectType::Buffer) { + n = ObjectBytes(a, buf, MaxBufferLen); + } else { + n = ObjectBytes(MakeInteger(ToInt(a)), buf, MaxBufferLen); + } + Object bb = b; + if (a.Type != ObjectType::Buffer) bb = MakeInteger(ToInt(b)); + n += ObjectBytes(bb, buf + n, MaxBufferLen - n); + SetBuffer(out, buf, n); + } + + // Resource templates end in an EndTag (0x79, checksum). Concatenating + // two drops the first one's. + static void ConcatResources(const Object& a, const Object& b, Object& out) { + uint8_t buf[MaxBufferLen]; + uint32_t n = ObjectBytes(a, buf, MaxBufferLen); + if (n >= 2 && buf[n - 2] == 0x79) n -= 2; + uint32_t m = ObjectBytes(b, buf + n, MaxBufferLen - n); + uint32_t total = n + m; + if (total < 2 || buf[total - 2] != 0x79) { + if (total + 2 <= MaxBufferLen) { buf[total++] = 0x79; buf[total++] = 0x00; } + } + SetBuffer(out, buf, total); + } + + static uint8_t ObjectTypeCode(ObjectType t) { + switch (t) { + case ObjectType::Integer: return 1; + case ObjectType::String: return 2; + case ObjectType::Buffer: return 3; + case ObjectType::Package: return 4; + case ObjectType::Field: return 5; + case ObjectType::Device: return 6; + case ObjectType::Event: return 7; + case ObjectType::Method: return 8; + case ObjectType::Mutex: return 9; + case ObjectType::OperationRegion: return 10; + case ObjectType::PowerResource: return 11; + case ObjectType::Processor: return 12; + case ObjectType::ThermalZone: return 13; + case ObjectType::BufferField: return 14; + default: return 0; + } + } + + static bool MatchTerm(uint8_t matchOp, uint64_t elem, uint64_t operand) { + switch (matchOp) { + case 0: return true; // MTR + case 1: return elem == operand; // MEQ + case 2: return elem <= operand; // MLE + case 3: return elem < operand; // MLT + case 4: return elem >= operand; // MGE + case 5: return elem > operand; // MGT + default: return false; + } } // ============================================================================ @@ -1236,633 +2110,1096 @@ namespace Hal { // ============================================================================ // Evaluate an AML term that produces a value. + // + // This is the interpreter's recursive core: every nested expression, + // and every method call, passes through it again. It is kept a thin + // dispatcher so that its own frame stays small; each opcode family is + // handled out of line, and those handlers keep at most one or two + // Objects live while they recurse (bulky scratch -- byte buffers, + // extra temporaries -- lives in leaf helpers that never recurse). bool Interpreter::EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result) { char stackMarker = 0; if (ctx.ExprDepth >= MaxExprDepth) ReportDepthLimit(); if (ctx.ExprDepth >= MaxExprDepth || !HaveStackHeadroom(&stackMarker)) { - result = Object{}; - result.Type = ObjectType::Integer; - result.Integer = 0; + result = MakeInteger(0); + ctx.Aborted = true; return false; } DepthGuard depthGuard(ctx.ExprDepth); - if (*pos >= endOffset) return false; + if (ctx.Aborted || *pos >= endOffset) { result = MakeInteger(0); 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; - } + switch (op) { + case ZeroOp: case OneOp: case OnesOp: + case BytePrefix: case WordPrefix: case DWordPrefix: case QWordPrefix: + result = MakeInteger(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]; + case StringPrefix: { (*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; - } - - // DerefOf. Index() already yields the element's value here, so a - // dereference of one is just the value again; anything else passes - // through unchanged. - if (op == DerefOfOp) { - (*pos)++; - return EvalTerm(ctx, pos, endOffset, result); - } - - // 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; - - // Retain the raw bytes (constant elements are read straight - // out of them) and, in parallel, evaluate each element into the - // package store so computed ones -- Package(){Local0, Local1}, - // the shape _BST and friends are built with -- survive. - 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); - - uint32_t elemPos = *pos; - int count = (elemPos < pkgEnd) ? (int)ctx.Aml[elemPos] : 0; - elemPos++; - if (count > MaxPackageElements) count = MaxPackageElements; - - if (count > 0) { - int32_t store = PackageStore::AllocTransient(count); - if (store > 0) { - for (int i = 0; i < count && elemPos < pkgEnd; i++) { - Object elem{}; - uint32_t before = elemPos; - if (!EvalTerm(ctx, &elemPos, pkgEnd, elem)) break; - // An element that consumed nothing would spin here - // forever on a stream we decoded wrongly. - if (elemPos <= before) break; - Object* slot = PackageStore::Element(store, i); - if (slot) *slot = elem; - } - result.PkgIndex = store; - } - } - - *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; - int32_t ps = -1; int pe = 0; - EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe); - StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe); - } else if (IsLeadNameChar(next) || next == '\\' || next == '^') { - int32_t tn = -1; - bool il = false, ia = false; - int li = 0, ai = 0; - int32_t ps = -1; int pe = 0; - EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe); - StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe); - } - // ZeroOp as "no target" — skip - else if (next == ZeroOp) { + result = Object{}; + result.Type = ObjectType::String; + int i = 0; + while (*pos < endOffset && ctx.Aml[*pos] != 0) { + if (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; } - return true; - } + case BufferOp: + return EvalBufferOp(ctx, pos, endOffset, result); - // 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; - int32_t ps = -1; int pe = 0; - EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe); - StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe); - } else if (*pos < endOffset && ctx.Aml[*pos] == ZeroOp) { + case PackageOp: + case VarPackageOp: + return EvalPackageOp(ctx, pos, endOffset, result); + + case LAndOp: case LOrOp: case LEqualOp: case LGreaterOp: case LLessOp: + case AddOp: case SubtractOp: case MultiplyOp: case AndOp: case OrOp: + case XorOp: case ShiftLeftOp: case ShiftRightOp: case NandOp: case NorOp: + case ModOp: case ConcatOp: case ConcatResOp: + return EvalBinary(ctx, pos, endOffset, result); + + case LNotOp: case NotOp: case FindSetLeftBitOp: case FindSetRightBitOp: + case ToBufferOp: case ToIntegerOp: case ToHexStringOp: case ToDecimalStringOp: + return EvalUnary(ctx, pos, endOffset, result); + + case DivideOp: + return EvalDivide(ctx, pos, endOffset, result); + + case StoreOp: case CopyObjectOp: case IncrementOp: case DecrementOp: + case NotifyOp: + return EvalStoreOp(ctx, pos, endOffset, result); + + case RefOfOp: case DerefOfOp: case SizeOfOp: case ObjectTypeOp: + return EvalReference(ctx, pos, endOffset, result); + + case IndexOp: + return EvalIndexOp(ctx, pos, endOffset, result); + + case MatchOp: + return EvalMatchOp(ctx, pos, endOffset, result); + + case MidOp: case ToStringOp: + return EvalMidOp(ctx, pos, endOffset, result); + + case CreateDWordFieldOp: case CreateWordFieldOp: case CreateByteFieldOp: + case CreateBitFieldOp: case CreateQWordFieldOp: (*pos)++; - } - return true; + CreateBufferField(ctx, pos, endOffset, op); + result = MakeInteger(0); + return true; + + case ExtOpPrefix: + return EvalExtOp(ctx, pos, endOffset, result); + + default: + break; } - // DivideOp: Divide(Dividend, Divisor, Remainder, Result) - if (op == DivideOp) { + if (IsLocalOp(op)) { + result = ctx.Locals[op - LocalPrefix]; (*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; - int32_t ps = -1; int pe = 0; - EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe); - StoreToTarget(ctx, remainder, tn, il, li, ia, ai, ps, pe); - } - // Result target - if (*pos < endOffset) { - int32_t tn = -1; - bool il = false, ia = false; - int li = 0, ai = 0; - int32_t ps = -1; int pe = 0; - EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe); - StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe); - } return true; } - // SizeOf - if (op == SizeOfOp) { + if (IsArgOp(op)) { + result = ctx.Args[op - ArgPrefix]; (*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 if (obj.Type == ObjectType::Package) - result.Integer = (uint64_t)GetPackageCount(obj); - else - result.Integer = 0; return true; } - // Index - if (op == IndexOp) { - (*pos)++; - Object source{}, index{}; - EvalTerm(ctx, pos, endOffset, source); - EvalTerm(ctx, pos, endOffset, index); + if (IsNameStart(op)) + return EvalNameRef(ctx, pos, endOffset, result); - uint64_t idx = index.Integer; + // Unknown opcode. Carrying on would execute its operands as code. + result = MakeInteger(0); + return Abort(ctx, *pos, "Unsupported AML opcode"); + } - if (source.Type == ObjectType::Package) { - // ACPI defines Index() as producing a reference; we hand - // back the element's value instead, which is what every - // operand context here goes on to use. Writes through - // Index() are handled separately, in EvalTarget. - if (!GetPackageElement(source, (int)idx, result)) { - result = Object{}; - result.Type = ObjectType::Integer; - result.Integer = 0; - } - } else if (source.Type == ObjectType::Buffer && idx < source.Buffer.Length) { - result.Type = ObjectType::Integer; - result.Integer = source.Buffer.Data[idx]; - } else { - result.Type = ObjectType::Integer; - 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; - int32_t ps = -1; int pe = 0; - EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe); - StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe); - } else if (*pos < endOffset && ctx.Aml[*pos] == ZeroOp) { - (*pos)++; - } - return true; - } + // EvalTerm handlers - // 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]; - char relative[64]; - bool searchable = false; - ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256, - relative, sizeof(relative), &searchable); - - int32_t node = m_ns.FindNode(path); - if (node < 0 && searchable) - node = m_ns.SearchUpward(relative, 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* sub = AcquireContext(); - if (!sub) { - result.Type = ObjectType::Integer; - result.Integer = 0; - return true; - } - ExecContext& subCtx = *sub; - 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; - subCtx.ExprDepth = ctx.ExprDepth; - 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; - } - ReleaseContext(); - } 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 + // ============================================================================ + bool Interpreter::EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { (*pos)++; - result.Type = ObjectType::Integer; - result.Integer = 0; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t bufEnd = pkgStart + pkgLen; + if (bufEnd > endOffset) bufEnd = endOffset; + + // BufferSize is a TermArg; evaluate it into result, then build + // the buffer over it. + EvalTerm(ctx, pos, bufEnd, result); + uint32_t sz = (uint32_t)ToInt(result); + if (sz > MaxBufferLen) sz = MaxBufferLen; + + result = Object{}; + result.Type = ObjectType::Buffer; + result.Buffer.Length = sz; + memset(result.Buffer.Data, 0, sz); + + uint32_t initLen = (bufEnd > *pos) ? bufEnd - *pos : 0; + if (initLen > sz) initLen = sz; + memcpy(result.Buffer.Data, &ctx.Aml[*pos], initLen); + + *pos = bufEnd; + return true; + } + + bool Interpreter::EvalPackageOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + (*pos)++; + uint32_t pkgStart = *pos; + uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos); + uint32_t pkgEnd = pkgStart + pkgLen; + if (pkgEnd > endOffset) pkgEnd = endOffset; + + uint32_t count; + if (op == PackageOp) { + count = (*pos < pkgEnd) ? ctx.Aml[*pos] : 0; + (*pos)++; + } else { + EvalTerm(ctx, pos, pkgEnd, result); + count = (uint32_t)ToInt(result); + } + + // Retain the constant form (pointing into the table) and, in + // parallel, evaluate each element into the package store so + // computed ones -- Package(){Local0, Local1}, the shape _BST + // and friends are built with -- survive. A package too large + // for the store keeps only the constant form. + result = Object{}; + result.Type = ObjectType::Package; + result.Package.Data = &ctx.Aml[*pos]; + result.Package.Length = (*pos <= pkgEnd) ? pkgEnd - *pos : 0; + result.Package.Count = count; + + if (count > 0 && count <= (uint32_t)MaxPackageElements) { + int32_t store = PackageStore::AllocTransient((int)count); + if (store > 0) { + uint32_t elemPos = *pos; + for (uint32_t i = 0; i < count && elemPos < pkgEnd; i++) { + Object* slot = PackageStore::Element(store, (int)i); + if (!slot) break; + uint32_t before = elemPos; + if (!EvalPackageElement(ctx, &elemPos, pkgEnd, *slot)) break; + // An element that consumed nothing would spin here + // forever on a stream we decoded wrongly. + if (elemPos <= before) break; + } + result.PkgIndex = store; + } + } + + *pos = pkgEnd; + return !ctx.Aborted; + } + + // The arithmetic of a binary opcode, out of line from the recursion. + __attribute__((noinline)) + static void ComputeBinary(uint8_t op, const Object& lhs, const Object& rhs, Object& out) { + switch (op) { + case LAndOp: out = MakeInteger((ToInt(lhs) && ToInt(rhs)) ? 1 : 0); return; + case LOrOp: out = MakeInteger((ToInt(lhs) || ToInt(rhs)) ? 1 : 0); return; + case LEqualOp: out = MakeInteger(CompareObjects(lhs, rhs) == 0 ? 1 : 0); return; + case LGreaterOp: out = MakeInteger(CompareObjects(lhs, rhs) > 0 ? 1 : 0); return; + case LLessOp: out = MakeInteger(CompareObjects(lhs, rhs) < 0 ? 1 : 0); return; + case ConcatOp: { + Object a = lhs; + Concatenate(a, rhs, out); + return; + } + case ConcatResOp: { + Object a = lhs; + ConcatResources(a, rhs, out); + return; + } + default: + break; + } + + uint64_t x = ToInt(lhs), y = ToInt(rhs), r = 0; + switch (op) { + case AddOp: r = x + y; break; + case SubtractOp: r = x - y; break; + case MultiplyOp: r = x * y; break; + case AndOp: r = x & y; break; + case OrOp: r = x | y; break; + case XorOp: r = x ^ y; break; + case ShiftLeftOp: r = (y >= 64) ? 0 : x << y; break; + case ShiftRightOp: r = (y >= 64) ? 0 : x >> y; break; + case NandOp: r = ~(x & y); break; + case NorOp: r = ~(x | y); break; + case ModOp: r = y ? x % y : 0; break; + default: break; + } + out = MakeInteger(r); + } + + // Op(Operand, Operand[, Target]). Both operands are always evaluated: + // AML has no short-circuit, and skipping one would desync the stream. + bool Interpreter::EvalBinary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + (*pos)++; + + EvalTerm(ctx, pos, endOffset, result); + Object rhs{}; + EvalTerm(ctx, pos, endOffset, rhs); + ComputeBinary(op, result, rhs, result); + + bool logical = op >= LAndOp && op <= LLessOp; + if (!logical) StoreResult(ctx, pos, result); + return true; + } + + __attribute__((noinline)) + static void ComputeUnary(uint8_t op, Object& value) { + if (op == LNotOp) { value = MakeInteger(ToInt(value) == 0 ? 1 : 0); return; } + + if (op == NotOp || op == FindSetLeftBitOp || op == FindSetRightBitOp) { + uint64_t x = ToInt(value), r = 0; + if (op == NotOp) { + r = ~x; + } else if (x != 0) { + if (op == FindSetLeftBitOp) { r = 64; while (!(x & (1ULL << (r - 1)))) r--; } + else { r = 1; while (!(x & (1ULL << (r - 1)))) r++; } + } + value = MakeInteger(r); + return; + } + + if (op == ToBufferOp) { + uint8_t buf[MaxBufferLen]; + uint32_t n = ObjectBytes(value, buf, MaxBufferLen); + // A string converts with its terminator. + if (value.Type == ObjectType::String && n < MaxBufferLen) buf[n++] = 0; + SetBuffer(value, buf, n); + return; + } + + if (op == ToIntegerOp) { + // Explicit conversion: a string is decimal unless it says 0x. + if (value.Type == ObjectType::String) + value = MakeInteger(ParseIntegerString(value.String.Data, value.String.Length, true)); + else + value = MakeInteger(ToInt(value)); + return; + } + + // ToHexString / ToDecimalString + if (value.Type == ObjectType::String) return; + char buf[MaxStringLen]; + uint32_t n = 0; + uint32_t max = MaxStringLen - 1; + if (value.Type == ObjectType::Buffer) { + for (uint32_t i = 0; i < value.Buffer.Length && n < max; i++) { + if (i && n < max) buf[n++] = ','; + if (op == ToHexStringOp) { + if (n + 4 > max) break; + buf[n++] = '0'; buf[n++] = 'x'; + AppendHex(buf, n, max, value.Buffer.Data[i], 2); + } else { + AppendDecimal(buf, n, max, value.Buffer.Data[i]); + } + } + } else if (op == ToHexStringOp) { + buf[n++] = '0'; buf[n++] = 'x'; + AppendHex(buf, n, max, ToInt(value), 16); + } else { + AppendDecimal(buf, n, max, ToInt(value)); + } + SetString(value, buf, n); + } + + // Op(Operand[, Target]) + bool Interpreter::EvalUnary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + (*pos)++; + EvalTerm(ctx, pos, endOffset, result); + ComputeUnary(op, result); + if (op != LNotOp) StoreResult(ctx, pos, result); + return true; + } + + // Divide(Dividend, Divisor, Remainder, Result) + bool Interpreter::EvalDivide(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + (*pos)++; + EvalTerm(ctx, pos, endOffset, result); + Object other{}; + EvalTerm(ctx, pos, endOffset, other); + + uint64_t x = ToInt(result), y = ToInt(other); + other = MakeInteger(y ? x % y : 0); // remainder + result = MakeInteger(y ? x / y : 0); + + StoreResult(ctx, pos, other); + StoreResult(ctx, pos, result); + return true; + } + + // Store, CopyObject, Increment, Decrement, Notify + bool Interpreter::EvalStoreOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + (*pos)++; + + if (op == StoreOp) { + EvalTerm(ctx, pos, endOffset, result); + StoreResult(ctx, pos, result); + return true; + } + + if (op == IncrementOp || op == DecrementOp) { + Target t; + EvalTarget(ctx, pos, t); + uint64_t v = ToInt(ReadTarget(ctx, t)); + result = MakeInteger(op == IncrementOp ? v + 1 : v - 1); + StoreToTarget(ctx, result, t); + return true; + } + + if (op == NotifyOp) { + // Notify(Object, Value): nothing listens. + Target t; + EvalTarget(ctx, pos, t); + EvalTerm(ctx, pos, endOffset, result); + result = MakeInteger(0); + return true; + } + + // CopyObject(Source, SimpleName): no conversion. + EvalTerm(ctx, pos, endOffset, result); + Target t; + EvalTarget(ctx, pos, t); + if (t.What == Target::Kind::Node) { + auto* n = m_ns.GetNode(t.Node); + if (n && n->Obj.Type != ObjectType::Field && + n->Obj.Type != ObjectType::BufferField && + n->Obj.Type != ObjectType::Method) { + n->Obj = result; + if (result.Type == ObjectType::Package) n->Obj.PkgIndex = -1; + return true; + } + } + StoreToTarget(ctx, result, t); + return true; + } + + // RefOf, DerefOf, SizeOf, ObjectType + bool Interpreter::EvalReference(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + (*pos)++; + + if (op == RefOfOp || op == ObjectTypeOp) { + Target t; + EvalTarget(ctx, pos, t); + if (op == ObjectTypeOp) { + uint8_t code = 0; + if (t.What == Target::Kind::Node) { + if (auto* n = m_ns.GetNode(t.Node)) code = ObjectTypeCode(n->Obj.Type); + } else if (t.What != Target::Kind::None) { + code = ObjectTypeCode(ReadTarget(ctx, t).Type); + } + result = MakeInteger(code); + } else if (t.What == Target::Kind::Node && t.Node >= 0) { + result = Object{}; + result.Type = ObjectType::Reference; + result.Reference.Node = t.Node; + } else { + result = ReadTarget(ctx, t); + } + return true; + } + + EvalTerm(ctx, pos, endOffset, result); + + // DerefOf(ObjReference). Index() already yields the element's + // value, so a dereference of one is just the value again. + if (result.Type == ObjectType::Reference || result.Type == ObjectType::NameRef) { + int32_t node = ResolveReference(result, ctx.ScopeNode); + if (node >= 0) NodeValue(node, result, ctx.Depth + 1, ctx.ExprDepth); + } + if (op == DerefOfOp) return true; + + // SizeOf(SuperName) + uint64_t n = 0; + if (result.Type == ObjectType::String) n = result.String.Length; + else if (result.Type == ObjectType::Buffer) n = result.Buffer.Length; + else if (result.Type == ObjectType::Package) n = (uint64_t)GetPackageCount(result); + result = MakeInteger(n); + return true; + } + + // One element of a package, buffer or string, as Index() reads it. + __attribute__((noinline)) + bool Interpreter::IndexElement(const Object& source, uint64_t idx, Object& out) { + if (source.Type == ObjectType::Package) { + // ACPI defines Index() as producing a reference; we hand back + // the element's value instead, which is what every operand + // context here goes on to use. Writes through Index() are + // handled separately, in EvalTarget. + Object element{}; + if (!GetPackageElement(source, (int)idx, element)) element = MakeInteger(0); + out = element; + } else if (source.Type == ObjectType::Buffer && idx < source.Buffer.Length) { + out = MakeInteger(source.Buffer.Data[idx]); + } else if (source.Type == ObjectType::String && idx < source.String.Length) { + out = MakeInteger((uint8_t)source.String.Data[idx]); + } else { + out = MakeInteger(0); + } + return true; + } + + // Index(Source, Index, Target) + bool Interpreter::EvalIndexOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + (*pos)++; + EvalTerm(ctx, pos, endOffset, result); + if (result.Type == ObjectType::Reference) + NodeValue(result.Reference.Node, result, ctx.Depth + 1, ctx.ExprDepth); + + uint64_t idx; + { + Object index{}; + EvalTerm(ctx, pos, endOffset, index); + idx = ToInt(index); + } + IndexElement(result, idx, result); + StoreResult(ctx, pos, result); + return true; + } + + // Match(SearchPkg, Op1, Operand1, Op2, Operand2, StartIndex) + bool Interpreter::EvalMatchOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + (*pos)++; + Object pkg{}, o1{}; + EvalTerm(ctx, pos, endOffset, pkg); + uint8_t m1 = ctx.Aml[(*pos)++]; + EvalTerm(ctx, pos, endOffset, o1); + uint8_t m2 = ctx.Aml[(*pos)++]; + uint64_t v1 = ToInt(o1); + EvalTerm(ctx, pos, endOffset, o1); + uint64_t v2 = ToInt(o1); + EvalTerm(ctx, pos, endOffset, o1); + int start = (int)ToInt(o1); + + result = MakeInteger(~0ULL); + int count = GetPackageCount(pkg); + for (int i = start; i < count; i++) { + if (!GetPackageElement(pkg, i, o1)) break; + if (o1.Type != ObjectType::Integer && o1.Type != ObjectType::String && + o1.Type != ObjectType::Buffer) continue; + uint64_t v = ToInt(o1); + if (MatchTerm(m1, v, v1) && MatchTerm(m2, v, v2)) { + result = MakeInteger((uint64_t)i); + break; + } + } + return true; + } + + __attribute__((noinline)) + static void ComputeMid(Object& source, uint64_t start, uint64_t count) { + uint8_t buf[MaxBufferLen]; + uint32_t n = ObjectBytes(source, buf, MaxBufferLen); + if (start > n) start = n; + if (count > n - start) count = n - start; + if (source.Type == ObjectType::String) + SetString(source, (const char*)buf + start, (uint32_t)count); + else + SetBuffer(source, buf + start, (uint32_t)count); + } + + __attribute__((noinline)) + static void ComputeToString(Object& source, uint64_t limit) { + uint8_t buf[MaxBufferLen]; + uint32_t n = ObjectBytes(source, buf, MaxBufferLen); + uint32_t i = 0; + while (i < n && buf[i] != 0 && i < limit) i++; + SetString(source, (const char*)buf, i); + } + + // Mid(Source, Index, Length, Target) / ToString(Source, Length, Target) + bool Interpreter::EvalMidOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + (*pos)++; + EvalTerm(ctx, pos, endOffset, result); + + Object operand{}; + EvalTerm(ctx, pos, endOffset, operand); + uint64_t first = ToInt(operand); + + if (op == MidOp) { + EvalTerm(ctx, pos, endOffset, operand); + ComputeMid(result, first, ToInt(operand)); + } else { + ComputeToString(result, first); + } + StoreResult(ctx, pos, result); + return true; + } + + __attribute__((noinline)) + void Interpreter::ReportUnresolved(ExecContext& ctx, uint32_t namePos) { + char text[MaxStringLen]; + uint32_t p = namePos; + DecodeNameText(ctx.Aml, ctx.AmlBase + ctx.AmlLength, &p, text, sizeof(text)); + uint32_t key = 0x40000u; + for (int i = 0; text[i]; i++) key = key * 31 + (uint8_t)text[i]; + if (FirstTime(key)) { + char scope[128]; + m_ns.GetNodePath(ctx.ScopeNode, scope, sizeof(scope)); + KernelLogStream(DEBUG, "AML") << "Unresolved name " << text << " in " << scope; + } + } + + // A name in operand position: a method call, or the named object's value. + bool Interpreter::EvalNameRef(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint32_t namePos = *pos; + int32_t node = ReadAndResolveName(ctx.Aml, pos, ctx.ScopeNode); + + auto* nsNode = m_ns.GetNode(node); + if (!nsNode) { + ReportUnresolved(ctx, namePos); + result = MakeInteger(0); + return true; + } + + if (nsNode->Obj.Type == ObjectType::Method) + return CallMethod(ctx, node, pos, endOffset, result); + + return NodeValue(node, result, ctx.Depth + 1, ctx.ExprDepth); + } + + // A method call from AML: the arguments are evaluated straight into + // the callee's context, so no argument array sits on this frame + // while they (and the call) recurse. + bool Interpreter::CallMethod(ExecContext& ctx, int32_t node, uint32_t* pos, + uint32_t endOffset, Object& result) { + auto* nsNode = m_ns.GetNode(node); + int argc = nsNode->Obj.Method.ArgCount; + + ExecContext* callee = AcquireContext(); + if (!callee) { + // Out of contexts (call depth): still consume the arguments. + for (int i = 0; i < argc && *pos < endOffset; i++) + EvalTerm(ctx, pos, endOffset, result); + result = MakeInteger(0); + return true; + } + + for (int i = 0; i < argc && *pos < endOffset; i++) + EvalTerm(ctx, pos, endOffset, callee->Args[i]); + + if (ctx.Aborted) { + ReleaseContext(); + result = MakeInteger(0); + return false; + } + + RunMethodIn(callee, node, ctx.Depth + 1, ctx.ExprDepth, result); return true; } // ============================================================================ - // EvalTarget + // Extended opcodes // ============================================================================ - bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos, - int32_t& nodeIndex, bool& isLocal, int& localIdx, - bool& isArg, int& argIdx, - int32_t& pkgStore, int& pkgElement) { - nodeIndex = -1; + bool Interpreter::EvalExtOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint32_t opPos = *pos; + if (*pos + 1 >= endOffset) return Abort(ctx, opPos, "Truncated extended opcode"); + uint8_t extOp = ctx.Aml[*pos + 1]; + *pos += 2; + result = MakeInteger(0); + + switch (extOp) { + case CondRefOfOp: { + // CondRefOf(SuperName, Target): does the object exist? + Target src; + EvalTarget(ctx, pos, src); + bool exists = false; + Object ref{}; + if (src.What == Target::Kind::Node && src.Node >= 0) { + exists = true; + ref.Type = ObjectType::Reference; + ref.Reference.Node = src.Node; + } else if (src.What == Target::Kind::Local || src.What == Target::Kind::Arg) { + ref = ReadTarget(ctx, src); + exists = ref.Type != ObjectType::None; + } + Target dst; + EvalTarget(ctx, pos, dst); + if (exists) StoreToTarget(ctx, ref, dst); + result = MakeInteger(exists ? 1 : 0); + return true; + } + + case AcquireOp: { + // Evaluation is already serialized; every mutex is free. + Target t; + EvalTarget(ctx, pos, t); + *pos += 2; // timeout + result = MakeInteger(0); + return true; + } + + case ReleaseOp: + case SignalOp: + case ResetOp: + case UnloadOp: { + Target t; + EvalTarget(ctx, pos, t); + return true; + } + + case WaitOp: { + Target t; + EvalTarget(ctx, pos, t); + Object timeout{}; + EvalTerm(ctx, pos, endOffset, timeout); + return true; + } + + case SleepOp: { + Object ms{}; + EvalTerm(ctx, pos, endOffset, ms); + // Real delays: power sequencing (rail ramp, PERST#, + // link training) depends on them. + uint64_t v = ToInt(ms); + if (v > 5000) v = 5000; + Timekeeping::BusyWaitMs(v); + m_delayedUs += v * 1000; + return true; + } + + case StallOp: { + Object us{}; + EvalTerm(ctx, pos, endOffset, us); + uint64_t v = ToInt(us); + if (v > 10000) v = 10000; + Timekeeping::BusyWaitUs(v); + m_delayedUs += v; + return true; + } + + case FromBCDOp: + case ToBCDOp: { + Object a{}; + EvalTerm(ctx, pos, endOffset, a); + uint64_t x = ToInt(a), r = 0, mul = 1; + if (extOp == FromBCDOp) { + while (x) { r += (x & 0xF) * mul; mul *= 10; x >>= 4; } + } else { + int shift = 0; + while (x && shift < 64) { r |= (x % 10) << shift; x /= 10; shift += 4; } + } + result = MakeInteger(r); + StoreResult(ctx, pos, result); + return true; + } + + case LoadOp: { + // Load(Object, Target): dynamic SSDT load from a region. + char dummy[256]; + ReadNameString(ctx.Aml, pos, ctx.ScopeNode, dummy, 256); + Target t; + EvalTarget(ctx, pos, t); + if (FirstTime(0x50000u)) + KernelLogStream(INFO, "AML") << "Load() of a dynamic table is not supported"; + return true; + } + + case LoadTableOp: { + Object a{}; + for (int i = 0; i < 6; i++) EvalTerm(ctx, pos, endOffset, a); + if (FirstTime(0x50001u)) + KernelLogStream(INFO, "AML") << "LoadTable() is not supported"; + result = MakeInteger(0); + return true; + } + + case RevisionOp: + result = MakeInteger(2); + return true; + + case DebugOp: + return true; + + case FatalOp: { + uint8_t type = ctx.Aml[*pos]; + *pos += 5; // FatalType byte, FatalCode dword + Object arg{}; + EvalTerm(ctx, pos, endOffset, arg); + KernelLogStream(ERROR, "AML") << "Firmware Fatal(" << base::hex + << (uint64_t)type << ", " << ToInt(arg) << ")"; + return true; + } + + case TimerOp: + // 100 ns units. + result = MakeInteger(Timekeeping::GetMilliseconds() * 10000); + return true; + + case MutexOp: + case EventOp: + case OpRegionOp: + case FieldOp: + case DeviceOp: + case ProcessorOp: + case PowerResOp: + case ThermalZoneOp: + case IndexFieldOp: + case BankFieldOp: + case DataRegionOp: + case CreateFieldOp: + *pos = opPos; + ParseExtendedOp(ctx.Aml, pos, endOffset, ctx.ScopeNode, &ctx); + return true; + + default: + return Abort(ctx, opPos, "Unsupported AML extended opcode"); + } + } + + // ============================================================================ + + // Package elements + + // ============================================================================ + bool Interpreter::EvalPackageElement(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result) { + uint8_t op = ctx.Aml[*pos]; + if (!IsNameStart(op)) + return EvalTerm(ctx, pos, endOffset, result); + + // A name in a package is a reference to the object, never a call: + // Package(){PG00} lists a power resource, it does not run one. + uint32_t textPos = *pos; + char text[MaxStringLen]; + DecodeNameText(ctx.Aml, endOffset, &textPos, text, sizeof(text)); + + int32_t node = ReadAndResolveName(ctx.Aml, pos, ctx.ScopeNode); + auto* n = m_ns.GetNode(node); + if (!n) { + uint32_t len = 0; + while (text[len]) len++; + SetString(result, text, len); + result.Type = ObjectType::NameRef; + return true; + } + + if (IsDataType(n->Obj.Type)) { + result = n->Obj; + return true; + } + + result = Object{}; + result.Type = ObjectType::Reference; + result.Reference.Node = node; + return true; + } + + // ============================================================================ + + // Targets + + // ============================================================================ + bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos, Target& t) { + t = Target{}; char stackMarker = 0; if (ctx.ExprDepth >= MaxExprDepth) ReportDepthLimit(); if (ctx.ExprDepth >= MaxExprDepth || !HaveStackHeadroom(&stackMarker)) { - isLocal = false; isArg = false; - localIdx = 0; argIdx = 0; - pkgStore = -1; pkgElement = 0; + ctx.Aborted = true; return false; } DepthGuard depthGuard(ctx.ExprDepth); - isLocal = false; - isArg = false; - localIdx = 0; - argIdx = 0; - pkgStore = -1; - pkgElement = 0; - - if (*pos >= ctx.AmlBase + ctx.AmlLength) return false; + uint32_t end = ctx.AmlBase + ctx.AmlLength; + if (*pos >= end) return false; uint8_t op = ctx.Aml[*pos]; - // ZeroOp = null target (discard) + // NullName = no target (discard) if (op == ZeroOp) { (*pos)++; return true; } - // Local - if (op >= 0x60 && op <= 0x67) { - isLocal = true; - localIdx = op - 0x60; + if (IsLocalOp(op)) { + t.What = Target::Kind::Local; + t.Index = op - LocalPrefix; (*pos)++; return true; } - // Arg - if (op >= 0x68 && op <= 0x6E) { - isArg = true; - argIdx = op - 0x68; + if (IsArgOp(op)) { + t.What = Target::Kind::Arg; + t.Index = op - ArgPrefix; (*pos)++; return true; } + // Debug object + if (op == ExtOpPrefix && *pos + 1 < end && ctx.Aml[*pos + 1] == DebugOp) { + *pos += 2; + return true; + } + // Named target - if (IsLeadNameChar(op) || op == '\\' || op == '^' || - op == DualNamePrefix || op == MultiNamePrefix) { - char path[256]; - char relative[64]; - bool searchable = false; - ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256, - relative, sizeof(relative), &searchable); - nodeIndex = m_ns.FindNode(path); - if (nodeIndex < 0 && searchable) - nodeIndex = m_ns.SearchUpward(relative, ctx.ScopeNode); + if (IsNameStart(op)) { + t.What = Target::Kind::Node; + t.Node = ReadAndResolveName(ctx.Aml, pos, ctx.ScopeNode); return true; } - // Index(source, n) names a package element. This is how firmware - // fills a result package -- Store(x, Index(BST0, 1)) -- so the - // element has to be addressable, not just readable. - if (op == IndexOp) { + // Index(source, n) names an element: how firmware fills a + // result package -- Store(x, Index(BST0, 1)) -- or patches a + // byte of a buffer. + if (op == IndexOp) + return EvalIndexTarget(ctx, pos, t); + + // DerefOf(reference) / RefOf(name) as a target + if (op == DerefOfOp) + return EvalDerefTarget(ctx, pos, t); + if (op == RefOfOp) { (*pos)++; - - // Resolve the source by name where we can, so the write lands - // in the namespace object rather than in a copy of it. - int32_t srcNode = -1; - uint8_t srcOp = (*pos < ctx.AmlBase + ctx.AmlLength) ? ctx.Aml[*pos] : 0; - if (IsLeadNameChar(srcOp) || srcOp == '\\' || srcOp == '^' || - srcOp == DualNamePrefix || srcOp == MultiNamePrefix) { - char path[256]; - char relative[64]; - bool searchable = false; - ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256, - relative, sizeof(relative), &searchable); - srcNode = m_ns.FindNode(path); - if (srcNode < 0 && searchable) - srcNode = m_ns.SearchUpward(relative, ctx.ScopeNode); - } else { - // Locals and arguments can hold a package too. - Object src{}; - EvalTerm(ctx, pos, ctx.AmlBase + ctx.AmlLength, src); - if (src.Type == ObjectType::Package && src.PkgIndex > 0) - pkgStore = src.PkgIndex; - } - - Object idx{}; - EvalTerm(ctx, pos, ctx.AmlBase + ctx.AmlLength, idx); - pkgElement = (int)idx.Integer; - - if (srcNode >= 0) { - auto* srcObj = m_ns.GetNode(srcNode); - if (srcObj && srcObj->Obj.Type == ObjectType::Package) - pkgStore = MaterializeNodePackage(srcNode); - } - - // Index() always carries a target operand of its own -- ZeroOp - // when there is none. It has to be consumed either way, or the - // rest of the method decodes off by however long it was. - if (*pos < ctx.AmlBase + ctx.AmlLength) { - int32_t innerNode = -1; - bool innerLocal = false, innerArg = false; - int innerLocalIdx = 0, innerArgIdx = 0; - int32_t innerStore = -1; int innerElement = 0; - EvalTarget(ctx, pos, innerNode, innerLocal, innerLocalIdx, - innerArg, innerArgIdx, innerStore, innerElement); - } - - if (pkgStore <= 0) { - // Not a package we can write through (a buffer, or an - // unresolvable source): discard the store rather than - // corrupting an unrelated object. - pkgStore = -1; - nodeIndex = -1; - } - return true; + return EvalTarget(ctx, pos, t); } - // DerefOf or other complex target — skip + Abort(ctx, *pos, "Unsupported AML target"); + return false; + } + + bool Interpreter::EvalIndexTarget(ExecContext& ctx, uint32_t* pos, Target& t) { + uint32_t end = ctx.AmlBase + ctx.AmlLength; (*pos)++; + + // Resolve the source by location where we can, so the write + // lands in the object itself rather than in a copy of it. + uint8_t srcOp = (*pos < end) ? ctx.Aml[*pos] : 0; + Object* srcObj = nullptr; + int32_t srcNode = -1; + int32_t tempStore = -1; + if (IsNameStart(srcOp)) { + srcNode = ReadAndResolveName(ctx.Aml, pos, ctx.ScopeNode); + if (auto* n = m_ns.GetNode(srcNode)) srcObj = &n->Obj; + } else if (IsLocalOp(srcOp)) { + srcObj = &ctx.Locals[srcOp - LocalPrefix]; + (*pos)++; + } else if (IsArgOp(srcOp)) { + srcObj = &ctx.Args[srcOp - ArgPrefix]; + (*pos)++; + } else { + Object temp{}; + EvalTerm(ctx, pos, end, temp); + if (temp.Type == ObjectType::Package) tempStore = temp.PkgIndex; + } + + int element; + { + Object idx{}; + EvalTerm(ctx, pos, end, idx); + element = (int)ToInt(idx); + } + + // Index() always carries a target operand of its own -- ZeroOp + // when there is none. It has to be consumed either way, or the + // rest of the method decodes off by however long it was. + if (*pos < end) { + Target inner; + EvalTarget(ctx, pos, inner); + } + + if (srcObj && srcObj->Type == ObjectType::Buffer) { + t.What = Target::Kind::BufByte; + t.Buf = srcObj; + t.Index = element; + return true; + } + if (srcNode >= 0 && srcObj && srcObj->Type == ObjectType::Package) + t.PkgStore = MaterializeNodePackage(srcNode); + else if (srcObj && srcObj->Type == ObjectType::Package && srcObj->PkgIndex > 0) + t.PkgStore = srcObj->PkgIndex; + else if (tempStore > 0) + t.PkgStore = tempStore; + + if (t.PkgStore > 0) { + t.What = Target::Kind::PkgElement; + t.Index = element; + } + // Anything else is not something we can write through: the store + // is discarded rather than corrupting an unrelated object. return true; } + bool Interpreter::EvalDerefTarget(ExecContext& ctx, uint32_t* pos, Target& t) { + (*pos)++; + Object inner{}; + EvalTerm(ctx, pos, ctx.AmlBase + ctx.AmlLength, inner); + int32_t node = ResolveReference(inner, ctx.ScopeNode); + if (node >= 0) { + t.What = Target::Kind::Node; + t.Node = node; + } + return true; + } + + Object Interpreter::ReadTarget(ExecContext& ctx, const Target& t) { + switch (t.What) { + case Target::Kind::Local: + return ctx.Locals[t.Index]; + case Target::Kind::Arg: + return ctx.Args[t.Index]; + case Target::Kind::Node: { + Object v{}; + NodeValue(t.Node, v, ctx.Depth + 1, ctx.ExprDepth); + return v; + } + case Target::Kind::PkgElement: { + Object* slot = PackageStore::Element(t.PkgStore, t.Index); + return slot ? *slot : MakeInteger(0); + } + case Target::Kind::BufByte: + if (t.Buf && t.Index >= 0 && (uint32_t)t.Index < t.Buf->Buffer.Length) + return MakeInteger(t.Buf->Buffer.Data[t.Index]); + return MakeInteger(0); + default: + return MakeInteger(0); + } + } + + void Interpreter::StoreResult(ExecContext& ctx, uint32_t* pos, const Object& value) { + if (*pos >= ctx.AmlBase + ctx.AmlLength) return; + Target t; + if (EvalTarget(ctx, pos, t)) + StoreToTarget(ctx, value, t); + } + // ============================================================================ // StoreToTarget // ============================================================================ - void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value, - int32_t nodeIndex, bool isLocal, int localIdx, - bool isArg, int argIdx, - int32_t pkgStore, int pkgElement) { - if (pkgStore > 0) { - Object* slot = PackageStore::Element(pkgStore, pkgElement); - if (slot) *slot = value; - return; + void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value, const Target& t) { + switch (t.What) { + case Target::Kind::None: + return; + + case Target::Kind::Local: + ctx.Locals[t.Index] = value; + return; + + case Target::Kind::Arg: + ctx.Args[t.Index] = value; + return; + + case Target::Kind::PkgElement: { + Object* slot = PackageStore::Element(t.PkgStore, t.Index); + if (slot) *slot = value; + return; + } + + case Target::Kind::BufByte: + if (t.Buf && t.Buf->Type == ObjectType::Buffer && + t.Index >= 0 && (uint32_t)t.Index < t.Buf->Buffer.Length) + t.Buf->Buffer.Data[t.Index] = (uint8_t)ToInt(value); + return; + + case Target::Kind::Node: + break; } - 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; + auto* node = m_ns.GetNode(t.Node); + if (!node) return; + Object& dst = node->Obj; + + bool scalar = value.Type == ObjectType::Integer || value.Type == ObjectType::String || + value.Type == ObjectType::Buffer; + + switch (dst.Type) { + case ObjectType::Field: + WriteFieldObject(t.Node, value); + return; + + case ObjectType::BufferField: + WriteBufferField(dst, value); + return; + + case ObjectType::Method: + case ObjectType::Device: + case ObjectType::Processor: + case ObjectType::ThermalZone: + case ObjectType::PowerResource: + case ObjectType::OperationRegion: + case ObjectType::Mutex: + case ObjectType::Event: + return; + + // Named data objects keep their type: the value is converted. + case ObjectType::Integer: + if (scalar) { dst.Integer = ToInt(value); return; } + break; + + case ObjectType::Buffer: + if (scalar) { + uint8_t bytes[MaxBufferLen] = {}; + ObjectBytes(value, bytes, MaxBufferLen); + memcpy(dst.Buffer.Data, bytes, dst.Buffer.Length); + return; + } + break; + + case ObjectType::String: + if (scalar) { + if (value.Type == ObjectType::String) dst = value; + else ToStringObject(value, dst); + return; + } + break; + + default: + break; + } + + // Everything else is replaced outright. A package computed by the + // running method lives in the transient ring, which the next + // evaluation rewinds -- copy its elements somewhere permanent. + int32_t keep = (dst.Type == ObjectType::Package) ? dst.PkgIndex : -1; + dst = value; + if (value.Type == ObjectType::Package && value.PkgIndex > 0 && + !PackageStore::IsPersistent(value.PkgIndex)) { + int count = PackageStore::Count(value.PkgIndex); + int32_t store = (keep > 0 && PackageStore::IsPersistent(keep) && + PackageStore::Count(keep) >= count) + ? keep : PackageStore::AllocPersistent(count); + if (store > 0) { + for (int i = 0; i < count; i++) { + Object* from = PackageStore::Element(value.PkgIndex, i); + Object* to = PackageStore::Element(store, i); + if (from && to) *to = *from; } } + dst.PkgIndex = store > 0 ? store : -1; } } @@ -1872,7 +3209,7 @@ namespace Hal { // ============================================================================ bool Interpreter::DecodeConstElement(const uint8_t* data, uint32_t length, - uint32_t* pos, Object& out) { + uint32_t* pos, Object& out) const { out = Object{}; if (*pos >= length) return false; @@ -1880,16 +3217,13 @@ namespace Hal { switch (op) { case ZeroOp: - out.Type = ObjectType::Integer; - out.Integer = 0; + out = MakeInteger(0); return true; case OneOp: - out.Type = ObjectType::Integer; - out.Integer = 1; + out = MakeInteger(1); return true; case OnesOp: - out.Type = ObjectType::Integer; - out.Integer = ~0ULL; + out = MakeInteger(~0ULL); return true; default: break; @@ -1908,8 +3242,7 @@ namespace Hal { for (int i = 0; i < width; i++) value |= (uint64_t)data[*pos + i] << (i * 8); *pos += (uint32_t)width; - out.Type = ObjectType::Integer; - out.Integer = value; + out = MakeInteger(value); return true; } @@ -1927,46 +3260,66 @@ namespace Hal { return true; } - if (op == BufferOp || op == PackageOp || op == VarPackageOp) { + if (op == BufferOp) { uint32_t start = *pos; - uint32_t pkgLen = DecodePkgLength(data, pos); + uint32_t pkgLen = PkgLength(data, pos); uint32_t end = start + pkgLen; if (end > length) end = length; if (end < *pos) return false; - uint32_t dataLen = end - *pos; - if (dataLen > MaxBufferLen) dataLen = MaxBufferLen; - - out.Type = (op == BufferOp) ? ObjectType::Buffer : ObjectType::Package; - out.Buffer.Length = dataLen; - memcpy(out.Buffer.Data, &data[*pos], dataLen); + // BufferSize comes first, then the initializer bytes. + uint32_t size = (uint32_t)ConstInteger(data, pos); + if (size > MaxBufferLen) size = MaxBufferLen; + out.Type = ObjectType::Buffer; + out.Buffer.Length = size; + memset(out.Buffer.Data, 0, size); + uint32_t init = (end > *pos) ? end - *pos : 0; + if (init > size) init = size; + memcpy(out.Buffer.Data, &data[*pos], init); *pos = end; return true; } - // A name reference, local, or anything else we cannot resolve from - // the bytes alone. Skip it and hand back zero. - if (IsLeadNameChar(op) || op == '\\' || op == '^' || - op == DualNamePrefix || op == MultiNamePrefix) { - (*pos)--; - uint32_t p = *pos; - // Walk the NameString by hand: prefixes, then 4-char segments. - while (p < length && (data[p] == '\\' || data[p] == '^')) p++; - if (p < length && data[p] == DualNamePrefix) { - p += 1 + 8; - } else if (p < length && data[p] == MultiNamePrefix) { - uint8_t segs = (p + 1 < length) ? data[p + 1] : 0; - p += 2 + (uint32_t)segs * 4; - } else if (p < length && data[p] == 0x00) { - p += 1; // NullName + if (op == PackageOp || op == VarPackageOp) { + uint32_t start = *pos; + uint32_t pkgLen = PkgLength(data, pos); + uint32_t end = start + pkgLen; + if (end > length) end = length; + if (end < *pos) return false; + + // NumElements: a bare byte for Package, a TermArg (here + // necessarily a constant) for VarPackage. + uint32_t count; + if (op == PackageOp) { + count = (*pos < end) ? data[*pos] : 0; + (*pos)++; } else { - p += 4; + count = (uint32_t)ConstInteger(data, pos); } - *pos = (p > length) ? length : p; + + out.Type = ObjectType::Package; + out.Package.Data = &data[*pos]; + out.Package.Length = (end > *pos) ? end - *pos : 0; + out.Package.Count = count; + *pos = end; + return true; } - out.Type = ObjectType::Integer; - out.Integer = 0; + // A name: keep it as written; the reader resolves it in its scope. + if (IsNameStart(op)) { + (*pos)--; + char text[MaxStringLen]; + DecodeNameText(data, length, pos, text, sizeof(text)); + uint32_t n = 0; + while (text[n]) n++; + SetString(out, text, n); + out.Type = ObjectType::NameRef; + return true; + } + + // A local, or anything else we cannot resolve from the bytes + // alone: zero. + out = MakeInteger(0); return true; } @@ -1974,9 +3327,7 @@ namespace Hal { if (package.Type != ObjectType::Package) return 0; if (package.PkgIndex > 0) return PackageStore::Count(package.PkgIndex); - // Raw form: the first byte of the retained AML is NumElements. - if (package.Buffer.Length < 1) return 0; - return (int)package.Buffer.Data[0]; + return (int)package.Package.Count; } bool Interpreter::GetPackageElement(const Object& package, int index, Object& result) { @@ -1990,38 +3341,45 @@ namespace Hal { return true; } - uint32_t length = package.Buffer.Length; - if (length < 1) return false; - if (index >= (int)package.Buffer.Data[0]) return false; + if (!package.Package.Data || index >= (int)package.Package.Count) return false; - uint32_t pos = 1; // past NumElements + uint32_t length = package.Package.Length; + uint32_t pos = 0; for (int i = 0; i <= index; i++) { - if (!DecodeConstElement(package.Buffer.Data, length, &pos, result)) + // Elements past the initializer list are uninitialized. + if (pos >= length) { result = MakeInteger(0); return true; } + if (!DecodeConstElement(package.Package.Data, length, &pos, result)) return false; } + + // A name naming plain data is as good as the data. + if (result.Type == ObjectType::NameRef) { + int32_t node = ResolveReference(result, m_ns.RootIndex()); + auto* n = m_ns.GetNode(node); + if (n && IsDataType(n->Obj.Type)) result = n->Obj; + } return true; } int32_t Interpreter::MaterializeNodePackage(int32_t nodeIndex) { auto* node = m_ns.GetNode(nodeIndex); if (!node || node->Obj.Type != ObjectType::Package) return -1; - if (node->Obj.PkgIndex > 0) return node->Obj.PkgIndex; + if (node->Obj.PkgIndex > 0 && PackageStore::IsPersistent(node->Obj.PkgIndex)) + return node->Obj.PkgIndex; - int count = GetPackageCount(node->Obj); + int count = (int)node->Obj.Package.Count; if (count <= 0) return -1; if (count > MaxPackageElements) count = MaxPackageElements; int32_t store = PackageStore::AllocPersistent(count); if (store < 0) return -1; - // Seed the elements from the constant bytes the package was - // declared with, so a package that is only partly written through - // Index() keeps its declared values everywhere else. - uint32_t pos = 1; + // Seed the elements from what the package holds now, so a package + // that is only partly written through Index() keeps its declared + // values everywhere else. for (int i = 0; i < count; i++) { Object elem{}; - if (!DecodeConstElement(node->Obj.Buffer.Data, node->Obj.Buffer.Length, - &pos, elem)) + if (!GetPackageElement(node->Obj, i, elem)) break; Object* slot = PackageStore::Element(store, i); if (slot) *slot = elem; @@ -2033,185 +3391,407 @@ namespace Hal { // ============================================================================ - // ReadField + // Fields // ============================================================================ 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; - - uint64_t raw = 0; - if (!ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, raw)) - return false; - - // ReadRegion hands back whole access units starting at byteAddr; - // narrow that to the field's own bits so sub-byte flags (common in - // EC and PM register layouts) don't carry their neighbours along. - raw >>= bitShift; - if (bitLen < 64) - raw &= (1ULL << bitLen) - 1; - - value = raw; + Object v{}; + if (!ReadFieldObject(nodeIndex, v)) return false; + value = ToInt(v); return true; } - // ============================================================================ - - // 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; + return WriteFieldObject(nodeIndex, MakeInteger(value)); + } - int32_t regionIdx = (int32_t)node->Obj.Field.RegionNodeIndex; - auto* regionNode = m_ns.GetNode(regionIdx); - if (!regionNode || regionNode->Obj.Type != ObjectType::OperationRegion) + bool Interpreter::ReadFieldObject(int32_t fieldNode, Object& value) { + auto* node = m_ns.GetNode(fieldNode); + if (!node || node->Obj.Type != ObjectType::Field) { + value = MakeInteger(0); 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; + uint8_t bits[MaxBufferLen] = {}; + bool ok = AccessField(fieldNode, false, bits); - // 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; + if (bitLen <= 64) { + value = MakeInteger(GetBits(bits, 0, bitLen)); + } else { + SetBuffer(value, bits, MinU32((bitLen + 7) / 8, MaxBufferLen)); + } + return ok; + } + + bool Interpreter::WriteFieldObject(int32_t fieldNode, const Object& value) { + auto* node = m_ns.GetNode(fieldNode); + if (!node || node->Obj.Type != ObjectType::Field) return false; + + uint8_t bits[MaxBufferLen] = {}; + ObjectBytes(value, bits, MaxBufferLen); + return AccessField(fieldNode, true, bits); + } + + // Read or write a field through its region in units of its access + // width, the way ACPI defines it: a DWordAcc field is only ever + // touched with aligned 32-bit accesses (MMIO register files -- GPIO + // pads, PCH private config -- do not decode narrower ones), and the + // bits of a unit that lie outside the field follow its update rule. + bool Interpreter::AccessField(int32_t fieldNode, bool write, uint8_t* bits) { + auto* node = m_ns.GetNode(fieldNode); + if (!node || node->Obj.Type != ObjectType::Field) return false; + const Object field = node->Obj; + + uint32_t bitOff = field.Field.BitOffset; + uint32_t bitLen = field.Field.BitLength; + if (bitLen == 0) return true; + if (bitLen > MaxBufferLen * 8) bitLen = MaxBufferLen * 8; + + bool ecRegion = false; + if (field.Field.Kind != (uint8_t)FieldKind::Index) { + auto* region = m_ns.GetNode((int32_t)field.Field.RegionNodeIndex); + if (!region || region->Obj.Type != ObjectType::OperationRegion) return false; + ecRegion = region->Obj.Region.Space == RegionSpace::EmbeddedControl; } - return WriteRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value); + uint32_t width; + switch (field.Field.AccessType) { + case 1: width = 1; break; + case 2: width = 2; break; + case 3: width = 4; break; + case 4: width = 8; break; + default: { + // AnyAcc / BufferAcc: the narrowest aligned unit that holds + // the whole field, up to a dword. + width = 4; + for (uint32_t w = 1; w <= 4; w *= 2) { + if (bitOff / (w * 8) == (bitOff + bitLen - 1) / (w * 8)) { + width = w; + break; + } + } + break; + } + } + if (ecRegion) width = 1; + + uint32_t unitBits = width * 8; + uint64_t unitMask = (width == 8) ? ~0ULL : ((1ULL << unitBits) - 1); + uint32_t firstUnit = bitOff / unitBits; + uint32_t lastUnit = (bitOff + bitLen - 1) / unitBits; + + for (uint32_t u = firstUnit; u <= lastUnit; u++) { + uint32_t unitStart = u * unitBits; + uint32_t lo = bitOff > unitStart ? bitOff : unitStart; + uint32_t hiField = bitOff + bitLen; + uint32_t hi = hiField < unitStart + unitBits ? hiField : unitStart + unitBits; + uint32_t n = hi - lo; + uint32_t shift = lo - unitStart; + uint32_t fieldPos = lo - bitOff; + uint64_t low = (n == 64) ? ~0ULL : ((1ULL << n) - 1); + uint64_t mask = low << shift; + uint64_t byteOffset = (uint64_t)u * width; + + if (!write) { + uint64_t v = 0; + if (!FieldUnitIo(field, byteOffset, width, false, v)) return false; + PutBits(bits, fieldPos, n, (v >> shift) & low); + continue; + } + + uint64_t newBits = GetBits(bits, fieldPos, n) << shift; + uint64_t v; + if (mask == unitMask) { + v = newBits; + } else { + switch (field.Field.UpdateRule) { + case 1: v = (unitMask & ~mask) | newBits; break; // WriteAsOnes + case 2: v = newBits; break; // WriteAsZeros + default: { // Preserve + uint64_t old = 0; + if (!FieldUnitIo(field, byteOffset, width, false, old)) return false; + v = (old & ~mask) | newBits; + break; + } + } + } + if (!FieldUnitIo(field, byteOffset, width, true, v)) return false; + } + return true; + } + + bool Interpreter::FieldUnitIo(const Object& field, uint64_t byteOffset, uint32_t width, + bool write, uint64_t& value) { + switch ((FieldKind)field.Field.Kind) { + case FieldKind::Index: { + // Select the unit through the index register, then move + // it through the data register. + if (!WriteFieldObject((int32_t)field.Field.RegionNodeIndex, + MakeInteger(byteOffset))) + return false; + if (write) + return WriteFieldObject(field.Field.DataNodeIndex, MakeInteger(value)); + Object v{}; + if (!ReadFieldObject(field.Field.DataNodeIndex, v)) return false; + value = ToInt(v); + return true; + } + + case FieldKind::Bank: + if (!WriteFieldObject(field.Field.BankNodeIndex, + MakeInteger(field.Field.BankValue))) + return false; + return RegionIo((int32_t)field.Field.RegionNodeIndex, byteOffset, width, + write, value); + + default: + return RegionIo((int32_t)field.Field.RegionNodeIndex, byteOffset, width, + write, value); + } } // ============================================================================ - // ReadRegion + // Region access // ============================================================================ - bool Interpreter::ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, + bool Interpreter::ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t& value) { - value = 0; + return RegionIo(regionNode, offset, width, false, value); + } - // Round up to access width - uint32_t accessBytes = (bitWidth + 7) / 8; - if (accessBytes > 8) accessBytes = 8; + bool Interpreter::WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width, + uint64_t value) { + return RegionIo(regionNode, offset, width, true, value); + } - 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; - } + bool Interpreter::RegionIo(int32_t regionNode, uint64_t offset, uint32_t width, + bool write, uint64_t& value) { + auto* node = m_ns.GetNode(regionNode); + if (!node || node->Obj.Type != ObjectType::OperationRegion) return false; - if (space == RegionSpace::SystemMemory) { - // The offset comes from firmware AML and may be nonsense: a - // region we mis-decoded, or one whose base is computed by code - // we do not fully execute. Dereferencing that through the HHDM - // is how a bad table takes the kernel down with a #GP, so the - // address has to be inside what Init() actually mapped. - if (!HhdmReadable(address, accessBytes)) return false; + RegionSpace space = node->Obj.Region.Space; + uint64_t base = node->Obj.Region.Offset; + uint64_t length = node->Obj.Region.Length; - 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; - } + // Never touch past the region's declared end: that is where a + // mis-decoded field would otherwise land. + if (offset >= length) return false; - if (space == RegionSpace::EmbeddedControl) { - // The EC address space is a flat 256-byte array reached one - // byte at a time over the command/data port pair, so a wider - // field is assembled little-endian from consecutive reads. - for (uint32_t i = 0; i < accessBytes; i++) { - uint8_t byte = 0; - if (!EmbeddedController::Read((uint8_t)(address + i), byte)) - return false; - value |= (uint64_t)byte << (i * 8); + if (!write) value = 0; + + switch (space) { + case RegionSpace::SystemIO: { + uint64_t port = base + offset; + if (port > 0xFFFF) return false; + for (uint32_t done = 0; done < width; ) { + uint32_t step = (width - done >= 4) ? 4 : (width - done >= 2 ? 2 : 1); + uint16_t p = (uint16_t)(port + done); + uint64_t part = write ? (value >> (done * 8)) : 0; + if (step == 4) { + if (write) Io::Out32((uint32_t)part, p); else part = Io::In32(p); + } else if (step == 2) { + if (write) Io::Out16((uint16_t)part, p); else part = Io::In16(p); + } else { + if (write) Io::Out8((uint8_t)part, p); else part = Io::In8(p); + } + if (!write) value |= (part & ((step == 4) ? 0xFFFFFFFFULL : (step == 2 ? 0xFFFFULL : 0xFFULL))) << (done * 8); + done += step; + } + return true; } - return true; - } - if (space == RegionSpace::PciConfig) { - // PCI config space access -- not yet implemented + case RegionSpace::SystemMemory: + // The offset comes from firmware AML and may be nonsense: + // a region we mis-decoded, or one whose base is computed + // by code we do not fully execute. MemoryIo vets it. + return MemoryIo(base + offset, width, write, value); + + case RegionSpace::EmbeddedControl: { + // The EC address space is a flat 256-byte array reached one + // byte at a time over the command/data port pair, so a wider + // field is assembled little-endian from consecutive reads. + for (uint32_t i = 0; i < width; i++) { + uint8_t addr = (uint8_t)(base + offset + i); + if (write) { + if (!EmbeddedController::Write(addr, (uint8_t)(value >> (i * 8)))) + return false; + } else { + uint8_t byte = 0; + if (!EmbeddedController::Read(addr, byte)) return false; + value |= (uint64_t)byte << (i * 8); + } + } + return true; + } + + case RegionSpace::PciConfig: { + // The function is the one the region's device sits at: + // its _ADR on the bus behind its parent bridge. + uint8_t bus, dev, fn; + if (!ResolvePciAddress(regionNode, bus, dev, fn, 0)) { + if (FirstTime(0x60000u ^ (uint32_t)regionNode)) { + char path[128]; + m_ns.GetNodePath(regionNode, path, sizeof(path)); + KernelLogStream(WARNING, "AML") << "Cannot place PCI_Config region " + << path << " on a PCI function"; + } + return false; + } + + uint64_t reg = base + offset; + for (uint32_t done = 0; done < width; ) { + uint32_t step = (width - done >= 4 && ((reg + done) & 3) == 0) ? 4 + : (width - done >= 2 && ((reg + done) & 1) == 0) ? 2 : 1; + uint16_t at = (uint16_t)(reg + done); + if (reg + done > 0xFFF) return false; + if (write) { + uint32_t part = (uint32_t)(value >> (done * 8)); + if (!Pci::ConfigWrite(bus, dev, fn, at, (uint8_t)step, part)) + return false; + } else { + uint64_t part = Pci::ConfigRead(bus, dev, fn, at, (uint8_t)step); + uint64_t m = (step == 4) ? 0xFFFFFFFFULL : (step == 2 ? 0xFFFFULL : 0xFFULL); + value |= (part & m) << (done * 8); + } + done += step; + } + return true; + } + + case RegionSpace::CMOS: { + for (uint32_t i = 0; i < width; i++) { + uint64_t index = base + offset + i; + if (index > 0xFF) return false; + uint16_t idxPort = index < 0x80 ? 0x70 : 0x72; + uint16_t dataPort = index < 0x80 ? 0x71 : 0x73; + Io::Out8((uint8_t)(index & 0x7F), idxPort); + if (write) Io::Out8((uint8_t)(value >> (i * 8)), dataPort); + else value |= (uint64_t)Io::In8(dataPort) << (i * 8); + } + return true; + } + + default: + if (FirstTime(0x70000u | (uint32_t)space)) { + KernelLogStream(WARNING, "AML") << "Operation region space " + << base::hex << (uint64_t)space << " is not supported"; + } + return false; + } + } + + // ============================================================================ + + // PCI device placement + + // ============================================================================ + bool Interpreter::IntegerChild(int32_t device, const char* seg, uint64_t fallback, + uint64_t& value, int depth) { + value = fallback; + int32_t child = m_ns.FindChild(device, seg); + if (child < 0) return false; + Object v{}; + if (!NodeValue(child, v, depth, 0)) return false; + value = ToInt(v); + return true; + } + + // _HID/_CID matching for the PCI root bridge IDs. An EISA ID integer + // is decoded to its 7-character text form first. + static bool IsRootBridgeId(const Object& id) { + char text[8] = {}; + if (id.Type == ObjectType::Integer) { + uint32_t raw = (uint32_t)id.Integer; + uint32_t v = ((raw & 0xFF) << 24) | ((raw & 0xFF00) << 8) | + ((raw >> 8) & 0xFF00) | (raw >> 24); + text[0] = (char)('@' + ((v >> 26) & 0x1F)); + text[1] = (char)('@' + ((v >> 21) & 0x1F)); + text[2] = (char)('@' + ((v >> 16) & 0x1F)); + uint32_t n = 3; + AppendHex(text, n, 7, v & 0xFFFF, 4); + } else if (id.Type == ObjectType::String) { + for (int i = 0; i < 7 && i < id.String.Length; i++) text[i] = id.String.Data[i]; + } else { return false; } + return memcmp(text, "PNP0A03", 7) == 0 || memcmp(text, "PNP0A08", 7) == 0; + } + bool Interpreter::IsPciRootBridge(int32_t device, int depth) { + int32_t hid = m_ns.FindChild(device, "_HID"); + if (hid >= 0) { + Object v{}; + if (NodeValue(hid, v, depth, 0) && IsRootBridgeId(v)) return true; + } + int32_t cid = m_ns.FindChild(device, "_CID"); + if (cid >= 0) { + Object v{}; + if (!NodeValue(cid, v, depth, 0)) return false; + if (v.Type != ObjectType::Package) return IsRootBridgeId(v); + int count = GetPackageCount(v); + for (int i = 0; i < count; i++) { + Object e{}; + if (GetPackageElement(v, i, e) && IsRootBridgeId(e)) return true; + } + } return false; } - // ============================================================================ + bool Interpreter::ResolvePciAddress(int32_t node, uint8_t& bus, uint8_t& dev, + uint8_t& fn, int depth) { + if (depth > 8) return false; - // WriteRegion + // The device the region (or method) belongs to. + int32_t d = node; + while (d >= 0) { + auto* n = m_ns.GetNode(d); + if (!n) return false; + if (n->Obj.Type == ObjectType::Device) break; + d = n->ParentIndex; + } + if (d < 0) return false; - // ============================================================================ - 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; + uint64_t adr = 0; + IntegerChild(d, "_ADR", 0, adr, depth); + dev = (uint8_t)((adr >> 16) & 0x1F); + fn = (uint8_t)((adr & 0xFFFF) == 0xFFFF ? 0 : (adr & 0x7)); - 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); + if (IsPciRootBridge(d, depth)) { + uint64_t bbn = 0; + IntegerChild(d, "_BBN", 0, bbn, depth); + bus = (uint8_t)bbn; return true; } - if (space == RegionSpace::SystemMemory) { - if (!HhdmReadable(address, accessBytes)) return false; + // The bus is the one behind the parent: a root bridge's _BBN, or + // a PCI-to-PCI bridge's secondary bus number. + auto* dn = m_ns.GetNode(d); + int32_t p = dn ? dn->ParentIndex : -1; + while (p >= 0) { + auto* n = m_ns.GetNode(p); + if (!n) return false; + if (n->Obj.Type == ObjectType::Device) break; + p = n->ParentIndex; + } + if (p < 0) { bus = 0; return true; } - 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; + if (IsPciRootBridge(p, depth)) { + uint64_t bbn = 0; + IntegerChild(p, "_BBN", 0, bbn, depth); + bus = (uint8_t)bbn; return true; } - if (space == RegionSpace::EmbeddedControl) { - for (uint32_t i = 0; i < accessBytes; i++) { - if (!EmbeddedController::Write((uint8_t)(address + i), - (uint8_t)(value >> (i * 8)))) - return false; - } - return true; - } - - return false; + uint8_t pb, pd, pf; + if (!ResolvePciAddress(p, pb, pd, pf, depth + 1)) return false; + uint32_t header = Pci::ConfigRead(pb, pd, pf, 0x0E, 1); + if (header == 0xFFFFFFFF || (header & 0x7F) != 0x01) return false; + uint32_t secondary = Pci::ConfigRead(pb, pd, pf, Pci::PCI_REG_SECONDARY_BUS, 1); + if (secondary == 0 || secondary > 0xFF || secondary == 0xFF) return false; + bus = (uint8_t)secondary; + return true; } }; diff --git a/kernel/src/ACPI/AML/AmlInterpreter.hpp b/kernel/src/ACPI/AML/AmlInterpreter.hpp index 960fce9..5542109 100644 --- a/kernel/src/ACPI/AML/AmlInterpreter.hpp +++ b/kernel/src/ACPI/AML/AmlInterpreter.hpp @@ -97,6 +97,35 @@ namespace Hal { static constexpr uint8_t ConcatResOp = 0x84; static constexpr uint8_t ToHexStringOp = 0x98; static constexpr uint8_t ToDecimalStringOp = 0x97; + static constexpr uint8_t ExternalOp = 0x15; + static constexpr uint8_t RefOfOp = 0x71; + static constexpr uint8_t FindSetLeftBitOp = 0x81; + static constexpr uint8_t FindSetRightBitOp = 0x82; + static constexpr uint8_t NotifyOp = 0x86; + static constexpr uint8_t MatchOp = 0x89; + static constexpr uint8_t ObjectTypeOp = 0x8E; + static constexpr uint8_t CreateQWordFieldOp = 0x8F; + static constexpr uint8_t ToStringOp = 0x9C; + static constexpr uint8_t CopyObjectOp = 0x9D; + static constexpr uint8_t MidOp = 0x9E; + static constexpr uint8_t ContinueOp = 0x9F; + static constexpr uint8_t BreakPointOp = 0xCC; + + // More extended opcodes (after ExtOpPrefix) + static constexpr uint8_t CondRefOfOp = 0x12; + static constexpr uint8_t CreateFieldOp = 0x13; + static constexpr uint8_t LoadTableOp = 0x1F; + static constexpr uint8_t LoadOp = 0x20; + static constexpr uint8_t SignalOp = 0x24; + static constexpr uint8_t WaitOp = 0x25; + static constexpr uint8_t ResetOp = 0x26; + static constexpr uint8_t FromBCDOp = 0x28; + static constexpr uint8_t ToBCDOp = 0x29; + static constexpr uint8_t UnloadOp = 0x2A; + static constexpr uint8_t DebugOp = 0x31; + static constexpr uint8_t FatalOp = 0x32; + static constexpr uint8_t TimerOp = 0x33; + static constexpr uint8_t DataRegionOp = 0x88; // ============================================================================ @@ -104,7 +133,11 @@ namespace Hal { // ============================================================================ static constexpr int MaxCallDepth = 16; - static constexpr int MaxLoopIterations = 1024; + + // ACPICA's ceiling. Firmware polls hardware in While loops with a + // Sleep or Stall in the body (link training, power-good, L2/L3 ready) + // and expects more than a token number of passes. + static constexpr int MaxLoopIterations = 0xFFFF; // Backstop cap on evaluation nesting. This counter is shared by // expression evaluation AND block execution, and block nesting is @@ -133,8 +166,10 @@ namespace Hal { public: Interpreter(); - // Parse a DSDT or SSDT table into the namespace. - // tableData points to the CommonSDTHeader (HHDM-mapped). + // Parse a DSDT or SSDT table into the namespace. Tables may be + // loaded one after another; each method remembers which table + // its body lives in. tableData points to the CommonSDTHeader + // (HHDM-mapped) and must stay mapped. bool LoadTable(void* tableData); // Evaluate a named object, returning its value. @@ -144,6 +179,28 @@ namespace Hal { // Evaluate a method with arguments. bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result); + // Evaluate a node: a method is run with `args`, a field is read, + // anything else hands back its value (non-data objects as a + // Reference to the node). + bool EvaluateNode(int32_t node, const Object* args, int argCount, Object& result); + + // Find the child of `parent` named `seg` ("_PS3", "_ON"), -1 if + // there is none. Short names are padded with '_'. + int32_t FindChild(int32_t parent, const char* seg) const; + + // EvaluateNode() on a named child. False when it does not exist. + bool EvaluateChild(int32_t parent, const char* seg, const Object* args, + int argCount, Object& result); + + // Turn a Reference, or a NameRef/String naming an object relative + // to `scopeNode`, into a node index. -1 when it names nothing. + int32_t ResolveReference(const Object& obj, int32_t scopeNode) const; + + // The PCI function a Device node describes: its _ADR on the bus + // behind its parent (a root bridge's _BBN, or a PCI-to-PCI + // bridge's secondary bus). False when it cannot be placed. + bool GetPciAddress(int32_t deviceNode, uint8_t& bus, uint8_t& dev, uint8_t& fn); + // Read a field value. Returns integer. bool ReadField(int32_t nodeIndex, uint64_t& value); @@ -157,6 +214,10 @@ namespace Hal { // Check if the interpreter has been initialized bool IsInitialized() const { return m_initialized; } + // Nodes [0, DsdtNodeCount()) came from the DSDT. Nodes past it were + // added by SSDTs (or by methods run since). + int32_t DsdtNodeCount() const { return m_dsdtNodeCount; } + // Log every opcode executed, with its offset, for the next // `budget` opcodes. The only way to see where a method that runs to // completion without returning a value actually goes. @@ -170,8 +231,10 @@ namespace Hal { // Read one element of a Package object. Works for both a package // whose elements were computed at run time (materialized in the // package store) and one that is still just the constant AML bytes - // it was declared with. Returns false when the object is not a - // package or the index is out of range. + // it was declared with. A name in a constant package comes back as + // a NameRef unless it resolves (from the root) to plain data, in + // which case its value is returned. Returns false when the object + // is not a package or the index is out of range. bool GetPackageElement(const Object& package, int index, Object& result); // Number of elements a Package declares. @@ -183,17 +246,35 @@ namespace Hal { int32_t MaterializeNodePackage(int32_t nodeIndex); private: + struct ExecContext; + // ============================================================================ // Parsing (table load) // ============================================================================ + // ctx is the method being executed when a definition appears in + // a method body, nullptr at table load. Operands that are TermArgs + // (region offsets, buffer sizes, bank values) are evaluated in it. 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); + int32_t scopeNode, ExecContext* ctx = nullptr); bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, - int32_t scopeNode); + int32_t scopeNode, ExecContext* ctx = nullptr); + + // The FieldList shared by Field, IndexField and BankField. + void ParseFieldList(const uint8_t* aml, uint32_t* pos, uint32_t fieldEnd, + int32_t scopeNode, uint8_t fieldFlags, const Object& proto); + + // Evaluate a TermArg operand of a definition: in the running + // method, or in the table-load context. + bool EvalOperand(ExecContext* ctx, const uint8_t* aml, uint32_t* pos, + uint32_t endOffset, int32_t scopeNode, Object& result); + + // CreateBitField .. CreateQWordField and CreateField. + bool CreateBufferField(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + uint8_t op); // ============================================================================ @@ -214,6 +295,10 @@ namespace Hal { char* outRelative = nullptr, int relLen = 0, bool* outSearchable = nullptr); + // Read a NameString and resolve it with the search rule. -1 when + // it names nothing. + int32_t ReadAndResolveName(const uint8_t* aml, uint32_t* pos, int32_t scopeNode); + // Read a single 4-char NameSeg from AML. Advances *pos. void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg); @@ -225,11 +310,10 @@ namespace Hal { uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos); // Decode one constant package element out of raw AML bytes and - // advance *pos past it. Non-constant elements (name references, - // locals) decode to integer 0, which is what they were worth - // before the package store existed. + // advance *pos past it. Names come back as NameRef; locals and + // anything else that needs a running method decode to integer 0. bool DecodeConstElement(const uint8_t* data, uint32_t length, - uint32_t* pos, Object& out); + uint32_t* pos, Object& out) const; uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos); // ============================================================================ @@ -247,6 +331,8 @@ namespace Hal { Object ReturnValue; bool Returned; bool Broken; + bool Continued; + bool Aborted; // undecodable AML: unwind the method int Depth; // method-call nesting int ExprDepth; // expression/block nesting }; @@ -259,36 +345,117 @@ namespace Hal { ~DepthGuard() { Value--; } }; + // Where a Store lands. + struct Target { + enum class Kind : uint8_t { None, Node, Local, Arg, PkgElement, BufByte }; + Kind What = Kind::None; + int32_t Node = -1; + int Index = 0; // local/arg number, or element index + int32_t PkgStore = -1; + Object* Buf = nullptr; // BufByte: the buffer being indexed + }; + 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. - // A target may also be Index(package, n), which names a package - // element rather than a node, local or arg. pkgStore/pkgElement - // carry that case (pkgStore > 0 when it applies). - bool EvalTarget(ExecContext& ctx, uint32_t* pos, - int32_t& nodeIndex, bool& isLocal, int& localIdx, - bool& isArg, int& argIdx, - int32_t& pkgStore, int& pkgElement); + // Extended (0x5B-prefixed) opcodes in expression or statement position. + bool EvalExtOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); - // 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, - int32_t pkgStore = -1, int pkgElement = 0); + // EvalTerm's opcode handlers. They live out of line so the + // recursive path only carries the locals of the one running. + bool EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalPackageOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalBinary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalUnary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalDivide(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalStoreOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalReference(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalIndexOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalMatchOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalMidOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool EvalNameRef(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); + bool CallMethod(ExecContext& ctx, int32_t node, uint32_t* pos, uint32_t endOffset, + Object& result); + bool IndexElement(const Object& source, uint64_t idx, Object& out); + void ReportUnresolved(ExecContext& ctx, uint32_t namePos); + + // Target forms that need operand evaluation of their own. + bool EvalIndexTarget(ExecContext& ctx, uint32_t* pos, Target& target); + bool EvalDerefTarget(ExecContext& ctx, uint32_t* pos, Target& target); + + // A package element: like EvalTerm, except that a name is a + // reference to the object rather than a call or a read. + bool EvalPackageElement(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, + Object& result); + + // Parse a SuperName/Target operand. + bool EvalTarget(ExecContext& ctx, uint32_t* pos, Target& target); + + // Store a value to a target, with ACPI's implicit conversion for + // named data objects. + void StoreToTarget(ExecContext& ctx, const Object& value, const Target& target); + + // Parse a Target operand and store `value` there. + void StoreResult(ExecContext& ctx, uint32_t* pos, const Object& value); + + // The current value behind a target (for Increment and friends). + Object ReadTarget(ExecContext& ctx, const Target& target); + + // Run a method node. Caller holds m_evalLock. + bool InvokeMethodNode(int32_t node, const Object* args, int argCount, + Object& result, int depth, int exprDepth); + + // Run a method in a context already acquired and holding its + // arguments; releases the context. + bool RunMethodIn(ExecContext* ctx, int32_t node, int depth, int exprDepth, + Object& result); + + // The value of a node as an operand: fields read, methods run + // without arguments, non-data objects become References. + bool NodeValue(int32_t node, Object& result, int depth, int exprDepth); + + // Log an opcode we cannot decode and stop the method: carrying on + // past it would execute operand bytes as code. + bool Abort(ExecContext& ctx, uint32_t pos, const char* why); + + // Native methods (\_OSI). + bool RunNativeMethod(uint8_t which, const Object* args, int argCount, Object& result); + + // Install the OS-defined objects (\_OSI, \_REV, \_OS_, \_GL_). + void InstallPredefined(); + + // Device identification (for PCI_Config regions). + bool IntegerChild(int32_t device, const char* seg, uint64_t fallback, + uint64_t& value, int depth); + bool IsPciRootBridge(int32_t device, int depth); + bool ResolvePciAddress(int32_t node, uint8_t& bus, uint8_t& dev, + uint8_t& fn, int depth); // ============================================================================ // 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); + // Access `bits` (little-endian, bitLength long) of a field unit. + bool AccessField(int32_t fieldNode, bool write, uint8_t* bits); + bool ReadFieldObject(int32_t fieldNode, Object& value); + bool WriteFieldObject(int32_t fieldNode, const Object& value); + + // One aligned access unit of a field's backing store. + bool FieldUnitIo(const Object& field, uint64_t byteOffset, uint32_t width, + bool write, uint64_t& value); + + // Region access at byte `offset` into the region, `width` bytes. + bool ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t& value); + bool WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t value); + bool RegionIo(int32_t regionNode, uint64_t offset, uint32_t width, + bool write, uint64_t& value); + + bool ReadBufferField(const Object& field, Object& value); + bool WriteBufferField(const Object& field, const Object& value); // ============================================================================ @@ -310,16 +477,23 @@ namespace Hal { ExecContext* AcquireContext(); void ReleaseContext(); + // Run `body` as a top-level evaluation: serialized, with the stack + // budget anchored here. + template bool TopLevel(Fn body); + Namespace m_ns; - const uint8_t* m_dsdt; - uint32_t m_dsdtLength; bool m_initialized; + bool m_predefined; + int32_t m_dsdtNodeCount; ExecContext* m_ctxPool; // MaxCallDepth + 1 entries, heap int m_ctxInUse; int m_evalNesting; // top-level evaluations in progress uint64_t m_stackFloor; // stack address the interpreter stops at int m_traceBudget; // opcodes left to log, 0 = off + ExecContext* m_loadCtx; // table-load operand context + uint64_t m_delayedUs; // total Sleep/Stall time requested + int m_methodScan; // >0 while scanning a method body at load kcp::Mutex m_evalLock; }; diff --git a/kernel/src/ACPI/AML/AmlNamespace.cpp b/kernel/src/ACPI/AML/AmlNamespace.cpp index 9c03207..c5e34c0 100644 --- a/kernel/src/ACPI/AML/AmlNamespace.cpp +++ b/kernel/src/ACPI/AML/AmlNamespace.cpp @@ -89,6 +89,10 @@ namespace Hal { s_transientNext = 0; } + bool IsPersistent(int32_t index) { + return index > 0 && index <= MaxPersistent; + } + static Entry* Lookup(int32_t index) { if (index <= 0) return nullptr; int32_t i = index - 1; @@ -293,6 +297,12 @@ namespace Hal { return -1; } + int32_t Namespace::FindChild(int32_t parentIndex, const char* seg) const { + char padded[MaxNameSegLen + 1]; + PadSegment(seg, padded); + return FindChildByName(parentIndex, padded); + } + int32_t Namespace::CreateNode(const char* absolutePath) { EnsureRoot(); char segments[MaxPathDepth][MaxNameSegLen + 1]; diff --git a/kernel/src/ACPI/AML/AmlNamespace.hpp b/kernel/src/ACPI/AML/AmlNamespace.hpp index 218f006..d505b72 100644 --- a/kernel/src/ACPI/AML/AmlNamespace.hpp +++ b/kernel/src/ACPI/AML/AmlNamespace.hpp @@ -31,6 +31,16 @@ namespace Hal { ThermalZone, PowerResource, BufferField, + Event, + // A reference to a namespace node: what RefOf() yields, and what a + // name evaluates to when it names something that is not data (a + // device or power resource listed in a _PRx package, say). + Reference, + // A name inside a constant package, kept as written ("PG00", + // "\_SB.PCI0.PEG0") because its scope is only known to whoever + // reads the package. Interpreter::ResolveReference() turns it + // into a node. + NameRef, }; // ============================================================================ @@ -63,7 +73,7 @@ namespace Hal { static constexpr int InitialChildren = 8; static constexpr int MaxStringLen = 64; static constexpr int MaxBufferLen = 256; - static constexpr int MaxPackageElements = 32; + static constexpr int MaxPackageElements = 64; static constexpr int MaxMethodArgs = 7; static constexpr int MaxMethodLocals = 8; @@ -102,10 +112,23 @@ namespace Hal { uint32_t Length; } Buffer; + // A package's constant form: its element bytes, left in the + // table they were declared in, and the declared element count. ACPI tables stay + // mapped for the life of the kernel, so pointing at them is + // both cheaper and free of the size cap a copy would carry -- + // a GPIO community table runs well past MaxBufferLen. + struct { + const uint8_t* Data; // first element + uint32_t Length; // bytes of element encodings + uint32_t Count; // NumElements + } Package; + 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 + uint8_t Native; // NativeMethod; 0 = AML + const uint8_t* Table; // the table the body lives in + uint32_t AmlOffset; // offset into Table where the method body starts uint32_t AmlLength; // length of the method body } Method; @@ -116,12 +139,33 @@ namespace Hal { } Region; struct { - uint32_t RegionNodeIndex; // index of the parent OperationRegion node + // Field / BankField: the OperationRegion node. + // IndexField: the index register field. + uint32_t RegionNodeIndex; uint32_t BitOffset; uint32_t BitLength; uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer + uint8_t UpdateRule; // 0=Preserve, 1=WriteAsOnes, 2=WriteAsZeros + uint8_t Kind; // FieldKind + int32_t DataNodeIndex; // IndexField: the data register field + int32_t BankNodeIndex; // BankField: the bank select field + uint64_t BankValue; // BankField: value selecting this bank } Field; + // CreateXxxField over a buffer. Source points at the object + // holding the buffer -- a namespace node's, or a local/arg slot + // of the method that created the field -- so reads and writes + // go through to it. + struct { + Object* Source; + uint32_t BitOffset; + uint32_t BitLength; + } BufferField; + + struct { + int32_t Node; + } Reference; + struct { uint8_t ProcId; uint32_t PblkAddr; @@ -132,6 +176,18 @@ namespace Hal { Object() : Type(ObjectType::None), PkgIndex(-1), Integer(0) {} }; + enum class FieldKind : uint8_t { + Normal = 0, + Index = 1, + Bank = 2, + }; + + // Methods the OS provides rather than the firmware. + enum class NativeMethod : uint8_t { + None = 0, + Osi = 1, // \_OSI + }; + // ============================================================================ // Package element store @@ -155,6 +211,9 @@ namespace Hal { // Rewind the transient ring. Called once per top-level evaluation. void ResetTransient(); + // True for an entry that outlives the evaluation that made it. + bool IsPersistent(int32_t index); + // Element access. Returns nullptr for an unknown entry or an // out-of-range element. Object* Element(int32_t index, int element); @@ -219,6 +278,10 @@ namespace Hal { // rebased onto a scope. Returns -1 if no scope contains it. int32_t SearchUpward(const char* relative, int32_t scopeNodeIndex) const; + // Find the child of `parent` named `seg`; a short name is padded + // with '_' the way node names are stored. Returns -1 if absent. + int32_t FindChild(int32_t parentIndex, const char* seg) const; + // Get a node by index. NamespaceNode* GetNode(int32_t index); const NamespaceNode* GetNode(int32_t index) const; diff --git a/kernel/src/ACPI/AML/AmlParser.cpp b/kernel/src/ACPI/AML/AmlParser.cpp index 3c5d324..a2c7909 100644 --- a/kernel/src/ACPI/AML/AmlParser.cpp +++ b/kernel/src/ACPI/AML/AmlParser.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include using namespace Kt; @@ -239,15 +241,144 @@ namespace Hal { // Full interpreter initialization // ============================================================================ - void InitializeInterpreter(void* dsdtData) { + void InitializeInterpreter(void* dsdtData, ACPI::CommonSDTHeader* xsdt) { auto& interp = GetInterpreter(); if (!interp.LoadTable(dsdtData)) { KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter"; } + + // SSDTs extend the DSDT's namespace and are loaded after it, in + // XSDT order. They are where most laptops keep CPU power tables, + // and -- the reason they matter here -- the discrete GPU's power + // control (_PR3 power resources, _PS3, NVIDIA/AMD _DSM). + int ssdts = 0; + if (xsdt) { + uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8; + auto* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader)); + for (uint32_t i = 0; i < entryCount; i++) { + if (entries[i] == 0) continue; + auto* table = (ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]); + const char* sig = table->Signature; + bool isSsdt = sig[0] == 'S' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T'; + bool isPsdt = sig[0] == 'P' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T'; + if (!isSsdt && !isPsdt) continue; + if (table->Length <= sizeof(ACPI::CommonSDTHeader)) continue; + if (interp.LoadTable(table)) ssdts++; + } + } + KernelLogStream(OK, "AML") << "Loaded DSDT + " << base::dec << (uint64_t)ssdts + << " SSDT(s)"; + auto& ns = interp.GetNamespace(); KernelLogStream(OK, "AML") << "Namespace: " << base::dec << (uint64_t)ns.NodeCount() << " / " << (uint64_t)ns.MaxCapacity() << " nodes (dynamic)"; } + + // ============================================================================ + + // Namespace initialization (_REG, _INI) + + // ============================================================================ + static bool SegIs(const char* name, const char* seg) { + return name[0] == seg[0] && name[1] == seg[1] && + name[2] == seg[2] && name[3] == seg[3]; + } + + void InitializeNamespaceObjects() { + auto& interp = GetInterpreter(); + if (!interp.IsInitialized()) return; + auto& ns = interp.GetNamespace(); + + // Collect first, evaluate second: executing AML creates nodes, + // which reallocates a parent's child array under a live walk. + int32_t capacity = ns.NodeCount(); + auto* regs = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity); + auto* devices = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity); + if (!regs || !devices) { + if (regs) Memory::g_heap->Free(regs); + if (devices) Memory::g_heap->Free(devices); + return; + } + int regCount = 0, deviceCount = 0; + + ns.WalkDescendants(ns.RootIndex(), ObjectType::None, + [&](int32_t idx, const NamespaceNode* node) { + if (node->Obj.Type == ObjectType::Method && SegIs(node->Name, "_REG") && + regCount < capacity) + regs[regCount++] = idx; + if (node->Obj.Type == ObjectType::Device && deviceCount < capacity) + devices[deviceCount++] = idx; + }); + + // _REG(PCI_Config, connect). A _REG for another space checks Arg0 + // and ignores the call, as with the EC's. + Object args[2]{}; + args[0].Type = ObjectType::Integer; + args[0].Integer = (uint64_t)RegionSpace::PciConfig; + args[1].Type = ObjectType::Integer; + args[1].Integer = 1; + for (int i = 0; i < regCount; i++) { + Object result{}; + interp.EvaluateNode(regs[i], args, 2, result); + } + + // \_SB._INI runs before any device's. + int32_t sb = ns.FindNode("\\_SB_"); + if (sb >= 0) { + Object result{}; + if (interp.EvaluateChild(sb, "_INI", nullptr, 0, result)) + KernelLogStream(INFO, "AML") << "Ran \\_SB._INI"; + } + + // Then each device, parents first (WalkDescendants is pre-order). + // A device that is neither present nor functional takes its whole + // subtree with it; one that is functional but absent skips only + // its own _INI. + static constexpr int MaxSkipped = 256; + int32_t skipped[MaxSkipped]; + int skippedCount = 0; + int ran = 0; + + for (int i = 0; i < deviceCount; i++) { + int32_t dev = devices[i]; + + bool underSkipped = false; + for (int32_t p = ns.GetNode(dev) ? ns.GetNode(dev)->ParentIndex : -1; + p > 0 && !underSkipped; p = ns.GetNode(p) ? ns.GetNode(p)->ParentIndex : -1) { + for (int k = 0; k < skippedCount; k++) + if (skipped[k] == p) { underSkipped = true; break; } + } + if (underSkipped) continue; + + uint64_t sta = 0x0F; + Object staObj{}; + if (interp.EvaluateChild(dev, "_STA", nullptr, 0, staObj) && + staObj.Type == ObjectType::Integer) + sta = staObj.Integer; + + if (!(sta & 0x01)) { + if (!(sta & 0x08) && skippedCount < MaxSkipped) + skipped[skippedCount++] = dev; + continue; + } + + if (interp.FindChild(dev, "_INI") < 0) continue; + Object result{}; + bool ok = interp.EvaluateChild(dev, "_INI", nullptr, 0, result); + ran++; + + char path[128]; + ns.GetNodePath(dev, path, sizeof(path)); + KernelLogStream(DEBUG, "AML") << " _INI " << path << (ok ? "" : " FAILED"); + } + + KernelLogStream(OK, "AML") << "Namespace initialized: " << base::dec + << (uint64_t)regCount << " _REG, " << (uint64_t)ran << " device _INI"; + + Memory::g_heap->Free(regs); + Memory::g_heap->Free(devices); + } + }; }; diff --git a/kernel/src/ACPI/AML/AmlParser.hpp b/kernel/src/ACPI/AML/AmlParser.hpp index ffddf77..ed5cf37 100644 --- a/kernel/src/ACPI/AML/AmlParser.hpp +++ b/kernel/src/ACPI/AML/AmlParser.hpp @@ -5,6 +5,7 @@ */ #pragma once +#include #include namespace Hal { @@ -27,11 +28,20 @@ namespace Hal { // 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 + // Initialize the AML interpreter with the DSDT, then every SSDT the + // XSDT lists (xsdt may be null to load the DSDT alone). + // This loads the full tables 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); + void InitializeInterpreter(void* dsdtData, ACPI::CommonSDTHeader* xsdt); + + // Run the namespace initialization the OS owes the firmware once its + // address-space handlers are up: _REG for PCI_Config, then \_SB._INI + // and every present device's _INI, parents first. Firmware works out + // which OS it is running under (OSYS, from _OSI) in these, and gates + // much of its later behaviour -- device power control included -- on + // the answer. Call once the Embedded Controller and PCI are up. + void InitializeNamespaceObjects(); }; }; diff --git a/kernel/src/ACPI/AcpiDevices.cpp b/kernel/src/ACPI/AcpiDevices.cpp index 572845a..1c2c4b3 100644 --- a/kernel/src/ACPI/AcpiDevices.cpp +++ b/kernel/src/ACPI/AcpiDevices.cpp @@ -453,15 +453,14 @@ namespace Hal { 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; + if (nsNode->Obj.Type == AML::ObjectType::Package && nsNode->Obj.Package.Data) { + // Parse the raw package elements to extract SLP_TYP values + const uint8_t* data = nsNode->Obj.Package.Data; + uint32_t len = nsNode->Obj.Package.Length; + if (len < 1) return result; - // NumElements - uint8_t numElements = data[0]; - uint32_t pos = 1; + uint32_t numElements = nsNode->Obj.Package.Count; + uint32_t pos = 0; if (numElements >= 1 && pos < len) { // Decode first element diff --git a/kernel/src/ACPI/AcpiPower.cpp b/kernel/src/ACPI/AcpiPower.cpp new file mode 100644 index 0000000..0ccf5b0 --- /dev/null +++ b/kernel/src/ACPI/AcpiPower.cpp @@ -0,0 +1,336 @@ +/* + * AcpiPower.cpp + * ACPI device power management: _PSx methods, _PRx power resources, + * _DSM calls, and finding the namespace node behind a PCI function + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "AcpiPower.hpp" +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Hal { + namespace AcpiPower { + + // ============================================================================ + + // PCI <-> ACPI + + // ============================================================================ + struct PciMapEntry { + int32_t Node; + uint8_t Bus, Dev, Fn; + uint8_t Score; + }; + + static PciMapEntry* g_map = nullptr; + static int g_mapCount = 0; + static bool g_mapBuilt = false; + + bool HasObject(int32_t device, const char* name) { + return AML::GetInterpreter().FindChild(device, name) >= 0; + } + + // How much power management a node carries. Picks the real node out + // of duplicates at one address. + static uint8_t PowerScore(int32_t node) { + uint8_t score = 0; + if (HasObject(node, "_PR0") || HasObject(node, "_PR3")) score += 8; + if (HasObject(node, "_PS3")) score += 4; + if (HasObject(node, "_DSM")) score += 2; + if (HasObject(node, "_OFF") || HasObject(node, "_PS0")) score += 1; + return score; + } + + static void BuildPciMap() { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + g_mapBuilt = true; + g_mapCount = 0; + if (!interp.IsInitialized()) return; + + // Device nodes with an _ADR, collected before anything is + // evaluated (evaluation may add nodes). + int32_t capacity = ns.NodeCount(); + auto* nodes = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity); + if (!nodes) return; + int count = 0; + int32_t sb = ns.FindNode("\\_SB_"); + if (sb < 0) sb = ns.RootIndex(); + ns.WalkDescendants(sb, AML::ObjectType::Device, + [&](int32_t idx, const AML::NamespaceNode*) { + if (count < capacity && ns.FindChild(idx, "_ADR") >= 0) + nodes[count++] = idx; + }); + + if (g_map) Memory::g_heap->Free(g_map); + g_map = (PciMapEntry*)Memory::g_heap->Request(sizeof(PciMapEntry) * (count ? count : 1)); + if (!g_map) { Memory::g_heap->Free(nodes); return; } + + for (int i = 0; i < count; i++) { + uint8_t bus, dev, fn; + if (!interp.GetPciAddress(nodes[i], bus, dev, fn)) continue; + g_map[g_mapCount++] = { nodes[i], bus, dev, fn, PowerScore(nodes[i]) }; + } + + Memory::g_heap->Free(nodes); + } + + void InvalidatePciMap() { + g_mapBuilt = false; + } + + int32_t FindPciDevice(uint8_t bus, uint8_t dev, uint8_t fn) { + if (!g_mapBuilt) BuildPciMap(); + + int32_t best = -1; + int bestScore = -1; + for (int i = 0; i < g_mapCount; i++) { + const auto& e = g_map[i]; + if (e.Bus != bus || e.Dev != dev || e.Fn != fn) continue; + if (e.Score > bestScore) { + best = e.Node; + bestScore = e.Score; + } + } + return best; + } + + // ============================================================================ + + // Power resources + + // ============================================================================ + static int AddUnique(int32_t* out, int count, int max, int32_t node) { + if (node < 0) return count; + for (int i = 0; i < count; i++) + if (out[i] == node) return count; + if (count < max) out[count++] = node; + return count; + } + + // The resources one _PRx object lists. The list is copied out before + // anything else is evaluated: a computed package lives in storage the + // next evaluation reuses. + static int CollectList(int32_t device, const char* name, int32_t* out, int count, int max) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + AML::Object pkg{}; + if (!interp.EvaluateChild(device, name, nullptr, 0, pkg)) return count; + if (pkg.Type != AML::ObjectType::Package) return count; + + int n = interp.GetPackageCount(pkg); + int32_t found[MaxResources]; + int foundCount = 0; + for (int i = 0; i < n && foundCount < MaxResources; i++) { + AML::Object elem{}; + if (!interp.GetPackageElement(pkg, i, elem)) break; + int32_t node = interp.ResolveReference(elem, device); + auto* nn = ns.GetNode(node); + if (nn && nn->Obj.Type == AML::ObjectType::PowerResource) + found[foundCount++] = node; + } + + for (int i = 0; i < foundCount; i++) + count = AddUnique(out, count, max, found[i]); + return count; + } + + int CollectResources(int32_t device, int32_t* out, int max) { + int count = 0; + count = CollectList(device, "_PR0", out, count, max); + count = CollectList(device, "_PR1", out, count, max); + count = CollectList(device, "_PR2", out, count, max); + count = CollectList(device, "_PR3", out, count, max); + return count; + } + + int ResourceState(int32_t resource) { + AML::Object sta{}; + if (!AML::GetInterpreter().EvaluateChild(resource, "_STA", nullptr, 0, sta)) + return -1; + return (ResultInteger(sta) & 1) ? 1 : 0; + } + + // Does a present device outside `owners` list this resource for D0? + // Turning off a rail another device is running on is how a GPU + // power-down would take out, say, an NVMe drive sharing a slot rail. + static bool NeededElsewhere(int32_t resource, const int32_t* owners, int ownerCount) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + int32_t capacity = ns.NodeCount(); + auto* users = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity); + if (!users) return true; // cannot tell: assume shared + int count = 0; + ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device, + [&](int32_t idx, const AML::NamespaceNode*) { + if (count < capacity && ns.FindChild(idx, "_PR0") >= 0) + users[count++] = idx; + }); + + bool shared = false; + for (int i = 0; i < count && !shared; i++) { + int32_t dev = users[i]; + + // Everything at or below an owner goes down with it: the + // card's own child nodes, a duplicate placeholder node at the + // same address, a function the firmware hid from PCI. + bool own = false; + for (int32_t a = dev; a > 0 && !own; ) { + for (int k = 0; k < ownerCount; k++) + if (owners[k] == a) { own = true; break; } + auto* an = ns.GetNode(a); + a = an ? an->ParentIndex : -1; + } + if (own) continue; + + int32_t list[MaxResources]; + int n = CollectList(dev, "_PR0", list, 0, MaxResources); + bool listed = false; + for (int k = 0; k < n; k++) + if (list[k] == resource) { listed = true; break; } + if (!listed) continue; + + // Only a device that is actually there counts. + AML::Object sta{}; + uint64_t status = 0x0F; + if (interp.EvaluateChild(dev, "_STA", nullptr, 0, sta)) + status = ResultInteger(sta); + if (status & 0x01) { + char path[128]; + ns.GetNodePath(dev, path, sizeof(path)); + KernelLogStream(INFO, "AcpiPower") << " power resource also used by " + << path << "; leaving it on"; + shared = true; + } + } + + Memory::g_heap->Free(users); + return shared; + } + + // ============================================================================ + + // Device power transitions + + // ============================================================================ + void EnterD3Cold(int32_t device, const int32_t* owners, int ownerCount, + D3ColdReport& report) { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + report = D3ColdReport{}; + if (device < 0) return; + + char path[128]; + ns.GetNodePath(device, path, sizeof(path)); + + // Gather the resources first: _PS3 may change what _PRx reports. + int32_t resources[MaxResources]; + int count = CollectResources(device, resources, MaxResources); + + if (HasObject(device, "_PS3")) { + AML::Object result{}; + bool ok = interp.EvaluateChild(device, "_PS3", nullptr, 0, result); + report.RanPs3 = true; + KernelLogStream(INFO, "AcpiPower") << " " << path << "._PS3" + << (ok ? "" : " (failed)"); + } + + // Off in reverse order of listing (ACPI turns resources off in + // reverse ResourceOrder; firmware lists them in that order). + for (int i = count - 1; i >= 0; i--) { + int32_t res = resources[i]; + char rpath[128]; + ns.GetNodePath(res, rpath, sizeof(rpath)); + + if (ResourceState(res) == 0) { + KernelLogStream(INFO, "AcpiPower") << " " << rpath << " already off"; + continue; + } + if (NeededElsewhere(res, owners, ownerCount)) { + report.ResourcesShared++; + continue; + } + + AML::Object result{}; + bool ok = interp.EvaluateChild(res, "_OFF", nullptr, 0, result); + int after = ResourceState(res); + if (ok) report.ResourcesOff++; + if (after == 1) report.ResourcesStillOn++; + KernelLogStream(INFO, "AcpiPower") << " " << rpath << "._OFF" + << (ok ? "" : " (failed)") + << (after == 0 ? ", now off" : after == 1 ? ", still on" : ""); + } + } + + // ============================================================================ + + // _DSM + + // ============================================================================ + AML::Object IntegerArg(uint64_t value) { + AML::Object o{}; + o.Type = AML::ObjectType::Integer; + o.Integer = value; + return o; + } + + AML::Object BufferArg(const uint8_t* bytes, uint32_t length) { + AML::Object o{}; + o.Type = AML::ObjectType::Buffer; + if (length > AML::MaxBufferLen) length = AML::MaxBufferLen; + memcpy(o.Buffer.Data, bytes, length); + o.Buffer.Length = length; + return o; + } + + AML::Object EmptyPackageArg() { + AML::Object o{}; + o.Type = AML::ObjectType::Package; + o.Package.Data = nullptr; + o.Package.Length = 0; + o.Package.Count = 0; + return o; + } + + uint64_t ResultInteger(const AML::Object& result) { + if (result.Type == AML::ObjectType::Integer) return result.Integer; + if (result.Type == AML::ObjectType::Buffer) { + uint64_t v = 0; + for (uint32_t i = 0; i < result.Buffer.Length && i < 8; i++) + v |= (uint64_t)result.Buffer.Data[i] << (8 * i); + return v; + } + return 0; + } + + bool EvaluateDsm(int32_t device, const uint8_t uuid[16], uint64_t revision, + uint64_t function, const AML::Object& arg3, AML::Object& result) { + auto& interp = AML::GetInterpreter(); + if (device < 0 || interp.FindChild(device, "_DSM") < 0) return false; + + AML::Object args[4]; + args[0] = BufferArg(uuid, 16); + args[1] = IntegerArg(revision); + args[2] = IntegerArg(function); + args[3] = arg3; + return interp.EvaluateChild(device, "_DSM", args, 4, result); + } + + uint64_t DsmFunctions(int32_t device, const uint8_t uuid[16], uint64_t revision, + const AML::Object& arg3) { + AML::Object result{}; + if (!EvaluateDsm(device, uuid, revision, 0, arg3, result)) return 0; + uint64_t mask = ResultInteger(result); + // Bit 0 clear means "no functions for this UUID/revision". + return (mask & 1) ? mask : 0; + } + }; +}; diff --git a/kernel/src/ACPI/AcpiPower.hpp b/kernel/src/ACPI/AcpiPower.hpp new file mode 100644 index 0000000..9773916 --- /dev/null +++ b/kernel/src/ACPI/AcpiPower.hpp @@ -0,0 +1,92 @@ +/* + * AcpiPower.hpp + * ACPI device power management: _PSx methods, _PRx power resources, + * _DSM calls, and finding the namespace node behind a PCI function + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Hal { + namespace AcpiPower { + + // ============================================================================ + + // PCI <-> ACPI + + // ============================================================================ + // The Device node describing PCI function bus:dev.fn, or -1. When + // firmware declares more than one node at the same address (a generic + // placeholder plus a vendor SSDT's real one is common for GPUs), the + // one carrying power-management objects wins. + int32_t FindPciDevice(uint8_t bus, uint8_t dev, uint8_t fn); + + // Forget the PCI address map built by FindPciDevice (bus numbers may + // change across a resume). + void InvalidatePciMap(); + + // True when `device` has a child object named `name` ("_PS3"). + bool HasObject(int32_t device, const char* name); + + // ============================================================================ + + // Power resources + + // ============================================================================ + static constexpr int MaxResources = 8; + + // The power resources the device lists in _PR0.._PR3 (deduplicated). + int CollectResources(int32_t device, int32_t* out, int max); + + // _STA of a power resource: 1 on, 0 off, -1 unknown. + int ResourceState(int32_t resource); + + // ============================================================================ + + // Device power transitions + + // ============================================================================ + struct D3ColdReport { + bool RanPs3; + int ResourcesOff; // resources whose _OFF was run + int ResourcesShared; // left on: some other present device needs them + int ResourcesStillOn; // _OFF ran but _STA still says on + }; + + // Put an ACPI device into D3cold the way the spec orders it: _PS3, + // then every power resource it depends on off, last-listed first. A + // resource that a device outside `owners` also depends on is left + // on -- `owners` lists the nodes being powered down together (the + // functions of one card and the port above them), which may freely + // share, as may anything below them in the namespace. The PCI side + // (PMCSR to D3hot) is the caller's to do first. + void EnterD3Cold(int32_t device, const int32_t* owners, int ownerCount, + D3ColdReport& report); + + // ============================================================================ + + // _DSM + + // ============================================================================ + // Evaluate _DSM(uuid, revision, function, arg3) on a device. False + // when the device has no _DSM or it failed. + bool EvaluateDsm(int32_t device, const uint8_t uuid[16], uint64_t revision, + uint64_t function, const AML::Object& arg3, AML::Object& result); + + // The function bitmap _DSM function 0 reports for (uuid, revision), + // 0 when unsupported. Bit 0 set means "some functions exist". + uint64_t DsmFunctions(int32_t device, const uint8_t uuid[16], uint64_t revision, + const AML::Object& arg3); + + // Helpers for building _DSM / method arguments. + AML::Object IntegerArg(uint64_t value); + AML::Object BufferArg(const uint8_t* bytes, uint32_t length); + AML::Object EmptyPackageArg(); + + // Integer value of a method result: an integer as is, a buffer read + // little-endian (up to 8 bytes). + uint64_t ResultInteger(const AML::Object& result); + }; +}; diff --git a/kernel/src/ACPI/AcpiShutdown.cpp b/kernel/src/ACPI/AcpiShutdown.cpp index 1d6b714..2de8253 100644 --- a/kernel/src/ACPI/AcpiShutdown.cpp +++ b/kernel/src/ACPI/AcpiShutdown.cpp @@ -58,7 +58,7 @@ namespace Hal { // 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); + AML::InitializeInterpreter(dsdt, xsdt); // Enumerate ACPI devices now that the namespace is loaded if (AML::GetInterpreter().IsInitialized()) { diff --git a/kernel/src/ACPI/AcpiSleep.cpp b/kernel/src/ACPI/AcpiSleep.cpp index d7dc380..f8a8b35 100644 --- a/kernel/src/ACPI/AcpiSleep.cpp +++ b/kernel/src/ACPI/AcpiSleep.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -128,9 +129,9 @@ namespace Hal { const char* stepNames[] = { "?", "GDT", "TSS", "Syscalls", "PAT", "PIC", "LocalAPIC", "IoAPIC", "APICTimer", "PM1Clear", - "WAK", "PS2", "IntelGPU", "sti" + "WAK", "PS2", "IntelGPU", "dGPU", "sti" }; - const char* stepName = reinitStep <= 0x0D ? stepNames[reinitStep] : "?"; + const char* stepName = reinitStep <= 0x0E ? stepNames[reinitStep] : "?"; KernelLogStream(WARNING, "S3") << "Reinit stopped after step 0x" << base::hex << (uint64_t)reinitStep << " (" << stepName << ") - next step crashed/hung"; @@ -459,7 +460,9 @@ namespace Hal { if (Drivers::Graphics::IntelGPU::IsInitialized()) { Drivers::Graphics::IntelGPU::Reinitialize(); } - reinitProgress(0x0D); // sti + reinitProgress(0x0D); // discrete GPU back off + Drivers::Graphics::DgpuPower::Resume(); + reinitProgress(0x0E); // sti asm volatile("sti"); KernelLogStream(OK, "S3") << "Resumed from S3 suspend"; diff --git a/kernel/src/ACPI/CpuIdle.cpp b/kernel/src/ACPI/CpuIdle.cpp index 55d9503..9ad326a 100644 --- a/kernel/src/ACPI/CpuIdle.cpp +++ b/kernel/src/ACPI/CpuIdle.cpp @@ -249,13 +249,17 @@ namespace Hal { } static bool ParseCstObject(const char* path, const AML::Object& obj) { - if (obj.Type != AML::ObjectType::Package || obj.Buffer.Length < 2) { + if (obj.Type != AML::ObjectType::Package || !obj.Package.Data || + obj.Package.Length < 1) { return false; } - uint32_t pos = 1; // package element count byte + // Package.Data starts at the first element (the state count). + const uint8_t* data = obj.Package.Data; + uint32_t length = obj.Package.Length; + uint32_t pos = 0; uint64_t declaredStateCount = 0; - if (!DecodeInteger(obj.Buffer.Data, obj.Buffer.Length, pos, declaredStateCount)) { + if (!DecodeInteger(data, length, pos, declaredStateCount)) { return false; } @@ -263,7 +267,7 @@ namespace Hal { for (uint64_t i = 0; i < declaredStateCount && parsedCount < MaxIdleStates; i++) { const uint8_t* nestedData = nullptr; uint32_t nestedLength = 0; - if (!DecodePackageObject(obj.Buffer.Data, obj.Buffer.Length, pos, nestedData, nestedLength)) { + if (!DecodePackageObject(data, length, pos, nestedData, nestedLength)) { break; } @@ -310,7 +314,10 @@ namespace Hal { auto& ns = interp.GetNamespace(); char path[256]; - for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) { + // DSDT processors only, for the same reason as the _CST scan. + int32_t limit = interp.DsdtNodeCount(); + if (limit == 0) limit = ns.NodeCount(); + for (int32_t nodeIndex = 0; nodeIndex < limit; nodeIndex++) { auto* node = ns.GetNode(nodeIndex); if (node == nullptr || node->Obj.Type != AML::ObjectType::Processor) { continue; @@ -522,7 +529,13 @@ namespace Hal { auto& ns = interp.GetNamespace(); char path[256]; - for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) { + // DSDT objects only. SSDTs are loaded too now (for device power + // methods), and many carry processor _CST tables; those C-state + // lists have never been exercised against the deep-idle timer + // behaviour on real laptops, so they stay out until they have. + int32_t limit = interp.DsdtNodeCount(); + if (limit == 0) limit = ns.NodeCount(); + for (int32_t nodeIndex = 0; nodeIndex < limit; nodeIndex++) { auto* node = ns.GetNode(nodeIndex); if (node == nullptr) continue; if (!IsName(node->Name, "_CST")) continue; diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index a909fe2..319b101 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 270 +#define MONTAUK_BUILD_NUMBER 276 diff --git a/kernel/src/Boot/Main.cpp b/kernel/src/Boot/Main.cpp index a3d4f3f..9885c2d 100644 --- a/kernel/src/Boot/Main.cpp +++ b/kernel/src/Boot/Main.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,12 @@ extern "C" void kmain() { Pci::Initialize(g_acpi.GetXSDT()); + // _REG and _INI, once the EC and PCI config space (ECAM, for the + // extended registers PCI_Config regions reach) are both available: + // firmware settles which OS it is talking to here, and gates device + // power control on the answer. + Hal::AML::InitializeNamespaceObjects(); + Drivers::ProbeEarly(); Drivers::InitializeGraphics(); @@ -162,6 +169,10 @@ extern "C" void kmain() { Drivers::PS2::Keyboard::Initialize(); Drivers::PS2::Mouse::Initialize(); + // Hybrid graphics: switch off a discrete GPU nothing drives before + // the normal probe can claim its USB-C/audio functions. + Drivers::PowerDownUnusedGpus(); + Drivers::ProbeNormal(); Drivers::InitializeNetwork(); Drivers::InitializeStorage(); diff --git a/kernel/src/Drivers/Graphics/DgpuPower.cpp b/kernel/src/Drivers/Graphics/DgpuPower.cpp new file mode 100644 index 0000000..ba311a3 --- /dev/null +++ b/kernel/src/Drivers/Graphics/DgpuPower.cpp @@ -0,0 +1,560 @@ +/* + * DgpuPower.cpp + * Powers down discrete GPUs that no MontaukOS driver runs + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "DgpuPower.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; +namespace AcpiPower = Hal::AcpiPower; +namespace AML = Hal::AML; + +namespace Drivers::Graphics::DgpuPower { + + // ========================================================================= + // Vendor interfaces + // ========================================================================= + + static constexpr uint16_t VendorNvidia = 0x10DE; + static constexpr uint16_t VendorAmd = 0x1002; + + // NVIDIA Optimus _DSM (GUID a486d8f8-0bda-471b-a72b-6042a6b5bee0, rev + // 0x100). Function 0x1A (OPTIMUS_CAPS) with "power down in PS3" set + // arms the GPU's _PS3 to cut its power; 0x1B (OPTIMUS_FLAGS) 0x3 is + // what the drivers send alongside it. + static constexpr uint8_t OptimusUuid[16] = { + 0xF8, 0xD8, 0x86, 0xA4, 0xDA, 0x0B, 0x1B, 0x47, + 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0, + }; + static constexpr uint64_t OptimusRevision = 0x100; + static constexpr uint64_t OptimusCaps = 0x1A; + static constexpr uint64_t OptimusFlags = 0x1B; + static constexpr uint32_t OptimusPowerdown = (3u << 24) | 1u; // POWERDOWN_PS3 | FLAGS_CHANGED + + // The older NVIDIA hybrid _DSM (GUID d4a50b75-65c7-46f7-bfb7-41514cea0244, + // rev 0x102): function 3 (POWER) with 2 (STAMINA) switches the discrete + // GPU off on pre-Optimus muxed laptops. + static constexpr uint8_t NvidiaDsmUuid[16] = { + 0x75, 0x0B, 0xA5, 0xD4, 0xC7, 0x65, 0xF7, 0x46, + 0xBF, 0xB7, 0x41, 0x51, 0x4C, 0xEA, 0x02, 0x44, + }; + static constexpr uint64_t NvidiaDsmRevision = 0x102; + static constexpr uint64_t NvidiaDsmPower = 0x03; + static constexpr uint64_t NvidiaDsmStamina = 0x02; + + // AMD PowerXpress ATPX(function, buffer). + static constexpr uint64_t AtpxVerifyInterface = 0; + static constexpr uint64_t AtpxGetParameters = 1; + static constexpr uint64_t AtpxPowerControl = 2; + static constexpr uint32_t AtpxParamsSupported = 1u << 0; // function mask bits + static constexpr uint32_t AtpxPowerSupported = 1u << 1; + static constexpr uint32_t AtpxHybridGraphics = 1u << 14; // PX parameter flag + + // ========================================================================= + // State + // ========================================================================= + + static constexpr int MaxCards = 4; + static constexpr int MaxFunctions = 8; + static constexpr int MaxChain = 4; + + struct PciAddr { + uint8_t Bus, Dev, Fn; + }; + + struct Card { + uint8_t Bus, Dev, DisplayFn; + uint16_t Vendor, DeviceId; + uint8_t Functions[MaxFunctions]; + int FunctionCount; + PciAddr Chain[MaxChain]; // bridges above it, nearest first + int ChainCount; + }; + + static Card g_cards[MaxCards]; + static int g_cardCount = 0; + + int PoweredDownCount() { + return g_cardCount; + } + + // ========================================================================= + // PCI helpers + // ========================================================================= + + static uint32_t Cfg(uint8_t bus, uint8_t dev, uint8_t fn, uint16_t off, uint8_t width) { + return Pci::ConfigRead(bus, dev, fn, off, width); + } + + static bool Present(uint8_t bus, uint8_t dev, uint8_t fn) { + return (Cfg(bus, dev, fn, 0x00, 2) & 0xFFFF) != 0xFFFF; + } + + // The bridge whose secondary bus is `bus`, or nullptr for a root bus. + static const Pci::PciDevice* BridgeAbove(uint8_t bus) { + auto& devices = Pci::GetDevices(); + for (uint64_t i = 0; i < devices.size(); i++) { + const auto& d = devices[i]; + if (!Pci::IsBridge(d)) continue; + if (Cfg(d.Bus, d.Device, d.Function, Pci::PCI_REG_SECONDARY_BUS, 1) == bus) + return &d; + } + return nullptr; + } + + static void BusRange(const Pci::PciDevice& b, uint8_t& secondary, uint8_t& subordinate) { + secondary = (uint8_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_SECONDARY_BUS, 1); + subordinate = (uint8_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_SUBORDINATE_BUS, 1); + } + + // Does anything other than the card and its own bridges live below b? + static bool SubtreeIsCard(const Pci::PciDevice& b, const Card& card) { + uint8_t sec, sub; + BusRange(b, sec, sub); + auto& devices = Pci::GetDevices(); + for (uint64_t i = 0; i < devices.size(); i++) { + const auto& d = devices[i]; + if (d.Bus < sec || d.Bus > sub) continue; + if (d.Bus == card.Bus && d.Device == card.Dev) continue; + bool chain = false; + for (int k = 0; k < card.ChainCount; k++) { + if (card.Chain[k].Bus == d.Bus && card.Chain[k].Dev == d.Device && + card.Chain[k].Fn == d.Function) { chain = true; break; } + } + if (!chain) return false; + } + return true; + } + + // Does the bridge forward this physical address downstream? + static bool WindowContains(const Pci::PciDevice& b, uint64_t phys) { + uint32_t mem = Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_MEM_BASE, 4); + uint64_t base = (uint64_t)(mem & 0xFFF0) << 16; + uint64_t limit = ((uint64_t)((mem >> 16) & 0xFFF0) << 16) | 0xFFFFF; + if (base <= limit && phys >= base && phys <= limit) return true; + + uint32_t pref = Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_PREF_BASE, 4); + uint64_t pbase = (uint64_t)(pref & 0xFFF0) << 16; + uint64_t plimit = ((uint64_t)((pref >> 16) & 0xFFF0) << 16) | 0xFFFFF; + if ((pref & 0xF) == 0x1) { + pbase |= (uint64_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_PREF_BASE_UPPER, 4) << 32; + plimit |= (uint64_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_PREF_LIMIT_UPPER, 4) << 32; + } + return pbase <= plimit && phys >= pbase && phys <= plimit; + } + + // Functions that ship on a graphics card: the GPU itself, its HDMI/DP + // audio, and (NVIDIA Turing+, AMD Navi) a USB-C xHCI and UCSI controller. + // An integrated GPU behind an internal bridge -- AMD APUs -- shares its + // device with the platform security processor, audio DSP and the + // machine's own USB, and must never be mistaken for a card. + static bool IsCardFunction(const Pci::PciDevice& d) { + if (d.ClassCode == 0x03) return true; // display + if (d.ClassCode == 0x04 && d.SubClass == 0x03) return true; // HD audio + if (d.ClassCode == 0x0C && d.SubClass == 0x03) return true; // USB + if (d.ClassCode == 0x0C && d.SubClass == 0x80) return true; // UCSI + return false; + } + + static int PciePortType(uint8_t bus, uint8_t dev, uint8_t fn) { + uint8_t cap = Pci::FindCapability(bus, dev, fn, Pci::PCI_CAP_PCIE); + if (cap == 0) return -1; + return (int)((Cfg(bus, dev, fn, cap + 2, 2) >> 4) & 0xF); + } + static constexpr int PortTypeRootPort = 0x4; + + static void LogAddr(KernelLogStream& log, uint8_t bus, uint8_t dev, uint8_t fn) { + log << base::hex << (uint64_t)bus << ":" << (uint64_t)dev << "." << (uint64_t)fn; + } + + // ========================================================================= + // Detection + // ========================================================================= + + static bool BootFramebuffer(uint64_t& phys) { + const auto& info = montauk::boot::Info(); + if (!info.has(montauk::boot::FeatureFramebuffer) || info.framebuffer.address == 0) + return false; + phys = Memory::SubHHDM(info.framebuffer.address); + return true; + } + + // Decide whether `gpu` is a discrete card we may switch off, filling in + // `card` if so. Every reason to leave it alone is logged. + static bool Qualifies(const Pci::PciDevice& gpu, Card& card) { + card = Card{}; + card.Bus = gpu.Bus; + card.Dev = gpu.Device; + card.DisplayFn = gpu.Function; + card.Vendor = gpu.VendorId; + card.DeviceId = gpu.DeviceId; + + auto reject = [&](const char* why) { + KernelLogStream log(INFO, "dGPU"); + log << "Leaving "; + LogAddr(log, gpu.Bus, gpu.Device, gpu.Function); + log << " (" << base::hex << (uint64_t)gpu.VendorId << ":" << (uint64_t)gpu.DeviceId + << ") on: " << why; + return false; + }; + + if (gpu.Claimed) return reject("a driver runs it"); + + // An integrated GPU sits on the root bus. A card is behind a port. + const Pci::PciDevice* port = BridgeAbove(gpu.Bus); + if (!port) return reject("on the root bus (integrated)"); + + // The whole device: every function must be one a card carries, and + // none may already belong to a driver. + auto& devices = Pci::GetDevices(); + bool otherDisplay = false; + for (uint64_t i = 0; i < devices.size(); i++) { + const auto& d = devices[i]; + if (d.Bus == gpu.Bus && d.Device == gpu.Device) { + if (!IsCardFunction(d)) return reject("shares its device with platform functions"); + if (d.Claimed) return reject("a driver runs one of its functions"); + if (card.FunctionCount < MaxFunctions) + card.Functions[card.FunctionCount++] = d.Function; + } else if (d.ClassCode == 0x03) { + otherDisplay = true; + } + } + + // Hybrid graphics only: something else has to drive the screen. + if (!otherDisplay) return reject("it is the only display controller"); + + // The firmware's boot display (the GOP framebuffer, or legacy VGA) + // must not be scanned out by this card -- on a muxed laptop switched + // to discrete mode it is, and the panel would go dark. + uint64_t fb = 0; + if (BootFramebuffer(fb)) { + if (WindowContains(*port, fb)) return reject("it scans out the boot display"); + } else { + uint16_t ctl = (uint16_t)Cfg(port->Bus, port->Device, port->Function, + Pci::PCI_REG_BRIDGE_CONTROL, 2); + if (ctl & Pci::PCI_BRIDGE_CTL_VGA) return reject("legacy VGA is routed to it"); + } + + // The port, and any switch above it that serves nothing else, power + // down with the card. The first bridge must serve only the card. + const Pci::PciDevice* b = port; + while (b && card.ChainCount < MaxChain) { + card.Chain[card.ChainCount++] = { b->Bus, b->Device, b->Function }; + if (!SubtreeIsCard(*b, card)) { + card.ChainCount--; + break; + } + if (b->Bus == 0) break; + b = BridgeAbove(b->Bus); + } + if (card.ChainCount == 0) return reject("its port also serves other devices"); + + return true; + } + + // ========================================================================= + // Vendor methods + // ========================================================================= + + // The ACPI nodes of every display controller, where the vendor _DSM or + // ATPX may live (the integrated GPU's node, on some muxed designs). + static int DisplayNodes(int32_t* out, int max) { + int count = 0; + auto& devices = Pci::GetDevices(); + for (uint64_t i = 0; i < devices.size() && count < max; i++) { + const auto& d = devices[i]; + if (d.ClassCode != 0x03) continue; + int32_t node = AcpiPower::FindPciDevice(d.Bus, d.Device, d.Function); + if (node >= 0) out[count++] = node; + } + return count; + } + + static AML::Object Dword(uint32_t v) { + uint8_t b[4] = { (uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24) }; + return AcpiPower::BufferArg(b, 4); + } + + // Arm Optimus power-down in _PS3. Returns true when firmware took it. + static bool ArmOptimus(int32_t gpuNode) { + if (gpuNode < 0) return false; + uint64_t funcs = AcpiPower::DsmFunctions(gpuNode, OptimusUuid, OptimusRevision, + AcpiPower::EmptyPackageArg()); + if (!(funcs & (1ULL << OptimusCaps))) return false; + + AML::Object result{}; + AcpiPower::EvaluateDsm(gpuNode, OptimusUuid, OptimusRevision, OptimusCaps, + Dword(OptimusPowerdown), result); + if (funcs & (1ULL << OptimusFlags)) + AcpiPower::EvaluateDsm(gpuNode, OptimusUuid, OptimusRevision, OptimusFlags, + Dword(0x3), result); + KernelLogStream(INFO, "dGPU") << " Optimus _DSM: power-down on _PS3 armed"; + return true; + } + + // The node offering the legacy NVIDIA power _DSM, or -1. + static int32_t FindNvidiaDsm(int32_t gpuNode) { + int32_t nodes[8]; + int count = 0; + if (gpuNode >= 0) nodes[count++] = gpuNode; + count += DisplayNodes(nodes + count, 8 - count); + for (int i = 0; i < count; i++) { + uint64_t funcs = AcpiPower::DsmFunctions(nodes[i], NvidiaDsmUuid, NvidiaDsmRevision, + AcpiPower::EmptyPackageArg()); + if (funcs & (1ULL << NvidiaDsmPower)) return nodes[i]; + } + return -1; + } + + // The ATPX method, if the platform offers one with power control that + // is meant to be used (not on "hybrid" designs, which use _PR3). + static int32_t FindAtpx() { + auto& interp = AML::GetInterpreter(); + auto& ns = interp.GetNamespace(); + + int32_t atpx = -1; + int32_t nodes[8]; + int count = DisplayNodes(nodes, 8); + for (int i = 0; i < count && atpx < 0; i++) + atpx = interp.FindChild(nodes[i], "ATPX"); + if (atpx < 0) { + ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Method, + [&](int32_t idx, const AML::NamespaceNode* n) { + if (atpx < 0 && n->Name[0] == 'A' && n->Name[1] == 'T' && + n->Name[2] == 'P' && n->Name[3] == 'X') + atpx = idx; + }); + } + if (atpx < 0) return -1; + + AML::Object args[2] = { AcpiPower::IntegerArg(AtpxVerifyInterface), + AcpiPower::IntegerArg(0) }; + AML::Object result{}; + if (!interp.EvaluateNode(atpx, args, 2, result) || + result.Type != AML::ObjectType::Buffer || result.Buffer.Length < 8) + return -1; + const uint8_t* r = result.Buffer.Data; + uint32_t functions = r[4] | (r[5] << 8) | (r[6] << 16) | ((uint32_t)r[7] << 24); + + if (functions & AtpxParamsSupported) { + args[0] = AcpiPower::IntegerArg(AtpxGetParameters); + if (interp.EvaluateNode(atpx, args, 2, result) && + result.Type == AML::ObjectType::Buffer && result.Buffer.Length >= 10) { + const uint8_t* p = result.Buffer.Data; + uint32_t valid = p[2] | (p[3] << 8) | (p[4] << 16) | ((uint32_t)p[5] << 24); + uint32_t flags = p[6] | (p[7] << 8) | (p[8] << 16) | ((uint32_t)p[9] << 24); + if (valid & flags & AtpxHybridGraphics) { + KernelLogStream(INFO, "dGPU") << " ATPX: hybrid design, power via _PR3"; + return -1; + } + } + } + + if (!(functions & AtpxPowerSupported)) return -1; + return atpx; + } + + // ========================================================================= + // Power-down sequence + // ========================================================================= + + static void PowerDownCard(const Card& card) { + { + KernelLogStream log(INFO, "dGPU"); + log << "Powering down "; + LogAddr(log, card.Bus, card.Dev, card.DisplayFn); + log << " (" << base::hex << (uint64_t)card.Vendor << ":" + << (uint64_t)card.DeviceId << ")"; + } + + bool haveAcpi = AML::GetInterpreter().IsInitialized(); + + // 1. Quiesce every function: no decode, no DMA, no interrupts. + for (int i = 0; i < card.FunctionCount; i++) { + uint8_t fn = card.Functions[i]; + if (!Present(card.Bus, card.Dev, fn)) continue; + uint16_t cmd = (uint16_t)Cfg(card.Bus, card.Dev, fn, Pci::PCI_REG_COMMAND, 2); + cmd &= (uint16_t)~(0x7u); // I/O, memory, bus master + Pci::ConfigWrite(card.Bus, card.Dev, fn, Pci::PCI_REG_COMMAND, 2, cmd); + Pci::DisableInterruptDelivery(card.Bus, card.Dev, fn); + } + + // 2. The namespace side of the card and its ports. + int32_t gpuNode = -1; + int32_t owners[MaxFunctions + MaxChain]; + int ownerCount = 0; + int32_t chainNodes[MaxChain]; + bool portHasPr3 = false; + + if (haveAcpi) { + AcpiPower::InvalidatePciMap(); + gpuNode = AcpiPower::FindPciDevice(card.Bus, card.Dev, card.DisplayFn); + for (int i = 0; i < card.FunctionCount; i++) { + int32_t n = AcpiPower::FindPciDevice(card.Bus, card.Dev, card.Functions[i]); + if (n >= 0) owners[ownerCount++] = n; + } + for (int i = 0; i < card.ChainCount; i++) { + chainNodes[i] = AcpiPower::FindPciDevice(card.Chain[i].Bus, card.Chain[i].Dev, + card.Chain[i].Fn); + if (chainNodes[i] >= 0) { + owners[ownerCount++] = chainNodes[i]; + if (AcpiPower::HasObject(chainNodes[i], "_PR3")) portHasPr3 = true; + } + } + + char path[128]; + if (gpuNode >= 0) { + AML::GetInterpreter().GetNamespace().GetNodePath(gpuNode, path, sizeof(path)); + KernelLogStream(INFO, "dGPU") << " ACPI node " << path; + } else { + KernelLogStream(INFO, "dGPU") << " no ACPI node for the GPU"; + } + } else { + for (int i = 0; i < card.ChainCount; i++) chainNodes[i] = -1; + } + + // 3. Vendor methods. With _PR3 on the port the platform does runtime + // D3cold through power resources, and the drivers leave the vendor + // methods alone there (they would make _PS3 cut power underneath + // the power resources' own sequencing). + int32_t nvidiaDsm = -1; + int32_t atpx = -1; + if (haveAcpi && !portHasPr3) { + if (card.Vendor == VendorNvidia) { + if (!ArmOptimus(gpuNode)) nvidiaDsm = FindNvidiaDsm(gpuNode); + } else if (card.Vendor == VendorAmd) { + atpx = FindAtpx(); + } + } + + // 4. PCI D3hot, companions first, the GPU last. + int d3hot = 0; + for (int pass = 0; pass < 2; pass++) { + for (int i = 0; i < card.FunctionCount; i++) { + uint8_t fn = card.Functions[i]; + if ((fn == card.DisplayFn) != (pass == 1)) continue; + if (!Present(card.Bus, card.Dev, fn)) continue; + if (Pci::SetPowerState(card.Bus, card.Dev, fn, 3)) d3hot++; + } + } + + // 5. Vendor power switch. + if (nvidiaDsm >= 0) { + AML::Object result{}; + AcpiPower::EvaluateDsm(nvidiaDsm, NvidiaDsmUuid, NvidiaDsmRevision, NvidiaDsmPower, + AcpiPower::IntegerArg(NvidiaDsmStamina), result); + KernelLogStream(INFO, "dGPU") << " NVIDIA _DSM: discrete GPU switched off"; + } + if (atpx >= 0) { + uint8_t params[3] = { 3, 0, 0 }; // size = 3, dGPU state = off + AML::Object args[2] = { AcpiPower::IntegerArg(AtpxPowerControl), + AcpiPower::BufferArg(params, 3) }; + AML::Object result{}; + AML::GetInterpreter().EvaluateNode(atpx, args, 2, result); + Timekeeping::BusyWaitMs(200); // the off transition needs it + KernelLogStream(INFO, "dGPU") << " ATPX: discrete GPU switched off"; + } + + // 6. The GPU's own ACPI D3cold: _PS3 and its power resources. + bool d3cold = false; + int shared = 0; + if (gpuNode >= 0) { + AcpiPower::D3ColdReport r{}; + AcpiPower::EnterD3Cold(gpuNode, owners, ownerCount, r); + if (r.ResourcesOff > 0 && r.ResourcesStillOn == 0) d3cold = true; + shared += r.ResourcesShared; + + // Older Optimus firmware hangs the power switch on a _OFF method + // of the GPU device itself and only calls it from _PS3 when the + // driver's flags say so. Call it directly if the card is still + // answering. + if (Present(card.Bus, card.Dev, card.DisplayFn) && !portHasPr3) { + int32_t off = AML::GetInterpreter().FindChild(gpuNode, "_OFF"); + auto* n = AML::GetInterpreter().GetNamespace().GetNode(off); + if (n && n->Obj.Type == AML::ObjectType::Method) { + AML::Object result{}; + AML::GetInterpreter().EvaluateNode(off, nullptr, 0, result); + KernelLogStream(INFO, "dGPU") << " ran the GPU's _OFF"; + } + } + } + if (!Present(card.Bus, card.Dev, card.DisplayFn)) d3cold = true; + + // 7. The ports above it, nearest first. A switch port belongs to the + // card outright; a root port only goes down when the platform says + // it can (power resources or _PS3), as Linux requires. + for (int i = 0; i < card.ChainCount; i++) { + const PciAddr& p = card.Chain[i]; + int32_t node = chainNodes[i]; + bool rootPort = PciePortType(p.Bus, p.Dev, p.Fn) == PortTypeRootPort; + bool acpiPm = node >= 0 && (AcpiPower::HasObject(node, "_PR3") || + AcpiPower::HasObject(node, "_PR0") || + AcpiPower::HasObject(node, "_PS3")); + if (rootPort && !acpiPm) break; + + Pci::SetPowerState(p.Bus, p.Dev, p.Fn, 3); + if (node >= 0) { + AcpiPower::D3ColdReport r{}; + AcpiPower::EnterD3Cold(node, owners, ownerCount, r); + if (r.ResourcesOff > 0 && r.ResourcesStillOn == 0) d3cold = true; + shared += r.ResourcesShared; + } + if (rootPort) break; + } + + KernelLogStream log(d3cold || d3hot ? OK : WARNING, "dGPU"); + LogAddr(log, card.Bus, card.Dev, card.DisplayFn); + if (d3cold) + log << " is powered off (D3cold)"; + else if (d3hot && shared) + log << " is in D3hot; its power rail also feeds another device, so it stays on"; + else if (d3hot) + log << " is in D3hot; the platform offered no way to cut its power"; + else + log << " could not be powered down (no PCI or ACPI power management)"; + } + + // ========================================================================= + // Public API + // ========================================================================= + + void PowerDownUnused() { + auto& devices = Pci::GetDevices(); + g_cardCount = 0; + + for (uint64_t i = 0; i < devices.size() && g_cardCount < MaxCards; i++) { + const auto& d = devices[i]; + if (d.ClassCode != 0x03 || d.Reserved) continue; + + Card card; + if (!Qualifies(d, card)) continue; + + // Withhold all of its functions from the driver probe that + // follows: the xHCI driver would otherwise claim the card's + // USB-C controller and lose it when the card powers off. + for (uint64_t k = 0; k < devices.size(); k++) { + if (devices[k].Bus == card.Bus && devices[k].Device == card.Dev) + devices[k].Reserved = true; + } + + g_cards[g_cardCount++] = card; + PowerDownCard(card); + } + } + + void Resume() { + for (int i = 0; i < g_cardCount; i++) { + KernelLogStream(INFO, "dGPU") << "Re-applying power-down after resume"; + PowerDownCard(g_cards[i]); + } + } +}; diff --git a/kernel/src/Drivers/Graphics/DgpuPower.hpp b/kernel/src/Drivers/Graphics/DgpuPower.hpp new file mode 100644 index 0000000..bc8fa65 --- /dev/null +++ b/kernel/src/Drivers/Graphics/DgpuPower.hpp @@ -0,0 +1,33 @@ +/* + * DgpuPower.hpp + * Powers down discrete GPUs that no MontaukOS driver runs + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include + +namespace Drivers::Graphics::DgpuPower { + + // A hybrid-graphics laptop boots with its discrete GPU (NVIDIA Optimus, + // AMD PowerXpress / hybrid, Intel Arc) powered and idling at boot clocks. + // With no driver to manage it, it stays there: several watts of heat and + // a fan that never settles. This finds every display controller that + // nothing claimed and that does not carry the boot display, takes its + // whole card (GPU, HDMI audio, USB-C controller) away from the driver + // probe, and powers it down: PCI D3hot, then the platform's D3cold path + // through ACPI (_PR3 power resources on the port above it, _PS3, and the + // NVIDIA Optimus / legacy and AMD ATPX vendor methods where firmware + // relies on them). + // + // Call after the early (graphics) driver probe and before the normal + // one, with the AML namespace initialized. + void PowerDownUnused(); + + // Firmware repowers the card during S3 resume. Call once the platform is + // back up to put every card powered down at boot back to sleep. + void Resume(); + + // Number of cards powered down (for diagnostics). + int PoweredDownCount(); +}; diff --git a/kernel/src/Drivers/Init.cpp b/kernel/src/Drivers/Init.cpp index 5eb7192..0ec3ce3 100644 --- a/kernel/src/Drivers/Init.cpp +++ b/kernel/src/Drivers/Init.cpp @@ -7,6 +7,7 @@ #include "Init.hpp" #include #include +#include #include #include #include @@ -222,6 +223,10 @@ namespace Drivers { } } + void PowerDownUnusedGpus() { + Graphics::DgpuPower::PowerDownUnused(); + } + void ProbeNormal() { Pci::ProbeAll(g_driverTable, g_driverTableCount, Pci::ProbePhase::Normal); } diff --git a/kernel/src/Drivers/Init.hpp b/kernel/src/Drivers/Init.hpp index d19d60f..b35ec67 100644 --- a/kernel/src/Drivers/Init.hpp +++ b/kernel/src/Drivers/Init.hpp @@ -14,6 +14,11 @@ namespace Drivers { // Post-probe: wire up GPU framebuffer to cursor subsystem. void InitializeGraphics(); + // Power down discrete GPUs no driver claimed (hybrid-graphics laptops), + // withholding their functions from the Normal-phase probe. Run between + // ProbeEarly() and ProbeNormal(), once the timer is running. + void PowerDownUnusedGpus(); + // Probe PCI devices for Normal-phase drivers (xHCI, E1000, E1000E, AHCI, NVMe, IntelHDA). // USB class drivers (Bluetooth, etc.) initialize automatically during xHCI enumeration. void ProbeNormal(); diff --git a/kernel/src/Memory/Paging.cpp b/kernel/src/Memory/Paging.cpp index 3d75e68..27e4a9d 100644 --- a/kernel/src/Memory/Paging.cpp +++ b/kernel/src/Memory/Paging.cpp @@ -213,6 +213,10 @@ namespace Memory::VMM { PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]); SetLeafPte(pageEntry, physicalAddress, false, true, true); + + // The page may have been mapped before with other cache attributes + // (an HHDM page remapped uncached): drop any stale translation. + asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory"); } void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) { diff --git a/kernel/src/Pci/Pci.cpp b/kernel/src/Pci/Pci.cpp index abb5a31..f43be69 100644 --- a/kernel/src/Pci/Pci.cpp +++ b/kernel/src/Pci/Pci.cpp @@ -11,6 +11,7 @@ #include #include #include +#include using namespace Kt; @@ -246,6 +247,54 @@ namespace Pci { return LegacyRead8(bus, device, function, (uint8_t)offset); } + static bool EcamCovers(uint8_t bus) { + return g_useEcam && bus >= g_ecamStartBus && bus <= g_ecamEndBus; + } + + uint32_t ConfigRead(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width) { + if (device > 31 || function > 7 || offset > 0xFFF || (offset & (width - 1))) + return 0xFFFFFFFF; + + if (EcamCovers(bus)) { + switch (width) { + case 1: return EcamRead8(g_ecamBase, bus, device, function, offset); + case 2: return EcamRead16(g_ecamBase, bus, device, function, offset); + case 4: return EcamRead32(g_ecamBase, bus, device, function, offset); + default: return 0xFFFFFFFF; + } + } + + if (offset > 0xFF) return 0xFFFFFFFF; + switch (width) { + case 1: return LegacyRead8(bus, device, function, (uint8_t)offset); + case 2: return LegacyRead16(bus, device, function, (uint8_t)offset); + case 4: return LegacyRead32(bus, device, function, (uint8_t)offset); + default: return 0xFFFFFFFF; + } + } + + bool ConfigWrite(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width, uint32_t value) { + if (device > 31 || function > 7 || offset > 0xFFF || (offset & (width - 1))) + return false; + + if (EcamCovers(bus)) { + switch (width) { + case 1: EcamWrite8(g_ecamBase, bus, device, function, offset, (uint8_t)value); return true; + case 2: EcamWrite16(g_ecamBase, bus, device, function, offset, (uint16_t)value); return true; + case 4: EcamWrite32(g_ecamBase, bus, device, function, offset, value); return true; + default: return false; + } + } + + if (offset > 0xFF) return false; + switch (width) { + case 1: LegacyWrite8(bus, device, function, (uint8_t)offset, (uint8_t)value); return true; + case 2: LegacyWrite16(bus, device, function, (uint8_t)offset, (uint16_t)value); return true; + case 4: LegacyWrite32(bus, device, function, (uint8_t)offset, value); return true; + default: return false; + } + } + // ------------------------------------------------------------------------- // PCI capability list traversal // ------------------------------------------------------------------------- @@ -493,6 +542,46 @@ namespace Pci { LegacyWrite16(bus, device, function, (uint8_t)PCI_REG_COMMAND, cmd); } + bool IsBridge(const PciDevice& dev) { + return (dev.HeaderType & 0x7F) == 0x01; + } + + // PMCSR lives at PM capability + 4; its low two bits are the D-state. + static constexpr uint16_t PmcsrOffset = 4; + + int GetPowerState(uint8_t bus, uint8_t device, uint8_t function) { + if (ReadConfig16(bus, device, function, RegVendorId) == 0xFFFF) return -1; + uint8_t pm = FindCapability(bus, device, function, PCI_CAP_PM); + if (pm == 0) return -1; + return (int)(ReadConfig16(bus, device, function, pm + PmcsrOffset) & 0x3); + } + + bool SetPowerState(uint8_t bus, uint8_t device, uint8_t function, int state) { + if (state < 0 || state > 3) return false; + uint8_t pm = FindCapability(bus, device, function, PCI_CAP_PM); + if (pm == 0) return false; + + // D1/D2 are optional; PMC bits 25/26 advertise them. + uint32_t pmc = ConfigRead(bus, device, function, pm, 4); + if (state == 1 && !(pmc & (1u << 25))) return false; + if (state == 2 && !(pmc & (1u << 26))) return false; + + uint16_t pmcsr = (uint16_t)ConfigRead(bus, device, function, pm + PmcsrOffset, 2); + int current = pmcsr & 0x3; + if (current == state) return true; + + // Keep PME_Status (bit 15, write-1-to-clear) from being cleared by + // accident; only the state field changes. + pmcsr = (uint16_t)((pmcsr & ~0x8003u) | (uint16_t)state); + ConfigWrite(bus, device, function, pm + PmcsrOffset, 2, pmcsr); + + // PCI PM 1.2 table 5-6: 10 ms into or out of D3hot, 200 us for D2. + if (state == 3 || current == 3) Timekeeping::BusyWaitMs(10); + else if (state == 2 || current == 2) Timekeeping::BusyWaitUs(200); + + return GetPowerState(bus, device, function) == state; + } + // ------------------------------------------------------------------------- // PCI driver matching // ------------------------------------------------------------------------- @@ -523,7 +612,9 @@ namespace Pci { void ProbeAll(const PciDriverDesc* table, uint16_t count, ProbePhase phase) { for (uint64_t d = 0; d < g_devices.size(); d++) { - const PciDevice& dev = g_devices[d]; + PciDevice& dev = g_devices[d]; + if (dev.Claimed || dev.Reserved) + continue; for (uint16_t i = 0; i < count; i++) { if (table[i].Phase != phase) @@ -537,6 +628,7 @@ namespace Pci { << "." << (uint64_t)dev.Function; if (table[i].Probe(dev)) { + dev.Claimed = true; KernelLogStream(OK, "PCI") << table[i].Name << " claimed device " << base::hex << (uint64_t)dev.Bus << ":" << (uint64_t)dev.Device << "." << (uint64_t)dev.Function; diff --git a/kernel/src/Pci/Pci.hpp b/kernel/src/Pci/Pci.hpp index 4837df8..48cf656 100644 --- a/kernel/src/Pci/Pci.hpp +++ b/kernel/src/Pci/Pci.hpp @@ -40,6 +40,9 @@ namespace Pci { uint8_t ProgIf; uint8_t RevisionId; uint8_t HeaderType; + + bool Claimed; // a driver's Probe() accepted it + bool Reserved; // withheld from driver probing (e.g. powered down) }; // Configuration space access (ECAM / memory-mapped) @@ -60,8 +63,18 @@ namespace Pci { void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value); void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value); + // Configuration access by whichever mechanism reaches the register: + // ECAM when the bus is covered by MCFG (required for offsets >= 0x100), + // the legacy CF8/CFC ports otherwise. width is 1, 2 or 4 and offset must + // be aligned to it. Reads of an unreachable register return all ones; + // writes to one return false. + uint32_t ConfigRead(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width); + bool ConfigWrite(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width, uint32_t value); + // PCI capability IDs + constexpr uint8_t PCI_CAP_PM = 0x01; constexpr uint8_t PCI_CAP_MSI = 0x05; + constexpr uint8_t PCI_CAP_PCIE = 0x10; constexpr uint8_t PCI_CAP_MSIX = 0x11; // Walk the PCI capability linked list for a given device. @@ -97,6 +110,30 @@ namespace Pci { // Enable memory space access and bus mastering in PCI command register. void EnableBusMaster(uint8_t bus, uint8_t device, uint8_t function); + // Type 1 (PCI-to-PCI bridge) header registers + constexpr uint16_t PCI_REG_PRIMARY_BUS = 0x18; + constexpr uint16_t PCI_REG_SECONDARY_BUS = 0x19; + constexpr uint16_t PCI_REG_SUBORDINATE_BUS = 0x1A; + constexpr uint16_t PCI_REG_MEM_BASE = 0x20; + constexpr uint16_t PCI_REG_PREF_BASE = 0x24; + constexpr uint16_t PCI_REG_PREF_BASE_UPPER = 0x28; + constexpr uint16_t PCI_REG_PREF_LIMIT_UPPER = 0x2C; + constexpr uint16_t PCI_REG_BRIDGE_CONTROL = 0x3E; + constexpr uint16_t PCI_BRIDGE_CTL_VGA = (1 << 3); + + // True for a function with a type 1 (PCI-to-PCI bridge) header. + bool IsBridge(const PciDevice& dev); + + // The PCI power state of a function from its PM capability: 0..3 for + // D0..D3hot, -1 when it has no PM capability or does not answer (which + // is also what a function in D3cold looks like). + int GetPowerState(uint8_t bus, uint8_t device, uint8_t function); + + // Move a function to D0..D3hot through PMCSR and wait out the transition + // recovery time the PCI PM spec requires. Returns false when the + // function has no PM capability or did not take the state. + bool SetPowerState(uint8_t bus, uint8_t device, uint8_t function, int state); + // Disable legacy INTx plus any enabled MSI/MSI-X capability. Returns true // when message-signalled delivery had been enabled and was turned off. bool DisableInterruptDelivery(uint8_t bus, uint8_t device, uint8_t function); diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index d1d2993..f724cfc 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -365,6 +365,35 @@ namespace Timekeeping { Hal::HaltWithInterruptsDisabled(); } + void BusyWaitUs(uint64_t us) { + // An I/O write to port 0x80 is an ISA-bus cycle of roughly a + // microsecond on every chipset since the 486, and never shorter. + for (uint64_t i = 0; i < us; i++) + Io::IoPortWait(); + } + + void BusyWaitMs(uint64_t ms) { + if (ms == 0) return; + + uint64_t flags; + asm volatile("pushfq; pop %0" : "=r"(flags)); + bool ticking = g_ticksPerMs != 0 && (flags & (1 << 9)); + + // Either clock alone is long enough, so stop at whichever says so + // first. The tick is the accurate one; the port writes bound the wait + // when the tick is not running yet (or is masked), so this can never + // hang on a timer that does not fire. One extra tick of slack because + // the first may already be nearly over. + uint64_t start = g_tickCount.load(std::memory_order_relaxed); + uint64_t portBudget = (ms + 1) * 1000; + for (uint64_t spins = 0; spins < portBudget; spins++) { + if (ticking && + g_tickCount.load(std::memory_order_relaxed) - start >= ms + 1) + return; + Io::IoPortWait(); + } + } + void Sleep(uint64_t ms) { if (ms == 0) return; // Use BlockForSleep to properly deschedule the process instead diff --git a/kernel/src/Timekeeping/ApicTimer.hpp b/kernel/src/Timekeeping/ApicTimer.hpp index a614178..6e3c98d 100644 --- a/kernel/src/Timekeeping/ApicTimer.hpp +++ b/kernel/src/Timekeeping/ApicTimer.hpp @@ -43,4 +43,12 @@ namespace Timekeeping { // Busy-wait sleep for the given number of milliseconds void Sleep(uint64_t ms); + + // Spin for at least `ms` milliseconds without descheduling. Usable from + // boot code, before the scheduler runs, and with interrupts disabled + // (falls back to timed I/O port writes when the tick is not advancing). + void BusyWaitMs(uint64_t ms); + + // Spin for at least `us` microseconds. Port-0x80 timed, so it errs long. + void BusyWaitUs(uint64_t us); };