Files
MontaukOS/kernel/src/ACPI/AML/AmlInterpreter.cpp
T

3799 lines
154 KiB
C++

/*
* AmlInterpreter.cpp
* AML bytecode interpreter — loads DSDT/SSDT into namespace, executes methods
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlInterpreter.hpp"
#include <ACPI/ACPI.hpp>
#include <ACPI/EmbeddedController.hpp>
#include <Boot/Boot.hpp>
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Io/IoPort.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Memory/Heap.hpp>
#include <Libraries/Memory.hpp>
#include <Timekeeping/Timer.hpp>
using namespace Kt;
namespace Hal {
namespace AML {
// ============================================================================
// Global instance
// ============================================================================
static Interpreter g_interpreter;
Interpreter& GetInterpreter() {
return g_interpreter;
}
// ============================================================================
// Helper: is a byte a lead name character?
// ============================================================================
static bool IsLeadNameChar(uint8_t c) {
return (c >= 'A' && c <= 'Z') || c == '_';
}
// 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
// stack early on, a thread's 16 KiB kernel stack once userspace is up.
// m_stackFloor is anchored at the top-level entry; a frame below it has
// spent the budget and must unwind rather than keep recursing.
bool Interpreter::HaveStackHeadroom(const void* frame) const {
if (m_stackFloor == 0) return true; // no evaluation in progress
if ((uint64_t)frame > m_stackFloor) return true;
// Say so once. A method that quietly gives up here returns a
// default value that looks like a hardware answer -- a battery
// reporting itself absent, say -- and nothing upstream can tell
// the difference.
static bool warned = false;
if (!warned) {
warned = true;
KernelLogStream(WARNING, "AML")
<< "Evaluation stopped: spent the " << base::dec
<< (uint64_t)(StackBudget / 1024) << " KiB stack allowance";
}
return false;
}
void Interpreter::ReportDepthLimit() {
static bool warned = false;
if (!warned) {
warned = true;
KernelLogStream(WARNING, "AML") << "Evaluation stopped: nesting past "
<< base::dec << (uint64_t)MaxExprDepth << " levels";
}
}
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) {
uint64_t limit = Memory::VMM::Paging::HhdmLimit();
if (limit == 0) return false;
if (address >= limit) return false;
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_initialized(false), m_predefined(false), m_dsdtNodeCount(0),
m_ctxPool(nullptr), m_ctxInUse(0), m_evalNesting(0), m_stackFloor(0),
m_traceBudget(0), m_loadCtx(nullptr), m_delayedUs(0), m_methodScan(0) {}
// ============================================================================
// Execution context pool
// ============================================================================
Interpreter::ExecContext* Interpreter::AcquireContext() {
if (!m_ctxPool) {
m_ctxPool = (ExecContext*)Memory::g_heap->Request(
sizeof(ExecContext) * (MaxCallDepth + 1));
if (!m_ctxPool) return nullptr;
}
if (m_ctxInUse > MaxCallDepth) return nullptr;
ExecContext* ctx = &m_ctxPool[m_ctxInUse++];
*ctx = ExecContext{};
return ctx;
}
void Interpreter::ReleaseContext() {
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<typename Fn>
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
// ============================================================================
static uint32_t PkgLength(const uint8_t* aml, uint32_t* pos) {
uint8_t lead = aml[*pos];
uint32_t byteCount = (lead >> 6) & 0x03;
if (byteCount == 0) {
(*pos)++;
return lead & 0x3F;
}
uint32_t length = lead & 0x0F;
(*pos)++;
for (uint32_t i = 0; i < byteCount; i++) {
length |= (uint32_t)aml[*pos] << (4 + 8 * i);
(*pos)++;
}
return length;
}
uint32_t Interpreter::DecodePkgLength(const uint8_t* aml, uint32_t* pos) {
return PkgLength(aml, pos);
}
// ============================================================================
// Integer decoding (data objects)
// ============================================================================
static uint64_t ConstInteger(const uint8_t* aml, uint32_t* pos) {
uint8_t op = aml[*pos];
switch (op) {
case ZeroOp:
(*pos)++;
return 0;
case OneOp:
(*pos)++;
return 1;
case OnesOp:
(*pos)++;
return 0xFFFFFFFFFFFFFFFFULL;
case BytePrefix: {
(*pos)++;
uint8_t val = aml[*pos];
(*pos)++;
return val;
}
case WordPrefix: {
(*pos)++;
uint16_t val = aml[*pos] | ((uint16_t)aml[*pos + 1] << 8);
*pos += 2;
return val;
}
case DWordPrefix: {
(*pos)++;
uint32_t val = aml[*pos]
| ((uint32_t)aml[*pos + 1] << 8)
| ((uint32_t)aml[*pos + 2] << 16)
| ((uint32_t)aml[*pos + 3] << 24);
*pos += 4;
return val;
}
case QWordPrefix: {
(*pos)++;
uint64_t val = 0;
for (int i = 0; i < 8; i++)
val |= (uint64_t)aml[*pos + i] << (8 * i);
*pos += 8;
return val;
}
default:
(*pos)++;
return 0;
}
}
uint64_t Interpreter::DecodeInteger(const uint8_t* aml, uint32_t* pos) {
return ConstInteger(aml, pos);
}
// ============================================================================
// NameSeg reading
// ============================================================================
void Interpreter::ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg) {
for (int i = 0; i < 4; i++) {
outSeg[i] = (char)aml[*pos + i];
}
outSeg[4] = '\0';
*pos += 4;
}
// 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
// ============================================================================
// Reads a NameString (which may include root prefix, parent prefixes,
// dual/multi name prefix) and produces an absolute path.
int Interpreter::ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
char* outPath, int maxLen,
char* outRelative, int relLen,
bool* outSearchable) {
int pathPos = 0;
bool isAbsolute = false;
int parentPrefixCount = 0;
// Handle root prefix
if (aml[*pos] == '\\') {
isAbsolute = true;
(*pos)++;
}
// Handle parent prefixes (^)
while (aml[*pos] == '^') {
parentPrefixCount++;
(*pos)++;
}
// Determine segment count
int segCount = 0;
char segments[MaxPathDepth][5];
if (aml[*pos] == 0x00) {
// NullName — zero segments
(*pos)++;
segCount = 0;
} else if (aml[*pos] == DualNamePrefix) {
(*pos)++;
segCount = 2;
ReadNameSeg(aml, pos, segments[0]);
ReadNameSeg(aml, pos, segments[1]);
} else if (aml[*pos] == MultiNamePrefix) {
(*pos)++;
int declared = aml[*pos];
(*pos)++;
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;
ReadNameSeg(aml, pos, segments[0]);
}
// Build the path
if (isAbsolute) {
outPath[pathPos++] = '\\';
} else {
// Build path from scope, walking up for parent prefixes
int32_t baseScope = scopeNode;
for (int i = 0; i < parentPrefixCount && baseScope > 0; i++) {
auto* node = m_ns.GetNode(baseScope);
if (node && node->ParentIndex >= 0)
baseScope = node->ParentIndex;
}
char scopePath[256];
m_ns.GetNodePath(baseScope, scopePath, 256);
int sp = 0;
while (scopePath[sp] && pathPos < maxLen - 1) {
outPath[pathPos++] = scopePath[sp++];
}
if (pathPos > 1 && segCount > 0)
outPath[pathPos++] = '.';
}
// Append segments
for (int i = 0; i < segCount; i++) {
if (i > 0 && pathPos < maxLen - 1)
outPath[pathPos++] = '.';
for (int j = 0; j < 4 && pathPos < maxLen - 1; j++)
outPath[pathPos++] = segments[i][j];
}
outPath[pathPos] = '\0';
// Hand back the name as written, for callers that must apply the
// ACPI search rule when the rebased path does not exist.
if (outSearchable)
*outSearchable = !isAbsolute && parentPrefixCount == 0 && segCount > 0;
if (outRelative && relLen > 0) {
int rp = 0;
for (int i = 0; i < segCount; i++) {
if (i > 0 && rp < relLen - 1) outRelative[rp++] = '.';
for (int j = 0; j < 4 && rp < relLen - 1; j++)
outRelative[rp++] = segments[i][j];
}
outRelative[rp] = '\0';
}
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
// ============================================================================
bool Interpreter::LoadTable(void* tableData) {
auto* header = (ACPI::CommonSDTHeader*)tableData;
if (!ACPI::TestChecksum(header)) {
KernelLogStream(ERROR, "AML") << "Table checksum failed";
return false;
}
char sig[5] = {};
memcpy(sig, header->Signature, 4);
char oemTable[9] = {};
memcpy(oemTable, header->OEMTableID, 8);
const uint8_t* aml = (const uint8_t*)tableData;
uint32_t length = header->Length;
uint32_t dataStart = sizeof(ACPI::CommonSDTHeader);
KernelLogStream(OK, "AML") << "Loading " << sig << " \"" << oemTable << "\" ("
<< base::dec << (uint64_t)(length - dataStart) << " bytes of AML)";
InstallPredefined();
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;
KernelLogStream(OK, "AML") << "Namespace loaded: "
<< base::dec << (uint64_t)m_ns.NodeCount() << " nodes";
return result;
}
// ============================================================================
// ParseBlock
// ============================================================================
// Parse a block of AML opcodes, creating namespace objects.
bool Interpreter::ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode) {
uint32_t pos = offset;
while (pos < endOffset) {
uint8_t op = aml[pos];
// Extended opcode
if (op == ExtOpPrefix) {
if (pos + 1 >= endOffset) break;
if (!ParseExtendedOp(aml, &pos, endOffset, scopeNode))
return false;
continue;
}
// Named object opcodes
if (op == NameOp || op == ScopeOp || op == MethodOp ||
op == BufferOp || op == PackageOp || 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; }
if (op == WordPrefix) { pos += 3; continue; }
if (op == DWordPrefix) { pos += 5; continue; }
if (op == QWordPrefix) { pos += 9; continue; }
if (op == StringPrefix) {
pos++;
while (pos < endOffset && aml[pos] != 0) pos++;
pos++; // skip null terminator
continue;
}
if (op == ZeroOp || op == OneOp || op == OnesOp || op == NoopOp) {
pos++;
continue;
}
// If/Else/While — skip the block (we only parse structure during load)
if (op == IfOp || op == ElseOp || op == WhileOp) {
pos++;
uint32_t pkgStart = pos;
uint32_t pkgLen = DecodePkgLength(aml, &pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
// Parse inside for nested definitions
ParseBlock(aml, pos, blockEnd, scopeNode);
pos = blockEnd;
continue;
}
// Store, arithmetic, logical, etc. — skip during load
if (op == StoreOp || op == ReturnOp || op == BreakOp ||
op == AddOp || op == SubtractOp || op == MultiplyOp ||
op == AndOp || op == OrOp || op == XorOp || op == NotOp ||
op == ShiftLeftOp || op == ShiftRightOp ||
op == IncrementOp || op == DecrementOp ||
op == DivideOp || op == IndexOp || op == SizeOfOp ||
op == DerefOfOp || op == ConcatOp ||
(op >= LAndOp && op <= LLessOp) ||
op == ToIntegerOp || op == ToBufferOp) {
// Can't easily skip these without parsing — just advance one byte
// and hope the next byte resynchronizes. This is imprecise but
// works for top-level definitions which are what we need.
pos++;
continue;
}
// Name characters (part of a name reference we don't need during load)
if (IsNameStart(op)) {
// Skip the name
if (op == '\\' || op == '^') { pos++; continue; }
if (op == DualNamePrefix) { pos += 9; continue; } // prefix + 2*4
if (op == MultiNamePrefix) {
uint8_t count = aml[pos + 1];
pos += 2 + count * 4;
continue;
}
// Single NameSeg
pos += 4;
continue;
}
// Locals and args
if (IsLocalOp(op) || IsArgOp(op)) {
pos++;
continue;
}
// Unknown — advance
pos++;
}
return true;
}
// ============================================================================
// 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, ExecContext* ctx) {
uint8_t op = aml[*pos];
(*pos)++;
if (op == ScopeOp) {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
if (node < 0) {
*pos = blockEnd;
return true;
}
ParseBlock(aml, *pos, blockEnd, node);
*pos = blockEnd;
return true;
}
if (op == NameOp) {
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
// Parse the data object
uint8_t dataOp = aml[*pos];
Object value{};
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;
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 {
// 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;
}
if (op == MethodOp) {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t methodEnd = pkgStart + pkgLen;
if (methodEnd > endOffset) methodEnd = endOffset;
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
if (node < 0) {
*pos = methodEnd;
return true;
}
auto* nsNode = m_ns.GetNode(node);
if (!nsNode) { *pos = methodEnd; return true; }
// Method flags byte
uint8_t flags = aml[*pos];
(*pos)++;
nsNode->Obj = 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).
// 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 || op == VarPackageOp) {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
*pos = blockEnd;
return true;
}
return true;
}
// ============================================================================
// 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, ExecContext* ctx) {
(*pos)++; // skip ExtOpPrefix
if (*pos >= endOffset) return true;
uint8_t extOp = aml[*pos];
(*pos)++;
switch (extOp) {
case DeviceOp: {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
if (node >= 0) {
auto* nsNode = m_ns.GetNode(node);
if (nsNode) nsNode->Obj.Type = ObjectType::Device;
ParseBlock(aml, *pos, blockEnd, node);
}
*pos = blockEnd;
return true;
}
case OpRegionOp: {
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
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;
}
case FieldOp: {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t fieldEnd = pkgStart + pkgLen;
if (fieldEnd > endOffset) fieldEnd = endOffset;
// Region name. A Field almost always names a region
// declared in an enclosing scope, so the search rule is not
// optional here.
int32_t regionNode = ReadAndResolveName(aml, pos, scopeNode);
uint8_t fieldFlags = aml[*pos];
(*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;
}
case IndexFieldOp: {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t fieldEnd = pkgStart + pkgLen;
if (fieldEnd > endOffset) fieldEnd = endOffset;
int32_t indexNode = ReadAndResolveName(aml, pos, scopeNode);
int32_t dataNode = ReadAndResolveName(aml, pos, scopeNode);
uint8_t fieldFlags = aml[*pos];
(*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;
}
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;
}
case ProcessorOp: {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
if (node >= 0) {
auto* nsNode = m_ns.GetNode(node);
if (nsNode) {
nsNode->Obj.Type = ObjectType::Processor;
nsNode->Obj.Processor.ProcId = aml[*pos]; (*pos)++;
nsNode->Obj.Processor.PblkAddr = aml[*pos]
| ((uint32_t)aml[*pos + 1] << 8)
| ((uint32_t)aml[*pos + 2] << 16)
| ((uint32_t)aml[*pos + 3] << 24);
*pos += 4;
nsNode->Obj.Processor.PblkLen = aml[*pos]; (*pos)++;
}
ParseBlock(aml, *pos, blockEnd, node);
}
*pos = blockEnd;
return true;
}
case ThermalZoneOp: {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
if (node >= 0) {
auto* nsNode = m_ns.GetNode(node);
if (nsNode) nsNode->Obj.Type = ObjectType::ThermalZone;
ParseBlock(aml, *pos, blockEnd, node);
}
*pos = blockEnd;
return true;
}
case PowerResOp: {
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(aml, pos);
uint32_t blockEnd = pkgStart + pkgLen;
if (blockEnd > endOffset) blockEnd = endOffset;
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
int32_t node = m_ns.CreateNode(path);
if (node >= 0) {
auto* nsNode = m_ns.GetNode(node);
if (nsNode) nsNode->Obj.Type = ObjectType::PowerResource;
// Skip SystemLevel and ResourceOrder
if (*pos + 3 <= blockEnd) *pos += 3;
ParseBlock(aml, *pos, blockEnd, node);
}
*pos = blockEnd;
return true;
}
case MutexOp: {
char path[256];
ReadNameString(aml, pos, scopeNode, path, 256);
(*pos)++; // SyncFlags
int32_t node = m_ns.CreateNode(path);
if (node >= 0) {
auto* nsNode = m_ns.GetNode(node);
if (nsNode) nsNode->Obj.Type = ObjectType::Mutex;
}
return true;
}
case 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 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: {
Object dummy{};
EvalOperand(ctx, aml, pos, endOffset, scopeNode, dummy);
return true;
}
case AcquireOp: {
// NameString + Timeout(word)
char dummy[256];
ReadNameString(aml, pos, scopeNode, dummy, 256);
*pos += 2; // timeout
return true;
}
case ReleaseOp: {
char dummy[256];
ReadNameString(aml, pos, scopeNode, dummy, 256);
return true;
}
default:
// Unknown extended opcode — skip one byte
return true;
}
}
// ============================================================================
// Buffer fields
// ============================================================================
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);
}
Object index{};
EvalTerm(ctx, pos, endOffset, index);
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;
}
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;
}
// ============================================================================
// 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);
if (node < 0) return false;
auto* nsNode = m_ns.GetNode(node);
if (!nsNode || nsNode->Obj.Type != ObjectType::Method) return false;
return EvaluateNode(node, args, argCount, result);
}
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;
}
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) 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 = nsNode->Obj.Method.Table;
ctx->AmlBase = nsNode->Obj.Method.AmlOffset;
ctx->AmlLength = nsNode->Obj.Method.AmlLength;
ctx->ScopeNode = node;
ctx->Depth = depth;
ctx->ExprDepth = exprDepth;
for (int i = 0; i < MaxMethodLocals; i++)
ctx->Locals[i] = MakeInteger(0);
ExecuteBlock(*ctx, ctx->AmlBase, ctx->AmlBase + ctx->AmlLength);
result = ctx->ReturnValue;
if (result.Type == ObjectType::None)
result = MakeInteger(0);
bool ok = !ctx->Aborted;
ReleaseContext();
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
// ============================================================================
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)) {
ctx.Aborted = true;
return false;
}
DepthGuard depthGuard(ctx.ExprDepth);
uint32_t pos = offset;
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 !ctx.Aborted;
}
// ============================================================================
// ExecuteOpcode
// ============================================================================
bool Interpreter::ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset) {
if (*pos >= endOffset) return true;
uint8_t op = ctx.Aml[*pos];
if (m_traceBudget > 0) {
m_traceBudget--;
// 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];
} else {
KernelLogStream(INFO, "AML") << " @" << base::hex << (uint64_t)*pos
<< " op " << (uint64_t)op;
}
}
// Return
if (op == ReturnOp) {
(*pos)++;
Object val{};
EvalTerm(ctx, pos, endOffset, val);
ctx.ReturnValue = val;
ctx.Returned = true;
return true;
}
// Break
if (op == BreakOp) {
(*pos)++;
ctx.Broken = true;
return true;
}
if (op == ContinueOp) {
(*pos)++;
ctx.Continued = true;
return true;
}
// Noop
if (op == NoopOp || op == BreakPointOp) {
(*pos)++;
return true;
}
// If
if (op == IfOp) {
(*pos)++;
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos);
uint32_t ifEnd = pkgStart + pkgLen;
if (ifEnd > endOffset) ifEnd = endOffset;
Object predicate{};
EvalTerm(ctx, pos, ifEnd, predicate);
if (ctx.Aborted) return false;
bool condition = ToInt(predicate) != 0;
if (condition) {
ExecuteBlock(ctx, *pos, ifEnd);
}
*pos = ifEnd;
// Check for Else
if (*pos < endOffset && ctx.Aml[*pos] == ElseOp) {
(*pos)++;
uint32_t elsePkgStart = *pos;
uint32_t elsePkgLen = DecodePkgLength(ctx.Aml, pos);
uint32_t elseEnd = elsePkgStart + elsePkgLen;
if (elseEnd > endOffset) elseEnd = endOffset;
if (!condition && !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;
}
// While
if (op == WhileOp) {
(*pos)++;
uint32_t pkgStart = *pos;
uint32_t pkgLen = DecodePkgLength(ctx.Aml, pos);
uint32_t whileEnd = pkgStart + pkgLen;
if (whileEnd > endOffset) whileEnd = endOffset;
uint32_t bodyStart = *pos;
// 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 (ToInt(predicate) == 0)
break;
ExecuteBlock(ctx, condPos, whileEnd);
ctx.Continued = false;
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 !ctx.Aborted;
}
// Extended opcodes during execution
if (op == ExtOpPrefix) {
if (*pos + 1 >= endOffset) return Abort(ctx, *pos, "Truncated extended opcode");
uint8_t extOp = ctx.Aml[*pos + 1];
// Definitions inside a method body
if (extOp == OpRegionOp || extOp == FieldOp || extOp == DeviceOp ||
extOp == ProcessorOp || extOp == ThermalZoneOp || extOp == PowerResOp ||
extOp == MutexOp || extOp == EventOp || extOp == IndexFieldOp ||
extOp == BankFieldOp || extOp == DataRegionOp || extOp == CreateFieldOp) {
return ParseExtendedOp(ctx.Aml, pos, endOffset, ctx.ScopeNode, &ctx);
}
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, &ctx);
}
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{};
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;
}
}
// ============================================================================
// EvalTerm
// ============================================================================
// 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 = MakeInteger(0);
ctx.Aborted = true;
return false;
}
DepthGuard depthGuard(ctx.ExprDepth);
if (ctx.Aborted || *pos >= endOffset) { result = MakeInteger(0); return false; }
uint8_t op = ctx.Aml[*pos];
switch (op) {
case ZeroOp: case OneOp: case OnesOp:
case BytePrefix: case WordPrefix: case DWordPrefix: case QWordPrefix:
result = MakeInteger(DecodeInteger(ctx.Aml, pos));
return true;
case StringPrefix: {
(*pos)++;
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;
}
case BufferOp:
return EvalBufferOp(ctx, pos, endOffset, result);
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)++;
CreateBufferField(ctx, pos, endOffset, op);
result = MakeInteger(0);
return true;
case ExtOpPrefix:
return EvalExtOp(ctx, pos, endOffset, result);
default:
break;
}
if (IsLocalOp(op)) {
result = ctx.Locals[op - LocalPrefix];
(*pos)++;
return true;
}
if (IsArgOp(op)) {
result = ctx.Args[op - ArgPrefix];
(*pos)++;
return true;
}
if (IsNameStart(op))
return EvalNameRef(ctx, pos, endOffset, result);
// Unknown opcode. Carrying on would execute its operands as code.
result = MakeInteger(0);
return Abort(ctx, *pos, "Unsupported AML opcode");
}
// ============================================================================
// EvalTerm handlers
// ============================================================================
bool Interpreter::EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
Object& result) {
(*pos)++;
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;
}
// ============================================================================
// Extended opcodes
// ============================================================================
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)) {
ctx.Aborted = true;
return false;
}
DepthGuard depthGuard(ctx.ExprDepth);
uint32_t end = ctx.AmlBase + ctx.AmlLength;
if (*pos >= end) return false;
uint8_t op = ctx.Aml[*pos];
// NullName = no target (discard)
if (op == ZeroOp) {
(*pos)++;
return true;
}
if (IsLocalOp(op)) {
t.What = Target::Kind::Local;
t.Index = op - LocalPrefix;
(*pos)++;
return true;
}
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 (IsNameStart(op)) {
t.What = Target::Kind::Node;
t.Node = ReadAndResolveName(ctx.Aml, pos, ctx.ScopeNode);
return true;
}
// 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)++;
return EvalTarget(ctx, pos, t);
}
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, 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;
}
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;
}
}
// ============================================================================
// Package elements
// ============================================================================
bool Interpreter::DecodeConstElement(const uint8_t* data, uint32_t length,
uint32_t* pos, Object& out) const {
out = Object{};
if (*pos >= length) return false;
uint8_t op = data[(*pos)++];
switch (op) {
case ZeroOp:
out = MakeInteger(0);
return true;
case OneOp:
out = MakeInteger(1);
return true;
case OnesOp:
out = MakeInteger(~0ULL);
return true;
default:
break;
}
// Fixed-width integer prefixes
int width = 0;
if (op == BytePrefix) width = 1;
else if (op == WordPrefix) width = 2;
else if (op == DWordPrefix) width = 4;
else if (op == QWordPrefix) width = 8;
if (width > 0) {
if (*pos + (uint32_t)width > length) return false;
uint64_t value = 0;
for (int i = 0; i < width; i++)
value |= (uint64_t)data[*pos + i] << (i * 8);
*pos += (uint32_t)width;
out = MakeInteger(value);
return true;
}
if (op == StringPrefix) {
out.Type = ObjectType::String;
uint16_t n = 0;
while (*pos < length && data[*pos] != 0) {
if (n < MaxStringLen - 1)
out.String.Data[n++] = (char)data[*pos];
(*pos)++;
}
if (*pos < length) (*pos)++; // trailing NUL
out.String.Data[n] = '\0';
out.String.Length = n;
return true;
}
if (op == BufferOp) {
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;
// 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;
}
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 {
count = (uint32_t)ConstInteger(data, pos);
}
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;
}
// 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;
}
int Interpreter::GetPackageCount(const Object& package) {
if (package.Type != ObjectType::Package) return 0;
if (package.PkgIndex > 0)
return PackageStore::Count(package.PkgIndex);
return (int)package.Package.Count;
}
bool Interpreter::GetPackageElement(const Object& package, int index, Object& result) {
result = Object{};
if (package.Type != ObjectType::Package || index < 0) return false;
if (package.PkgIndex > 0) {
Object* elem = PackageStore::Element(package.PkgIndex, index);
if (!elem) return false;
result = *elem;
return true;
}
if (!package.Package.Data || index >= (int)package.Package.Count) return false;
uint32_t length = package.Package.Length;
uint32_t pos = 0;
for (int i = 0; i <= index; i++) {
// 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 && PackageStore::IsPersistent(node->Obj.PkgIndex))
return node->Obj.PkgIndex;
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 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 (!GetPackageElement(node->Obj, i, elem))
break;
Object* slot = PackageStore::Element(store, i);
if (slot) *slot = elem;
}
node->Obj.PkgIndex = store;
return store;
}
// ============================================================================
// Fields
// ============================================================================
bool Interpreter::ReadField(int32_t nodeIndex, uint64_t& value) {
Object v{};
if (!ReadFieldObject(nodeIndex, v)) return false;
value = ToInt(v);
return true;
}
bool Interpreter::WriteField(int32_t nodeIndex, uint64_t value) {
return WriteFieldObject(nodeIndex, MakeInteger(value));
}
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;
}
uint32_t bitLen = node->Obj.Field.BitLength;
uint8_t bits[MaxBufferLen] = {};
bool ok = AccessField(fieldNode, false, bits);
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;
}
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);
}
}
// ============================================================================
// Region access
// ============================================================================
bool Interpreter::ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width,
uint64_t& value) {
return RegionIo(regionNode, offset, width, false, value);
}
bool Interpreter::WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width,
uint64_t value) {
return RegionIo(regionNode, offset, width, true, value);
}
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;
RegionSpace space = node->Obj.Region.Space;
uint64_t base = node->Obj.Region.Offset;
uint64_t length = node->Obj.Region.Length;
// Never touch past the region's declared end: that is where a
// mis-decoded field would otherwise land.
if (offset >= length) return false;
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;
}
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;
// 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;
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 (IsPciRootBridge(d, depth)) {
uint64_t bbn = 0;
IntegerChild(d, "_BBN", 0, bbn, depth);
bus = (uint8_t)bbn;
return true;
}
// 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; }
if (IsPciRootBridge(p, depth)) {
uint64_t bbn = 0;
IntegerChild(p, "_BBN", 0, bbn, depth);
bus = (uint8_t)bbn;
return true;
}
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;
}
};
};