3799 lines
154 KiB
C++
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;
|
|
}
|
|
|
|
};
|
|
};
|