356 lines
15 KiB
C++
356 lines
15 KiB
C++
/*
|
|
* 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,
|
|
Event,
|
|
// A reference to a namespace node: what RefOf() yields, and what a
|
|
// name evaluates to when it names something that is not data (a
|
|
// device or power resource listed in a _PRx package, say).
|
|
Reference,
|
|
// A name inside a constant package, kept as written ("PG00",
|
|
// "\_SB.PCI0.PEG0") because its scope is only known to whoever
|
|
// reads the package. Interpreter::ResolveReference() turns it
|
|
// into a node.
|
|
NameRef,
|
|
};
|
|
|
|
// ============================================================================
|
|
|
|
// 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;
|
|
// Children are held in a heap array that grows on demand, starting at
|
|
// this many entries. A fixed ceiling here is not survivable: an EC or
|
|
// GPE scope in a modern laptop DSDT carries well over a hundred named
|
|
// objects, and dropping the overflow loses whole devices (the battery
|
|
// among them) with no way to tell from the outside.
|
|
static constexpr int InitialChildren = 8;
|
|
static constexpr int MaxStringLen = 64;
|
|
static constexpr int MaxBufferLen = 256;
|
|
static constexpr int MaxPackageElements = 64;
|
|
static constexpr int MaxMethodArgs = 7;
|
|
static constexpr int MaxMethodLocals = 8;
|
|
|
|
// Nodes are allocated dynamically in chunks from the kernel heap.
|
|
// Each chunk holds this many nodes; new chunks are allocated on demand.
|
|
static constexpr int NodesPerChunk = 256;
|
|
static constexpr int MaxChunks = 128; // up to 32768 nodes
|
|
|
|
// ============================================================================
|
|
|
|
// AML Object
|
|
|
|
// ============================================================================
|
|
// Tagged union representing any AML value. Kept small for kernel use.
|
|
struct Object {
|
|
ObjectType Type = ObjectType::None;
|
|
|
|
// Packages keep their raw AML bytes in Buffer (constant elements can
|
|
// be decoded straight from there), but a package whose elements are
|
|
// computed or written through Index() needs real per-element
|
|
// storage. PkgIndex names that storage once it is materialized;
|
|
// -1 means "not materialized yet". It lives outside the union so a
|
|
// package can carry both representations at once.
|
|
int32_t PkgIndex = -1;
|
|
|
|
union {
|
|
uint64_t Integer;
|
|
|
|
struct {
|
|
char Data[MaxStringLen];
|
|
uint16_t Length;
|
|
} String;
|
|
|
|
struct {
|
|
uint8_t Data[MaxBufferLen];
|
|
uint32_t Length;
|
|
} Buffer;
|
|
|
|
// A package's constant form: its element bytes, left in the
|
|
// table they were declared in, and the declared element count. ACPI tables stay
|
|
// mapped for the life of the kernel, so pointing at them is
|
|
// both cheaper and free of the size cap a copy would carry --
|
|
// a GPIO community table runs well past MaxBufferLen.
|
|
struct {
|
|
const uint8_t* Data; // first element
|
|
uint32_t Length; // bytes of element encodings
|
|
uint32_t Count; // NumElements
|
|
} Package;
|
|
|
|
struct {
|
|
uint8_t ArgCount; // bits 0-2 of method flags
|
|
bool Serialized; // bit 3
|
|
uint8_t Native; // NativeMethod; 0 = AML
|
|
const uint8_t* Table; // the table the body lives in
|
|
uint32_t AmlOffset; // offset into Table where the method body starts
|
|
uint32_t AmlLength; // length of the method body
|
|
} Method;
|
|
|
|
struct {
|
|
RegionSpace Space;
|
|
uint64_t Offset;
|
|
uint64_t Length;
|
|
} Region;
|
|
|
|
struct {
|
|
// Field / BankField: the OperationRegion node.
|
|
// IndexField: the index register field.
|
|
uint32_t RegionNodeIndex;
|
|
uint32_t BitOffset;
|
|
uint32_t BitLength;
|
|
uint8_t AccessType; // 0=Any, 1=Byte, 2=Word, 3=DWord, 4=QWord, 5=Buffer
|
|
uint8_t UpdateRule; // 0=Preserve, 1=WriteAsOnes, 2=WriteAsZeros
|
|
uint8_t Kind; // FieldKind
|
|
int32_t DataNodeIndex; // IndexField: the data register field
|
|
int32_t BankNodeIndex; // BankField: the bank select field
|
|
uint64_t BankValue; // BankField: value selecting this bank
|
|
} Field;
|
|
|
|
// CreateXxxField over a buffer. Source points at the object
|
|
// holding the buffer -- a namespace node's, or a local/arg slot
|
|
// of the method that created the field -- so reads and writes
|
|
// go through to it.
|
|
struct {
|
|
Object* Source;
|
|
uint32_t BitOffset;
|
|
uint32_t BitLength;
|
|
} BufferField;
|
|
|
|
struct {
|
|
int32_t Node;
|
|
} Reference;
|
|
|
|
struct {
|
|
uint8_t ProcId;
|
|
uint32_t PblkAddr;
|
|
uint8_t PblkLen;
|
|
} Processor;
|
|
};
|
|
|
|
Object() : Type(ObjectType::None), PkgIndex(-1), Integer(0) {}
|
|
};
|
|
|
|
enum class FieldKind : uint8_t {
|
|
Normal = 0,
|
|
Index = 1,
|
|
Bank = 2,
|
|
};
|
|
|
|
// Methods the OS provides rather than the firmware.
|
|
enum class NativeMethod : uint8_t {
|
|
None = 0,
|
|
Osi = 1, // \_OSI
|
|
};
|
|
|
|
// ============================================================================
|
|
|
|
// Package element store
|
|
|
|
// ============================================================================
|
|
// Element arrays for materialized packages, kept outside Object so the
|
|
// tagged union stays small. Entries are split in two: a persistent
|
|
// region for packages declared with Name() (materialized once and kept
|
|
// for the life of the namespace) and a transient ring for packages
|
|
// built inside a method, which is rewound at each top-level evaluation
|
|
// so repeated calls -- polling _BST, say -- cannot exhaust it.
|
|
namespace PackageStore {
|
|
static constexpr int MaxPersistent = 32;
|
|
static constexpr int MaxTransient = 32;
|
|
|
|
// Allocate an element array of `count` objects. Returns -1 when the
|
|
// store is full or the count is out of range.
|
|
int32_t AllocPersistent(int count);
|
|
int32_t AllocTransient(int count);
|
|
|
|
// Rewind the transient ring. Called once per top-level evaluation.
|
|
void ResetTransient();
|
|
|
|
// True for an entry that outlives the evaluation that made it.
|
|
bool IsPersistent(int32_t index);
|
|
|
|
// Element access. Returns nullptr for an unknown entry or an
|
|
// out-of-range element.
|
|
Object* Element(int32_t index, int element);
|
|
int Count(int32_t index);
|
|
};
|
|
|
|
// ============================================================================
|
|
|
|
// 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; // heap array, ChildCapacity long
|
|
int32_t ChildCount;
|
|
int32_t ChildCapacity;
|
|
|
|
// Only ever called on a freshly allocated (zeroed) node, so there is
|
|
// no child array to release here.
|
|
void Clear() {
|
|
Name[0] = 0;
|
|
Obj = Object{};
|
|
ParentIndex = -1;
|
|
ChildIndices = nullptr;
|
|
ChildCount = 0;
|
|
ChildCapacity = 0;
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
|
|
// Namespace
|
|
|
|
// ============================================================================
|
|
// Dynamically-allocated node pool forming a tree via parent/child indices.
|
|
// Nodes are allocated in chunks from the kernel heap on demand.
|
|
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;
|
|
|
|
// The ACPI name search rule: for a name written WITHOUT a root or
|
|
// parent prefix, search the current scope for its first segment,
|
|
// then each parent scope up to the root, and descend the remaining
|
|
// segments from wherever it matched. `relative` is the name as it
|
|
// appeared in the AML ("PBIF", "BAT0.PBIF"), never a path already
|
|
// rebased onto a scope. Returns -1 if no scope contains it.
|
|
int32_t SearchUpward(const char* relative, int32_t scopeNodeIndex) const;
|
|
|
|
// Find the child of `parent` named `seg`; a short name is padded
|
|
// with '_' the way node names are stored. Returns -1 if absent.
|
|
int32_t FindChild(int32_t parentIndex, const char* seg) const;
|
|
|
|
// Get a node by index.
|
|
NamespaceNode* GetNode(int32_t index);
|
|
const NamespaceNode* GetNode(int32_t index) const;
|
|
|
|
// 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; }
|
|
|
|
// Maximum capacity with current chunk limit.
|
|
int32_t MaxCapacity() const { return MaxChunks * NodesPerChunk; }
|
|
|
|
// 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();
|
|
bool AllocChunk();
|
|
|
|
// Append a child index to a parent, growing its array as needed.
|
|
// Returns false only when the heap is exhausted.
|
|
bool AppendChild(NamespaceNode* parent, int32_t child);
|
|
|
|
void EnsureRoot();
|
|
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_chunks[MaxChunks]; // array of pointers to heap-allocated chunks
|
|
int32_t m_chunkCount;
|
|
int32_t m_nodeCount;
|
|
};
|
|
|
|
};
|
|
};
|