feat: expanded ACPI support, initial support for S3 sleep

This commit is contained in:
2026-03-15 00:55:19 +01:00
parent 39b0424085
commit 4c7efa3203
39 changed files with 4403 additions and 59 deletions
File diff suppressed because it is too large Load Diff
+200
View File
@@ -0,0 +1,200 @@
/*
* AmlInterpreter.hpp
* AML bytecode interpreter — parses DSDT/SSDT into the ACPI namespace
* and evaluates methods, fields, and device status
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "AmlNamespace.hpp"
#include <cstdint>
namespace Hal {
namespace AML {
// ── Extended AML Opcodes ────────────────────────────────────────
// Single-byte opcodes
static constexpr uint8_t ZeroOp = 0x00;
static constexpr uint8_t OneOp = 0x01;
static constexpr uint8_t AliasOp = 0x06;
static constexpr uint8_t NameOp = 0x08;
static constexpr uint8_t BytePrefix = 0x0A;
static constexpr uint8_t WordPrefix = 0x0B;
static constexpr uint8_t DWordPrefix = 0x0C;
static constexpr uint8_t StringPrefix = 0x0D;
static constexpr uint8_t QWordPrefix = 0x0E;
static constexpr uint8_t ScopeOp = 0x10;
static constexpr uint8_t BufferOp = 0x11;
static constexpr uint8_t PackageOp = 0x12;
static constexpr uint8_t VarPackageOp = 0x13;
static constexpr uint8_t MethodOp = 0x14;
static constexpr uint8_t DualNamePrefix = 0x2E;
static constexpr uint8_t MultiNamePrefix = 0x2F;
static constexpr uint8_t LocalPrefix = 0x60; // Local0..Local7 = 0x60..0x67
static constexpr uint8_t ArgPrefix = 0x68; // Arg0..Arg6 = 0x68..0x6E
static constexpr uint8_t StoreOp = 0x70;
static constexpr uint8_t AddOp = 0x72;
static constexpr uint8_t SubtractOp = 0x74;
static constexpr uint8_t MultiplyOp = 0x77;
static constexpr uint8_t ShiftLeftOp = 0x79;
static constexpr uint8_t ShiftRightOp = 0x7A;
static constexpr uint8_t AndOp = 0x7B;
static constexpr uint8_t NandOp = 0x7C;
static constexpr uint8_t OrOp = 0x7D;
static constexpr uint8_t NorOp = 0x7E;
static constexpr uint8_t XorOp = 0x7F;
static constexpr uint8_t NotOp = 0x80;
static constexpr uint8_t DerefOfOp = 0x83;
static constexpr uint8_t SizeOfOp = 0x87;
static constexpr uint8_t IndexOp = 0x88;
static constexpr uint8_t CreateDWordFieldOp = 0x8A;
static constexpr uint8_t CreateWordFieldOp = 0x8B;
static constexpr uint8_t CreateByteFieldOp = 0x8C;
static constexpr uint8_t CreateBitFieldOp = 0x8D;
static constexpr uint8_t OnesOp = 0xFF;
static constexpr uint8_t ReturnOp = 0xA4;
static constexpr uint8_t BreakOp = 0xA5;
static constexpr uint8_t IfOp = 0xA0;
static constexpr uint8_t ElseOp = 0xA1;
static constexpr uint8_t WhileOp = 0xA2;
static constexpr uint8_t NoopOp = 0xA3;
static constexpr uint8_t ConcatOp = 0x73;
static constexpr uint8_t ToIntegerOp = 0x99;
static constexpr uint8_t ToBufferOp = 0x96;
static constexpr uint8_t RevisionOp = 0x30; // not a real AML op, used internally
// ExtOp prefix (0x5B) followed by second byte
static constexpr uint8_t ExtOpPrefix = 0x5B;
static constexpr uint8_t MutexOp = 0x01; // after ExtOpPrefix
static constexpr uint8_t EventOp = 0x02;
static constexpr uint8_t OpRegionOp = 0x80;
static constexpr uint8_t FieldOp = 0x81;
static constexpr uint8_t DeviceOp = 0x82;
static constexpr uint8_t ProcessorOp = 0x83;
static constexpr uint8_t PowerResOp = 0x84;
static constexpr uint8_t ThermalZoneOp = 0x85;
static constexpr uint8_t IndexFieldOp = 0x86;
static constexpr uint8_t BankFieldOp = 0x87;
static constexpr uint8_t AcquireOp = 0x23;
static constexpr uint8_t ReleaseOp = 0x27;
static constexpr uint8_t SleepOp = 0x22;
static constexpr uint8_t StallOp = 0x21;
static constexpr uint8_t LNotOp = 0x92; // single-byte, actually
static constexpr uint8_t LEqualOp = 0x93; // single-byte
static constexpr uint8_t LGreaterOp = 0x94; // single-byte
static constexpr uint8_t LLessOp = 0x95; // single-byte
static constexpr uint8_t LAndOp = 0x90; // single-byte
static constexpr uint8_t LOrOp = 0x91; // single-byte
static constexpr uint8_t IncrementOp = 0x75;
static constexpr uint8_t DecrementOp = 0x76;
static constexpr uint8_t DivideOp = 0x78;
static constexpr uint8_t ModOp = 0x85;
static constexpr uint8_t ConcatResOp = 0x84;
static constexpr uint8_t ToHexStringOp = 0x98;
static constexpr uint8_t ToDecimalStringOp = 0x97;
// ── Interpreter Configuration ───────────────────────────────────
static constexpr int MaxCallDepth = 16;
static constexpr int MaxLoopIterations = 1024;
// ── Interpreter ─────────────────────────────────────────────────
class Interpreter {
public:
Interpreter();
// Parse a DSDT or SSDT table into the namespace.
// tableData points to the CommonSDTHeader (HHDM-mapped).
bool LoadTable(void* tableData);
// Evaluate a named object, returning its value.
// For methods, executes them with no arguments.
bool EvaluateObject(const char* path, Object& result);
// Evaluate a method with arguments.
bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result);
// Read a field value. Returns integer.
bool ReadField(int32_t nodeIndex, uint64_t& value);
// Write a field value.
bool WriteField(int32_t nodeIndex, uint64_t value);
// Get the namespace for direct queries.
Namespace& GetNamespace() { return m_ns; }
const Namespace& GetNamespace() const { return m_ns; }
// Check if the interpreter has been initialized
bool IsInitialized() const { return m_initialized; }
private:
// ── Parsing (table load) ────────────────────────────────────
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
int32_t scopeNode);
bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode);
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
int32_t scopeNode);
// ── Name resolution ─────────────────────────────────────────
// Read a NameString from AML and produce an absolute path.
// Advances *pos past the name.
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
char* outPath, int maxLen);
// Read a single 4-char NameSeg from AML. Advances *pos.
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
// ── Value decoding ──────────────────────────────────────────
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
// ── Method execution ────────────────────────────────────────
struct ExecContext {
const uint8_t* Aml;
uint32_t AmlBase; // start of the block within the table
uint32_t AmlLength;
int32_t ScopeNode;
Object Locals[MaxMethodLocals];
Object Args[MaxMethodArgs];
Object ReturnValue;
bool Returned;
bool Broken;
int Depth;
};
bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset);
bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset);
// Evaluate a term (expression that produces a value) within an execution context.
bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
// Evaluate a "SuperName" target for Store operations.
// Returns the node index for named targets, or handles locals/args.
// If isLocal/isArg is set, localIdx/argIdx contains the index.
bool EvalTarget(ExecContext& ctx, uint32_t* pos,
int32_t& nodeIndex, bool& isLocal, int& localIdx,
bool& isArg, int& argIdx);
// Store a value to a target (node, local, or arg).
void StoreToTarget(ExecContext& ctx, const Object& value,
int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx);
// ── Field I/O ───────────────────────────────────────────────
bool ReadRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t& value);
bool WriteRegion(RegionSpace space, uint64_t address, uint32_t bitWidth, uint64_t value);
// ── State ───────────────────────────────────────────────────
Namespace m_ns;
const uint8_t* m_dsdt;
uint32_t m_dsdtLength;
bool m_initialized;
};
// ── Global interpreter instance ─────────────────────────────────
Interpreter& GetInterpreter();
};
};
+204
View File
@@ -0,0 +1,204 @@
/*
* AmlNamespace.cpp
* ACPI namespace tree implementation
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlNamespace.hpp"
#include <Libraries/Memory.hpp>
namespace Hal {
namespace AML {
Namespace::Namespace() : m_nodeCount(0) {
for (int i = 0; i < MaxNamespaceNodes; i++)
m_nodes[i].Clear();
// Create root node "\"
int32_t root = AllocNode();
m_nodes[root].Name[0] = '\\';
m_nodes[root].Name[1] = '\0';
m_nodes[root].ParentIndex = -1;
}
int32_t Namespace::AllocNode() {
if (m_nodeCount >= MaxNamespaceNodes)
return -1;
int32_t idx = m_nodeCount++;
m_nodes[idx].Clear();
return idx;
}
bool Namespace::SegmentEqual(const char* a, const char* b) {
for (int i = 0; i < MaxNameSegLen; i++) {
char ca = a[i] ? a[i] : '_';
char cb = b[i] ? b[i] : '_';
if (ca != cb) return false;
}
return true;
}
void Namespace::PadSegment(const char* src, char* dst) {
int i = 0;
while (i < MaxNameSegLen && src[i] != '\0') {
dst[i] = src[i];
i++;
}
while (i < MaxNameSegLen) {
dst[i] = '_';
i++;
}
dst[MaxNameSegLen] = '\0';
}
int Namespace::ParsePath(const char* path, char segments[][MaxNameSegLen + 1], int maxSegments) {
if (!path || !*path) return 0;
const char* p = path;
// Skip leading backslash (root prefix)
if (*p == '\\') p++;
int count = 0;
while (*p && count < maxSegments) {
// Skip dots (parent prefix / dual/multi name prefix separator)
if (*p == '.') { p++; continue; }
// Skip caret (parent prefix) — we don't handle relative paths here
if (*p == '^') { p++; continue; }
// Read up to 4 characters for a name segment
int i = 0;
while (i < MaxNameSegLen && *p && *p != '.' && *p != '\\') {
segments[count][i] = *p;
i++;
p++;
}
// Pad with underscores
while (i < MaxNameSegLen) {
segments[count][i] = '_';
i++;
}
segments[count][MaxNameSegLen] = '\0';
count++;
}
return count;
}
int32_t Namespace::FindChildByName(int32_t parentIndex, const char* seg) const {
auto* parent = GetNode(parentIndex);
if (!parent) return -1;
for (int32_t i = 0; i < parent->ChildCount; i++) {
int32_t ci = parent->ChildIndices[i];
if (ci < 0 || ci >= m_nodeCount) continue;
if (SegmentEqual(m_nodes[ci].Name, seg))
return ci;
}
return -1;
}
int32_t Namespace::CreateNode(const char* absolutePath) {
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = ParsePath(absolutePath, segments, MaxPathDepth);
int32_t current = 0; // root
for (int i = 0; i < segCount; i++) {
int32_t child = FindChildByName(current, segments[i]);
if (child < 0) {
// Create the node
child = AllocNode();
if (child < 0) return -1;
memcpy(m_nodes[child].Name, segments[i], MaxNameSegLen + 1);
m_nodes[child].ParentIndex = current;
// Add to parent's children
auto* parent = &m_nodes[current];
if (parent->ChildCount < MaxChildren) {
parent->ChildIndices[parent->ChildCount++] = child;
} else {
return -1; // too many children
}
}
current = child;
}
return current;
}
int32_t Namespace::FindNode(const char* absolutePath) const {
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = ParsePath(absolutePath, segments, MaxPathDepth);
int32_t current = 0; // root
for (int i = 0; i < segCount; i++) {
current = FindChildByName(current, segments[i]);
if (current < 0) return -1;
}
return current;
}
int32_t Namespace::ResolveName(const char* name, int32_t scopeNodeIndex) const {
// If it starts with '\', it's absolute
if (name[0] == '\\')
return FindNode(name);
// Try to find relative to the current scope, walking up
char padded[MaxNameSegLen + 1];
PadSegment(name, padded);
int32_t scope = scopeNodeIndex;
while (scope >= 0) {
int32_t found = FindChildByName(scope, padded);
if (found >= 0) return found;
scope = m_nodes[scope].ParentIndex;
}
return -1;
}
NamespaceNode* Namespace::GetNode(int32_t index) {
if (index < 0 || index >= m_nodeCount) return nullptr;
return &m_nodes[index];
}
const NamespaceNode* Namespace::GetNode(int32_t index) const {
if (index < 0 || index >= m_nodeCount) return nullptr;
return &m_nodes[index];
}
char* Namespace::GetNodePath(int32_t index, char* outBuf, int maxLen) const {
if (!outBuf || maxLen < 2) return outBuf;
// Build path by walking up to root
// Collect segments in reverse
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = 0;
int32_t cur = index;
while (cur > 0 && segCount < MaxPathDepth) { // stop at root (index 0)
auto* node = GetNode(cur);
if (!node) break;
memcpy(segments[segCount], node->Name, MaxNameSegLen + 1);
segCount++;
cur = node->ParentIndex;
}
// Write root prefix
int pos = 0;
outBuf[pos++] = '\\';
// Write segments in reverse order
for (int i = segCount - 1; i >= 0 && pos < maxLen - 1; i--) {
if (i < segCount - 1 && pos < maxLen - 1)
outBuf[pos++] = '.';
for (int j = 0; j < MaxNameSegLen && pos < maxLen - 1; j++)
outBuf[pos++] = segments[i][j];
}
outBuf[pos] = '\0';
return outBuf;
}
};
};
+199
View File
@@ -0,0 +1,199 @@
/*
* AmlNamespace.hpp
* AML object types and ACPI namespace tree
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Libraries/Memory.hpp>
namespace Hal {
namespace AML {
// ── AML Object Types ────────────────────────────────────────────
enum class ObjectType : uint8_t {
None = 0,
Integer,
String,
Buffer,
Package,
Device,
Method,
OperationRegion,
Field,
Mutex,
Processor,
ThermalZone,
PowerResource,
BufferField,
};
// ── Region address spaces (OperationRegion) ─────────────────────
enum class RegionSpace : uint8_t {
SystemMemory = 0x00,
SystemIO = 0x01,
PciConfig = 0x02,
EmbeddedControl = 0x03,
SMBus = 0x04,
CMOS = 0x05,
PciBarTarget = 0x06,
};
// ── Constants ───────────────────────────────────────────────────
static constexpr int MaxNameSegLen = 4;
static constexpr int MaxPathDepth = 16;
static constexpr int MaxChildren = 32;
static constexpr int MaxStringLen = 64;
static constexpr int MaxBufferLen = 256;
static constexpr int MaxPackageElements = 16;
static constexpr int MaxMethodArgs = 7;
static constexpr int MaxMethodLocals = 8;
static constexpr int MaxNamespaceNodes = 256;
// ── AML Object ──────────────────────────────────────────────────
// Tagged union representing any AML value. Kept small for kernel use.
struct Object {
ObjectType Type = ObjectType::None;
union {
uint64_t Integer;
struct {
char Data[MaxStringLen];
uint16_t Length;
} String;
struct {
uint8_t Data[MaxBufferLen];
uint32_t Length;
} Buffer;
struct {
uint8_t ArgCount; // bits 0-2 of method flags
bool Serialized; // bit 3
uint32_t AmlOffset; // offset into DSDT AML where the method body starts
uint32_t AmlLength; // length of the method body
} Method;
struct {
RegionSpace Space;
uint64_t Offset;
uint64_t Length;
} Region;
struct {
uint32_t RegionNodeIndex; // index of the parent OperationRegion node
uint32_t BitOffset;
uint32_t BitLength;
uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer
} Field;
struct {
uint8_t ProcId;
uint32_t PblkAddr;
uint8_t PblkLen;
} Processor;
};
Object() : Type(ObjectType::None), Integer(0) {}
};
// ── Namespace Node ──────────────────────────────────────────────
// Each node has a 4-char name segment and an associated object.
struct NamespaceNode {
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
Object Obj;
int32_t ParentIndex; // -1 for root
int32_t ChildIndices[MaxChildren];
int32_t ChildCount;
void Clear() {
Name[0] = 0;
Obj = Object{};
ParentIndex = -1;
ChildCount = 0;
for (int i = 0; i < MaxChildren; i++)
ChildIndices[i] = -1;
}
};
// ── Namespace ───────────────────────────────────────────────────
// Flat array of nodes forming a tree via parent/child indices.
class Namespace {
public:
Namespace();
// Create or find a node at the given absolute path (e.g. "\\_SB_.PCI0").
// Returns the node index, or -1 on failure.
int32_t CreateNode(const char* absolutePath);
// Find a node by absolute path. Returns index or -1.
int32_t FindNode(const char* absolutePath) const;
// Find a node relative to a scope. Tries:
// 1. scopePath + name
// 2. Walk up parent scopes
// 3. Root scope
int32_t ResolveName(const char* name, int32_t scopeNodeIndex) const;
// Get a node by index.
NamespaceNode* GetNode(int32_t index);
const NamespaceNode* GetNode(int32_t index) const;
// Get the root node index (always 0).
int32_t RootIndex() const { return 0; }
// Build the absolute path of a node into outBuf. Returns outBuf.
char* GetNodePath(int32_t index, char* outBuf, int maxLen) const;
// Get the number of nodes in the namespace.
int32_t NodeCount() const { return m_nodeCount; }
// Iterate children of a node matching a given object type.
// callback returns true to continue, false to stop.
// Returns the index of the node that stopped iteration, or -1.
template<typename Fn>
int32_t ForEachChild(int32_t parentIndex, ObjectType type, Fn callback) const {
auto* parent = GetNode(parentIndex);
if (!parent) return -1;
for (int32_t i = 0; i < parent->ChildCount; i++) {
int32_t ci = parent->ChildIndices[i];
auto* child = GetNode(ci);
if (!child) continue;
if (type != ObjectType::None && child->Obj.Type != type) continue;
if (!callback(ci, child)) return ci;
}
return -1;
}
// Recursively find all descendants of a given type.
template<typename Fn>
void WalkDescendants(int32_t nodeIndex, ObjectType type, Fn callback) const {
auto* node = GetNode(nodeIndex);
if (!node) return;
for (int32_t i = 0; i < node->ChildCount; i++) {
int32_t ci = node->ChildIndices[i];
auto* child = GetNode(ci);
if (!child) continue;
if (type == ObjectType::None || child->Obj.Type == type)
callback(ci, child);
WalkDescendants(ci, type, callback);
}
}
private:
int32_t AllocNode();
int32_t FindChildByName(int32_t parentIndex, const char* seg) const;
// Parse an absolute path into segments. Returns number of segments.
static int ParsePath(const char* path, char segments[][MaxNameSegLen + 1], int maxSegments);
static bool SegmentEqual(const char* a, const char* b);
static void PadSegment(const char* src, char* dst); // pad to 4 chars with '_'
NamespaceNode m_nodes[MaxNamespaceNodes];
int32_t m_nodeCount;
};
};
};
+103 -34
View File
@@ -1,10 +1,11 @@
/*
* AmlParser.cpp
* Primitive AML bytecode parser for extracting ACPI sleep state values
* AML bytecode parser — S5 extraction (brute-force) and interpreter init
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlParser.hpp"
#include "AmlInterpreter.hpp"
#include <ACPI/ACPI.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -14,19 +15,28 @@ using namespace Kt;
namespace Hal {
namespace AML {
// Decode a PkgLength field and return its value.
// Advances *pos past the PkgLength bytes.
// ── Legacy S5 extraction (brute-force scan) ─────────────────────
// Kept for fast S5 extraction during early boot before the full
// interpreter is loaded.
static constexpr uint8_t NameOp_ = 0x08;
static constexpr uint8_t PackageOp_ = 0x12;
static constexpr uint8_t ZeroOp_ = 0x00;
static constexpr uint8_t OneOp_ = 0x01;
static constexpr uint8_t OnesOp_ = 0xFF;
static constexpr uint8_t BytePrefix_ = 0x0A;
static constexpr uint8_t WordPrefix_ = 0x0B;
static constexpr uint8_t DWordPrefix_= 0x0C;
static uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos) {
uint8_t lead = aml[*pos];
uint32_t byteCount = (lead >> 6) & 0x03;
if (byteCount == 0) {
// Single byte encoding: bits 0-5 are the length
(*pos)++;
return lead & 0x3F;
}
// Multi-byte: lead bits 0-3 are low nibble, followed by byteCount bytes
uint32_t length = lead & 0x0F;
(*pos)++;
@@ -38,35 +48,32 @@ namespace Hal {
return length;
}
// Decode an AML integer at position *pos.
// Handles ZeroOp, OneOp, OnesOp, BytePrefix, WordPrefix, DWordPrefix.
// Returns the decoded value and advances *pos.
static uint32_t DecodeInteger(const uint8_t* aml, uint32_t* pos) {
static uint32_t DecodeIntegerLegacy(const uint8_t* aml, uint32_t* pos) {
uint8_t op = aml[*pos];
switch (op) {
case ZeroOp:
case ZeroOp_:
(*pos)++;
return 0;
case OneOp:
case OneOp_:
(*pos)++;
return 1;
case OnesOp:
case OnesOp_:
(*pos)++;
return 0xFFFFFFFF;
case BytePrefix: {
case BytePrefix_: {
(*pos)++;
uint8_t val = aml[*pos];
(*pos)++;
return val;
}
case WordPrefix: {
case WordPrefix_: {
(*pos)++;
uint16_t val = aml[*pos] | ((uint16_t)aml[*pos + 1] << 8);
*pos += 2;
return val;
}
case DWordPrefix: {
case DWordPrefix_: {
(*pos)++;
uint32_t val = aml[*pos]
| ((uint32_t)aml[*pos + 1] << 8)
@@ -76,7 +83,6 @@ namespace Hal {
return val;
}
default:
// Unknown encoding — treat as zero and skip
(*pos)++;
return 0;
}
@@ -93,23 +99,17 @@ namespace Hal {
return result;
}
// The AML bytecode starts right after the CommonSDTHeader
const uint8_t* aml = (const uint8_t*)dsdtData;
uint32_t amlLength = header->Length;
uint32_t dataStart = sizeof(ACPI::CommonSDTHeader);
// Scan for the \_S5_ name in the AML stream.
// We look for the 4-byte sequence '_S5_' preceded by a NameOp (0x08)
// or preceded by a scope path like '\' (0x5C).
for (uint32_t i = dataStart; i + 4 < amlLength; i++) {
if (aml[i] == '_' && aml[i+1] == 'S' && aml[i+2] == '5' && aml[i+3] == '_') {
// Verify a valid AML context: either NameOp before it,
// or '\' + NameOp pattern, or just the name in a scope
bool validContext = false;
if (i >= 1 && aml[i-1] == NameOp) {
if (i >= 1 && aml[i-1] == NameOp_) {
validContext = true;
} else if (i >= 2 && aml[i-2] == NameOp && aml[i-1] == '\\') {
} else if (i >= 2 && aml[i-2] == NameOp_ && aml[i-1] == '\\') {
validContext = true;
}
@@ -118,21 +118,16 @@ namespace Hal {
KernelLogStream(OK, "AML") << "Found \\_S5_ object at offset " << base::hex << (uint64_t)i;
// Move past the name
uint32_t pos = i + 4;
// Expect PackageOp
if (pos >= amlLength || aml[pos] != PackageOp) {
if (pos >= amlLength || aml[pos] != PackageOp_) {
KernelLogStream(ERROR, "AML") << "Expected PackageOp after \\_S5_, got " << base::hex << (uint64_t)aml[pos];
continue;
}
pos++;
// Decode package length (we don't actually need the value,
// but must advance past it)
DecodePkgLength(aml, &pos);
// Number of elements in the package
if (pos >= amlLength) continue;
uint8_t numElements = aml[pos];
pos++;
@@ -142,12 +137,10 @@ namespace Hal {
continue;
}
// First element: SLP_TYPa
result.SLP_TYPa = (uint16_t)DecodeInteger(aml, &pos);
result.SLP_TYPa = (uint16_t)DecodeIntegerLegacy(aml, &pos);
// Second element: SLP_TYPb (if present)
if (numElements >= 2 && pos < amlLength) {
result.SLP_TYPb = (uint16_t)DecodeInteger(aml, &pos);
result.SLP_TYPb = (uint16_t)DecodeIntegerLegacy(aml, &pos);
} else {
result.SLP_TYPb = 0;
}
@@ -165,5 +158,81 @@ namespace Hal {
return result;
}
// ── Generalized brute-force sleep state scanner ──────────────────
SleepObject FindSleepState(void* dsdtData, int state) {
SleepObject result{};
result.Valid = false;
if (state < 0 || state > 5) return result;
auto* header = (ACPI::CommonSDTHeader*)dsdtData;
if (!ACPI::TestChecksum(header)) {
KernelLogStream(ERROR, "AML") << "DSDT checksum failed";
return result;
}
// Build the 4-char name we're looking for: _S0_ through _S5_
char target[4] = { '_', 'S', (char)('0' + state), '_' };
const uint8_t* aml = (const uint8_t*)dsdtData;
uint32_t amlLength = header->Length;
uint32_t dataStart = sizeof(ACPI::CommonSDTHeader);
for (uint32_t i = dataStart; i + 4 < amlLength; i++) {
if (aml[i] == target[0] && aml[i+1] == target[1] &&
aml[i+2] == target[2] && aml[i+3] == target[3]) {
bool validContext = false;
if (i >= 1 && aml[i-1] == NameOp_)
validContext = true;
else if (i >= 2 && aml[i-2] == NameOp_ && aml[i-1] == '\\')
validContext = true;
if (!validContext) continue;
uint32_t pos = i + 4;
if (pos >= amlLength || aml[pos] != PackageOp_) continue;
pos++;
DecodePkgLength(aml, &pos);
if (pos >= amlLength) continue;
uint8_t numElements = aml[pos];
pos++;
if (numElements < 1) continue;
result.SLP_TYPa = (uint16_t)DecodeIntegerLegacy(aml, &pos);
if (numElements >= 2 && pos < amlLength)
result.SLP_TYPb = (uint16_t)DecodeIntegerLegacy(aml, &pos);
else
result.SLP_TYPb = 0;
result.Valid = true;
KernelLogStream(OK, "AML") << "\\_S" << base::dec << (uint64_t)state
<< "_ found: SLP_TYPa=" << base::hex << (uint64_t)result.SLP_TYPa
<< " SLP_TYPb=" << base::hex << (uint64_t)result.SLP_TYPb;
return result;
}
}
KernelLogStream(INFO, "AML") << "\\_S" << base::dec << (uint64_t)state
<< "_ not found in DSDT";
return result;
}
// ── Full interpreter initialization ─────────────────────────────
void InitializeInterpreter(void* dsdtData) {
auto& interp = GetInterpreter();
if (!interp.LoadTable(dsdtData)) {
KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter";
}
}
};
};
+16 -14
View File
@@ -1,6 +1,6 @@
/*
* AmlParser.hpp
* Primitive AML bytecode parser for extracting ACPI sleep state values
* AML bytecode parser — S5 extraction and interpreter initialization
* Copyright (c) 2026 Daniel Hammer
*/
@@ -10,26 +10,28 @@
namespace Hal {
namespace AML {
// AML opcodes used during \_S5_ parsing
static constexpr uint8_t NameOp = 0x08;
static constexpr uint8_t PackageOp = 0x12;
static constexpr uint8_t ZeroOp = 0x00;
static constexpr uint8_t OneOp = 0x01;
static constexpr uint8_t OnesOp = 0xFF;
static constexpr uint8_t BytePrefix = 0x0A;
static constexpr uint8_t WordPrefix = 0x0B;
static constexpr uint8_t DWordPrefix = 0x0C;
struct S5Object {
struct SleepObject {
uint16_t SLP_TYPa;
uint16_t SLP_TYPb;
bool Valid;
};
// Legacy compat alias
using S5Object = SleepObject;
// Parse a DSDT (or SSDT) AML block to find the \_S5_ object.
// dsdtData points to the CommonSDTHeader of the DSDT (HHDM-mapped).
// Returns the parsed S5 values on success.
S5Object FindS5(void* dsdtData);
// Parse a DSDT to find any \_Sx_ object (x = 0-5) via brute-force scan.
// Works on any DSDT regardless of complexity — does not require the
// interpreter or namespace.
SleepObject FindSleepState(void* dsdtData, int state);
// Initialize the AML interpreter with the DSDT.
// This loads the full table into the namespace and enables
// method evaluation, device enumeration, and field access.
// Should be called during boot after ACPI table discovery.
void InitializeInterpreter(void* dsdtData);
};
};
+222
View File
@@ -0,0 +1,222 @@
/*
* AmlResource.cpp
* ACPI resource descriptor parsing
* Copyright (c) 2026 Daniel Hammer
*/
#include "AmlResource.hpp"
namespace Hal {
namespace AML {
// ── Small Resource Tags (bits 6:3 of the tag byte) ──────────────
static constexpr uint8_t SmallIrqTag = 0x04; // IRQ descriptor
static constexpr uint8_t SmallDmaTag = 0x05; // DMA descriptor
static constexpr uint8_t SmallIoPortTag = 0x08; // I/O port descriptor
static constexpr uint8_t SmallFixedIoTag = 0x09; // Fixed I/O port descriptor
static constexpr uint8_t SmallEndTag = 0x0F; // End tag
// ── Large Resource Tags (byte following the large tag prefix) ───
static constexpr uint8_t LargeMemory24Tag = 0x01;
static constexpr uint8_t LargeVendorTag = 0x04;
static constexpr uint8_t LargeMemory32Tag = 0x05;
static constexpr uint8_t LargeMem32FixedTag = 0x06;
static constexpr uint8_t LargeDWordAddrTag = 0x07;
static constexpr uint8_t LargeWordAddrTag = 0x08;
static constexpr uint8_t LargeExtIrqTag = 0x09;
static constexpr uint8_t LargeQWordAddrTag = 0x0A;
static constexpr uint8_t LargeGpioTag = 0x0C;
static uint16_t Read16(const uint8_t* p) {
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static uint32_t Read32(const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static uint64_t Read64(const uint8_t* p) {
uint64_t val = 0;
for (int i = 0; i < 8; i++)
val |= (uint64_t)p[i] << (i * 8);
return val;
}
// Find the lowest set bit in a mask. Returns the bit number, or -1.
static int FirstSetBit(uint16_t mask) {
for (int i = 0; i < 16; i++) {
if (mask & (1 << i)) return i;
}
return -1;
}
bool ParseResourceTemplate(const uint8_t* data, uint32_t length, ResourceList& result) {
result.Count = 0;
uint32_t pos = 0;
while (pos < length && result.Count < MaxResources) {
uint8_t tag = data[pos];
// End tag
if ((tag & 0x80) == 0 && ((tag >> 3) & 0x0F) == SmallEndTag)
break;
if (tag & 0x80) {
// ── Large resource descriptor ───────────────────────
uint8_t largeType = tag & 0x7F;
if (pos + 3 > length) break;
uint16_t resLen = Read16(&data[pos + 1]);
uint32_t dataStart = pos + 3;
uint32_t dataEnd = dataStart + resLen;
if (dataEnd > length) break;
auto& res = result.Resources[result.Count];
switch (largeType) {
case LargeExtIrqTag: {
if (resLen < 2) break;
res.Type = ResourceType::ExtendedIrq;
uint8_t flags = data[dataStart];
res.ExtendedIrq.Flags = flags;
res.ExtendedIrq.Shareable = (flags >> 3) & 1;
uint8_t irqCount = data[dataStart + 1];
if (irqCount > 0 && resLen >= 6) {
res.ExtendedIrq.Interrupt = Read32(&data[dataStart + 2]);
} else {
res.ExtendedIrq.Interrupt = 0;
}
result.Count++;
break;
}
case LargeMemory32Tag: {
if (resLen < 17) break;
res.Type = ResourceType::Memory32;
res.Memory32.ReadWrite = data[dataStart] & 1;
res.Memory32.Base = Read32(&data[dataStart + 1]);
// Max = data[dataStart + 5..8]
// Alignment = data[dataStart + 9..12]
res.Memory32.Length = Read32(&data[dataStart + 13]);
result.Count++;
break;
}
case LargeMem32FixedTag: {
if (resLen < 9) break;
res.Type = ResourceType::Memory32;
res.Memory32.ReadWrite = data[dataStart] & 1;
res.Memory32.Base = Read32(&data[dataStart + 1]);
res.Memory32.Length = Read32(&data[dataStart + 5]);
result.Count++;
break;
}
case LargeDWordAddrTag: {
if (resLen < 23) break;
res.Type = ResourceType::DWordAddress;
// ResourceType at dataStart+0, GenFlags at +1, TypeFlags at +2
res.AddressSpace.GranularityMin = Read32(&data[dataStart + 3]);
res.AddressSpace.GranularityMax = Read32(&data[dataStart + 7]);
// Min at +7, Max at +11, Translation at +15, Length at +19
res.AddressSpace.Base = Read32(&data[dataStart + 7]);
res.AddressSpace.Length = Read32(&data[dataStart + 19]);
result.Count++;
break;
}
case LargeQWordAddrTag: {
if (resLen < 43) break;
res.Type = ResourceType::QWordAddress;
res.AddressSpace.GranularityMin = Read64(&data[dataStart + 3]);
res.AddressSpace.GranularityMax = Read64(&data[dataStart + 11]);
res.AddressSpace.Base = Read64(&data[dataStart + 11]);
res.AddressSpace.Length = Read64(&data[dataStart + 35]);
result.Count++;
break;
}
case LargeWordAddrTag: {
if (resLen < 13) break;
res.Type = ResourceType::WordAddress;
res.AddressSpace.GranularityMin = Read16(&data[dataStart + 3]);
res.AddressSpace.GranularityMax = Read16(&data[dataStart + 5]);
res.AddressSpace.Base = Read16(&data[dataStart + 5]);
res.AddressSpace.Length = Read16(&data[dataStart + 11]);
result.Count++;
break;
}
default:
// Unknown large descriptor — skip
break;
}
pos = dataEnd;
} else {
// ── Small resource descriptor ───────────────────────
uint8_t smallType = (tag >> 3) & 0x0F;
uint8_t resLen = tag & 0x07;
uint32_t dataStart = pos + 1;
uint32_t dataEnd = dataStart + resLen;
if (dataEnd > length) break;
auto& res = result.Resources[result.Count];
switch (smallType) {
case SmallIrqTag: {
if (resLen < 2) break;
res.Type = ResourceType::Irq;
res.Irq.Mask = Read16(&data[dataStart]);
res.Irq.Flags = (resLen >= 3) ? data[dataStart + 2] : 0;
int irq = FirstSetBit(res.Irq.Mask);
res.Irq.Irq = (irq >= 0) ? (uint8_t)irq : 0;
result.Count++;
break;
}
case SmallDmaTag: {
if (resLen < 2) break;
res.Type = ResourceType::Dma;
res.Dma.Mask = data[dataStart];
res.Dma.Flags = data[dataStart + 1];
int ch = FirstSetBit(res.Dma.Mask);
res.Dma.Channel = (ch >= 0) ? (uint8_t)ch : 0;
result.Count++;
break;
}
case SmallIoPortTag: {
if (resLen < 7) break;
res.Type = ResourceType::IoPort;
res.IoPort.Decode16Bit = data[dataStart] & 1;
res.IoPort.Base = Read16(&data[dataStart + 1]);
// Max = data[dataStart + 3..4]
res.IoPort.Alignment = data[dataStart + 5];
res.IoPort.Length = data[dataStart + 6];
result.Count++;
break;
}
case SmallFixedIoTag: {
if (resLen < 3) break;
res.Type = ResourceType::FixedIoPort;
res.FixedIoPort.Base = Read16(&data[dataStart]);
res.FixedIoPort.Length = data[dataStart + 2];
result.Count++;
break;
}
default:
break;
}
pos = dataEnd;
}
}
return result.Count > 0;
}
};
};
+158
View File
@@ -0,0 +1,158 @@
/*
* AmlResource.hpp
* ACPI resource descriptor parsing (_CRS, _PRS, _SRS buffers)
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Hal {
namespace AML {
// ── Resource Types ──────────────────────────────────────────────
enum class ResourceType : uint8_t {
None = 0,
Irq,
Dma,
IoPort,
FixedIoPort,
Memory16,
Memory32,
Memory32Fixed,
QWordAddress,
DWordAddress,
WordAddress,
ExtendedIrq,
GpioConnection,
};
// ── Single Resource Descriptor ──────────────────────────────────
struct ResourceDescriptor {
ResourceType Type;
union {
struct {
uint16_t Mask; // bitmask of supported IRQs
uint8_t Flags;
uint8_t Irq; // decoded first IRQ number
} Irq;
struct {
uint32_t Interrupt; // GSI number
uint8_t Flags; // edge/level, active high/low
bool Shareable;
} ExtendedIrq;
struct {
uint8_t Mask; // bitmask of supported DMA channels
uint8_t Flags;
uint8_t Channel; // decoded first channel
} Dma;
struct {
uint16_t Base;
uint16_t Length;
uint8_t Alignment;
bool Decode16Bit; // true = 16-bit decode, false = 10-bit
} IoPort;
struct {
uint16_t Base;
uint8_t Length;
} FixedIoPort;
struct {
uint32_t Base;
uint32_t Length;
bool ReadWrite; // true = R/W, false = read-only
} Memory32;
struct {
uint64_t Base;
uint64_t Length;
uint64_t GranularityMin;
uint64_t GranularityMax;
} AddressSpace;
};
ResourceDescriptor() : Type(ResourceType::None) {
// Zero the largest union member
AddressSpace = {};
}
};
// ── Parsed Resource List ────────────────────────────────────────
static constexpr int MaxResources = 16;
struct ResourceList {
ResourceDescriptor Resources[MaxResources];
int Count;
ResourceList() : Count(0) {}
// Find the first resource of a given type. Returns nullptr if not found.
const ResourceDescriptor* FindFirst(ResourceType type) const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == type)
return &Resources[i];
}
return nullptr;
}
// Get the IRQ number from the first IRQ or ExtendedIrq resource.
// Returns -1 if no IRQ found.
int GetIrq() const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == ResourceType::Irq)
return Resources[i].Irq.Irq;
if (Resources[i].Type == ResourceType::ExtendedIrq)
return (int)Resources[i].ExtendedIrq.Interrupt;
}
return -1;
}
// Get the first IO port base and length.
// Returns false if no IO port found.
bool GetIoPort(uint16_t& base, uint16_t& length) const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == ResourceType::IoPort) {
base = Resources[i].IoPort.Base;
length = Resources[i].IoPort.Length;
return true;
}
if (Resources[i].Type == ResourceType::FixedIoPort) {
base = Resources[i].FixedIoPort.Base;
length = Resources[i].FixedIoPort.Length;
return true;
}
}
return false;
}
// Get the first memory base and length.
bool GetMemory(uint64_t& base, uint64_t& length) const {
for (int i = 0; i < Count; i++) {
if (Resources[i].Type == ResourceType::Memory32) {
base = Resources[i].Memory32.Base;
length = Resources[i].Memory32.Length;
return true;
}
if (Resources[i].Type == ResourceType::QWordAddress ||
Resources[i].Type == ResourceType::DWordAddress ||
Resources[i].Type == ResourceType::WordAddress) {
base = Resources[i].AddressSpace.Base;
length = Resources[i].AddressSpace.Length;
return true;
}
}
return false;
}
};
// Parse a resource template buffer (as returned by _CRS evaluation)
// into a structured ResourceList.
bool ParseResourceTemplate(const uint8_t* data, uint32_t length, ResourceList& result);
};
};