/* * AmlNamespace.hpp * AML object types and ACPI namespace tree * Copyright (c) 2026 Daniel Hammer */ #pragma once #include #include 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 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 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; }; }; };