/* * AmlInterpreter.hpp * AML bytecode interpreter — parses DSDT/SSDT into the ACPI namespace * and evaluates methods, fields, and device status * Copyright (c) 2026 Daniel Hammer */ #pragma once #include "AmlNamespace.hpp" #include #include namespace Hal { namespace AML { // ============================================================================ // Extended AML Opcodes // ============================================================================ // Single-byte opcodes static constexpr uint8_t ZeroOp = 0x00; static constexpr uint8_t OneOp = 0x01; static constexpr uint8_t AliasOp = 0x06; static constexpr uint8_t NameOp = 0x08; static constexpr uint8_t BytePrefix = 0x0A; static constexpr uint8_t WordPrefix = 0x0B; static constexpr uint8_t DWordPrefix = 0x0C; static constexpr uint8_t StringPrefix = 0x0D; static constexpr uint8_t QWordPrefix = 0x0E; static constexpr uint8_t ScopeOp = 0x10; static constexpr uint8_t BufferOp = 0x11; static constexpr uint8_t PackageOp = 0x12; static constexpr uint8_t VarPackageOp = 0x13; static constexpr uint8_t MethodOp = 0x14; static constexpr uint8_t DualNamePrefix = 0x2E; static constexpr uint8_t MultiNamePrefix = 0x2F; static constexpr uint8_t LocalPrefix = 0x60; // Local0..Local7 = 0x60..0x67 static constexpr uint8_t ArgPrefix = 0x68; // Arg0..Arg6 = 0x68..0x6E static constexpr uint8_t StoreOp = 0x70; static constexpr uint8_t AddOp = 0x72; static constexpr uint8_t SubtractOp = 0x74; static constexpr uint8_t MultiplyOp = 0x77; static constexpr uint8_t ShiftLeftOp = 0x79; static constexpr uint8_t ShiftRightOp = 0x7A; static constexpr uint8_t AndOp = 0x7B; static constexpr uint8_t NandOp = 0x7C; static constexpr uint8_t OrOp = 0x7D; static constexpr uint8_t NorOp = 0x7E; static constexpr uint8_t XorOp = 0x7F; static constexpr uint8_t NotOp = 0x80; static constexpr uint8_t DerefOfOp = 0x83; static constexpr uint8_t SizeOfOp = 0x87; static constexpr uint8_t IndexOp = 0x88; static constexpr uint8_t CreateDWordFieldOp = 0x8A; static constexpr uint8_t CreateWordFieldOp = 0x8B; static constexpr uint8_t CreateByteFieldOp = 0x8C; static constexpr uint8_t CreateBitFieldOp = 0x8D; static constexpr uint8_t OnesOp = 0xFF; static constexpr uint8_t ReturnOp = 0xA4; static constexpr uint8_t BreakOp = 0xA5; static constexpr uint8_t IfOp = 0xA0; static constexpr uint8_t ElseOp = 0xA1; static constexpr uint8_t WhileOp = 0xA2; static constexpr uint8_t NoopOp = 0xA3; static constexpr uint8_t ConcatOp = 0x73; static constexpr uint8_t ToIntegerOp = 0x99; static constexpr uint8_t ToBufferOp = 0x96; static constexpr uint8_t RevisionOp = 0x30; // not a real AML op, used internally // ExtOp prefix (0x5B) followed by second byte static constexpr uint8_t ExtOpPrefix = 0x5B; static constexpr uint8_t MutexOp = 0x01; // after ExtOpPrefix static constexpr uint8_t EventOp = 0x02; static constexpr uint8_t OpRegionOp = 0x80; static constexpr uint8_t FieldOp = 0x81; static constexpr uint8_t DeviceOp = 0x82; static constexpr uint8_t ProcessorOp = 0x83; static constexpr uint8_t PowerResOp = 0x84; static constexpr uint8_t ThermalZoneOp = 0x85; static constexpr uint8_t IndexFieldOp = 0x86; static constexpr uint8_t BankFieldOp = 0x87; static constexpr uint8_t AcquireOp = 0x23; static constexpr uint8_t ReleaseOp = 0x27; static constexpr uint8_t SleepOp = 0x22; static constexpr uint8_t StallOp = 0x21; static constexpr uint8_t LNotOp = 0x92; // single-byte, actually static constexpr uint8_t LEqualOp = 0x93; // single-byte static constexpr uint8_t LGreaterOp = 0x94; // single-byte static constexpr uint8_t LLessOp = 0x95; // single-byte static constexpr uint8_t LAndOp = 0x90; // single-byte static constexpr uint8_t LOrOp = 0x91; // single-byte static constexpr uint8_t IncrementOp = 0x75; static constexpr uint8_t DecrementOp = 0x76; static constexpr uint8_t DivideOp = 0x78; static constexpr uint8_t ModOp = 0x85; static constexpr uint8_t ConcatResOp = 0x84; static constexpr uint8_t ToHexStringOp = 0x98; static constexpr uint8_t ToDecimalStringOp = 0x97; static constexpr uint8_t ExternalOp = 0x15; static constexpr uint8_t RefOfOp = 0x71; static constexpr uint8_t FindSetLeftBitOp = 0x81; static constexpr uint8_t FindSetRightBitOp = 0x82; static constexpr uint8_t NotifyOp = 0x86; static constexpr uint8_t MatchOp = 0x89; static constexpr uint8_t ObjectTypeOp = 0x8E; static constexpr uint8_t CreateQWordFieldOp = 0x8F; static constexpr uint8_t ToStringOp = 0x9C; static constexpr uint8_t CopyObjectOp = 0x9D; static constexpr uint8_t MidOp = 0x9E; static constexpr uint8_t ContinueOp = 0x9F; static constexpr uint8_t BreakPointOp = 0xCC; // More extended opcodes (after ExtOpPrefix) static constexpr uint8_t CondRefOfOp = 0x12; static constexpr uint8_t CreateFieldOp = 0x13; static constexpr uint8_t LoadTableOp = 0x1F; static constexpr uint8_t LoadOp = 0x20; static constexpr uint8_t SignalOp = 0x24; static constexpr uint8_t WaitOp = 0x25; static constexpr uint8_t ResetOp = 0x26; static constexpr uint8_t FromBCDOp = 0x28; static constexpr uint8_t ToBCDOp = 0x29; static constexpr uint8_t UnloadOp = 0x2A; static constexpr uint8_t DebugOp = 0x31; static constexpr uint8_t FatalOp = 0x32; static constexpr uint8_t TimerOp = 0x33; static constexpr uint8_t DataRegionOp = 0x88; // ============================================================================ // Interpreter Configuration // ============================================================================ static constexpr int MaxCallDepth = 16; // ACPICA's ceiling. Firmware polls hardware in While loops with a // Sleep or Stall in the body (link training, power-good, L2/L3 ready) // and expects more than a token number of passes. static constexpr int MaxLoopIterations = 0xFFFF; // Backstop cap on evaluation nesting. This counter is shared by // expression evaluation AND block execution, and block nesting is // legitimately deep -- every If/Else/While body is another level, so a // long firmware method reaches well past a handful. The real protection // against running off the stack is HaveStackHeadroom(), which measures // actual usage; this only catches a decode that recurses without // consuming input. static constexpr int MaxExprDepth = 48; // How much stack the interpreter may consume before it gives up. Note // this is an ALLOWANCE, not a reserve: frames here are expensive (a // name-resolving EvalTerm frame carries a 256-byte path buffer plus // 272-byte Object temporaries, so ~800 bytes each), and a real // firmware _BIF nests far enough to need tens of them. Sized against // Sched::StackPages -- keep well under it, since the syscall path below // has already spent some of that stack. static constexpr uint64_t StackBudget = 32768; // ============================================================================ // Interpreter // ============================================================================ class Interpreter { public: Interpreter(); // Parse a DSDT or SSDT table into the namespace. Tables may be // loaded one after another; each method remembers which table // its body lives in. tableData points to the CommonSDTHeader // (HHDM-mapped) and must stay mapped. bool LoadTable(void* tableData); // Evaluate a named object, returning its value. // For methods, executes them with no arguments. bool EvaluateObject(const char* path, Object& result); // Evaluate a method with arguments. bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result); // Evaluate a node: a method is run with `args`, a field is read, // anything else hands back its value (non-data objects as a // Reference to the node). bool EvaluateNode(int32_t node, const Object* args, int argCount, Object& result); // Find the child of `parent` named `seg` ("_PS3", "_ON"), -1 if // there is none. Short names are padded with '_'. int32_t FindChild(int32_t parent, const char* seg) const; // EvaluateNode() on a named child. False when it does not exist. bool EvaluateChild(int32_t parent, const char* seg, const Object* args, int argCount, Object& result); // Turn a Reference, or a NameRef/String naming an object relative // to `scopeNode`, into a node index. -1 when it names nothing. int32_t ResolveReference(const Object& obj, int32_t scopeNode) const; // The PCI function a Device node describes: its _ADR on the bus // behind its parent (a root bridge's _BBN, or a PCI-to-PCI // bridge's secondary bus). False when it cannot be placed. bool GetPciAddress(int32_t deviceNode, uint8_t& bus, uint8_t& dev, uint8_t& fn); // Read a field value. Returns integer. bool ReadField(int32_t nodeIndex, uint64_t& value); // Write a field value. bool WriteField(int32_t nodeIndex, uint64_t value); // Get the namespace for direct queries. Namespace& GetNamespace() { return m_ns; } const Namespace& GetNamespace() const { return m_ns; } // Check if the interpreter has been initialized bool IsInitialized() const { return m_initialized; } // Nodes [0, DsdtNodeCount()) came from the DSDT. Nodes past it were // added by SSDTs (or by methods run since). int32_t DsdtNodeCount() const { return m_dsdtNodeCount; } // Log every opcode executed, with its offset, for the next // `budget` opcodes. The only way to see where a method that runs to // completion without returning a value actually goes. void SetTrace(int budget) { m_traceBudget = budget; } // ============================================================================ // Packages // ============================================================================ // Read one element of a Package object. Works for both a package // whose elements were computed at run time (materialized in the // package store) and one that is still just the constant AML bytes // it was declared with. A name in a constant package comes back as // a NameRef unless it resolves (from the root) to plain data, in // which case its value is returned. Returns false when the object // is not a package or the index is out of range. bool GetPackageElement(const Object& package, int index, Object& result); // Number of elements a Package declares. int GetPackageCount(const Object& package); // Give a package stored in a namespace node real per-element // storage so Index() can be written through. Returns the store // index, or -1 if the node does not hold a package. int32_t MaterializeNodePackage(int32_t nodeIndex); private: struct ExecContext; // ============================================================================ // Parsing (table load) // ============================================================================ // ctx is the method being executed when a definition appears in // a method body, nullptr at table load. Operands that are TermArgs // (region offsets, buffer sizes, bank values) are evaluated in it. bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset, int32_t scopeNode); bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, int32_t scopeNode, ExecContext* ctx = nullptr); bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset, int32_t scopeNode, ExecContext* ctx = nullptr); // The FieldList shared by Field, IndexField and BankField. void ParseFieldList(const uint8_t* aml, uint32_t* pos, uint32_t fieldEnd, int32_t scopeNode, uint8_t fieldFlags, const Object& proto); // Evaluate a TermArg operand of a definition: in the running // method, or in the table-load context. bool EvalOperand(ExecContext* ctx, const uint8_t* aml, uint32_t* pos, uint32_t endOffset, int32_t scopeNode, Object& result); // CreateBitField .. CreateQWordField and CreateField. bool CreateBufferField(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, uint8_t op); // ============================================================================ // Name resolution // ============================================================================ // Read a NameString from AML and produce an absolute path. // Advances *pos past the name. // Produces the absolute path (rebased onto scopeNode for a // relative name). outRelative, when given, receives the name as it // actually appeared in the AML, and *outSearchable says whether the // ACPI search rule applies to it -- true only when the name carried // no root or parent prefix. Resolution must fall back to // Namespace::SearchUpward() in that case: the absolute path is a // guess at one scope, and the object usually lives in a parent. int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode, char* outPath, int maxLen, char* outRelative = nullptr, int relLen = 0, bool* outSearchable = nullptr); // Read a NameString and resolve it with the search rule. -1 when // it names nothing. int32_t ReadAndResolveName(const uint8_t* aml, uint32_t* pos, int32_t scopeNode); // Read a single 4-char NameSeg from AML. Advances *pos. void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg); // ============================================================================ // Value decoding // ============================================================================ uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos); // Decode one constant package element out of raw AML bytes and // advance *pos past it. Names come back as NameRef; locals and // anything else that needs a running method decode to integer 0. bool DecodeConstElement(const uint8_t* data, uint32_t length, uint32_t* pos, Object& out) const; uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos); // ============================================================================ // Method execution // ============================================================================ struct ExecContext { const uint8_t* Aml; uint32_t AmlBase; // start of the block within the table uint32_t AmlLength; int32_t ScopeNode; Object Locals[MaxMethodLocals]; Object Args[MaxMethodArgs]; Object ReturnValue; bool Returned; bool Broken; bool Continued; bool Aborted; // undecodable AML: unwind the method int Depth; // method-call nesting int ExprDepth; // expression/block nesting }; // Increments a depth counter for the duration of a scope; pairs // with the MaxExprDepth check at each recursive entry point. struct DepthGuard { int& Value; explicit DepthGuard(int& v) : Value(v) { Value++; } ~DepthGuard() { Value--; } }; // Where a Store lands. struct Target { enum class Kind : uint8_t { None, Node, Local, Arg, PkgElement, BufByte }; Kind What = Kind::None; int32_t Node = -1; int Index = 0; // local/arg number, or element index int32_t PkgStore = -1; Object* Buf = nullptr; // BufByte: the buffer being indexed }; bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset); bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset); // Evaluate a term (expression that produces a value) within an execution context. bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); // Extended (0x5B-prefixed) opcodes in expression or statement position. bool EvalExtOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); // EvalTerm's opcode handlers. They live out of line so the // recursive path only carries the locals of the one running. bool EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalPackageOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalBinary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalUnary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalDivide(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalStoreOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalReference(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalIndexOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalMatchOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalMidOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool EvalNameRef(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); bool CallMethod(ExecContext& ctx, int32_t node, uint32_t* pos, uint32_t endOffset, Object& result); bool IndexElement(const Object& source, uint64_t idx, Object& out); void ReportUnresolved(ExecContext& ctx, uint32_t namePos); // Target forms that need operand evaluation of their own. bool EvalIndexTarget(ExecContext& ctx, uint32_t* pos, Target& target); bool EvalDerefTarget(ExecContext& ctx, uint32_t* pos, Target& target); // A package element: like EvalTerm, except that a name is a // reference to the object rather than a call or a read. bool EvalPackageElement(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result); // Parse a SuperName/Target operand. bool EvalTarget(ExecContext& ctx, uint32_t* pos, Target& target); // Store a value to a target, with ACPI's implicit conversion for // named data objects. void StoreToTarget(ExecContext& ctx, const Object& value, const Target& target); // Parse a Target operand and store `value` there. void StoreResult(ExecContext& ctx, uint32_t* pos, const Object& value); // The current value behind a target (for Increment and friends). Object ReadTarget(ExecContext& ctx, const Target& target); // Run a method node. Caller holds m_evalLock. bool InvokeMethodNode(int32_t node, const Object* args, int argCount, Object& result, int depth, int exprDepth); // Run a method in a context already acquired and holding its // arguments; releases the context. bool RunMethodIn(ExecContext* ctx, int32_t node, int depth, int exprDepth, Object& result); // The value of a node as an operand: fields read, methods run // without arguments, non-data objects become References. bool NodeValue(int32_t node, Object& result, int depth, int exprDepth); // Log an opcode we cannot decode and stop the method: carrying on // past it would execute operand bytes as code. bool Abort(ExecContext& ctx, uint32_t pos, const char* why); // Native methods (\_OSI). bool RunNativeMethod(uint8_t which, const Object* args, int argCount, Object& result); // Install the OS-defined objects (\_OSI, \_REV, \_OS_, \_GL_). void InstallPredefined(); // Device identification (for PCI_Config regions). bool IntegerChild(int32_t device, const char* seg, uint64_t fallback, uint64_t& value, int depth); bool IsPciRootBridge(int32_t device, int depth); bool ResolvePciAddress(int32_t node, uint8_t& bus, uint8_t& dev, uint8_t& fn, int depth); // ============================================================================ // Field I/O // ============================================================================ // Access `bits` (little-endian, bitLength long) of a field unit. bool AccessField(int32_t fieldNode, bool write, uint8_t* bits); bool ReadFieldObject(int32_t fieldNode, Object& value); bool WriteFieldObject(int32_t fieldNode, const Object& value); // One aligned access unit of a field's backing store. bool FieldUnitIo(const Object& field, uint64_t byteOffset, uint32_t width, bool write, uint64_t& value); // Region access at byte `offset` into the region, `width` bytes. bool ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t& value); bool WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t value); bool RegionIo(int32_t regionNode, uint64_t offset, uint32_t width, bool write, uint64_t& value); bool ReadBufferField(const Object& field, Object& value); bool WriteBufferField(const Object& field, const Object& value); // ============================================================================ // State // ============================================================================ // ExecContext is ~4.4 KB (sixteen Objects, each carrying a // 256-byte buffer inline). One per nested method call on a 16 KiB // kernel stack overflows after three levels, so they come from a // pool instead. The pool is only safe because evaluation is // serialized -- see m_evalLock. // Stack-exhaustion check; `frame` is the address of a local in // the calling frame. bool HaveStackHeadroom(const void* frame) const; // One-shot warning when the nesting backstop trips. static void ReportDepthLimit(); ExecContext* AcquireContext(); void ReleaseContext(); // Run `body` as a top-level evaluation: serialized, with the stack // budget anchored here. template bool TopLevel(Fn body); Namespace m_ns; bool m_initialized; bool m_predefined; int32_t m_dsdtNodeCount; ExecContext* m_ctxPool; // MaxCallDepth + 1 entries, heap int m_ctxInUse; int m_evalNesting; // top-level evaluations in progress uint64_t m_stackFloor; // stack address the interpreter stops at int m_traceBudget; // opcodes left to log, 0 = off ExecContext* m_loadCtx; // table-load operand context uint64_t m_delayedUs; // total Sleep/Stall time requested int m_methodScan; // >0 while scanning a method body at load kcp::Mutex m_evalLock; }; // ============================================================================ // Global interpreter instance // ============================================================================ Interpreter& GetInterpreter(); }; };