feat: add kernel ACPI support for querying battery percentage, add battery indicator to desktop, move log state to 0:/state directory

This commit is contained in:
daniel committed 2026-09-11 07:11:48 +02:00
1 parent 0a18b2cf2d
commit e641500b40
34 files changed
+2461 -100

No files matched your search

+568 -50
View File
@@ -6,10 +6,13 @@
#include "AmlInterpreter.hpp"
#include <ACPI/ACPI.hpp>
#include <ACPI/EmbeddedController.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>
using namespace Kt;
@@ -41,13 +44,78 @@ namespace Hal {
return IsLeadNameChar(c) || (c >= '0' && c <= '9');
}
// 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";
}
}
// 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;
}
// ============================================================================
// Constructor
// ============================================================================
Interpreter::Interpreter()
: m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false) {}
: m_dsdt(nullptr), m_dsdtLength(0), m_initialized(false),
m_ctxPool(nullptr), m_ctxInUse(0), m_evalNesting(0), m_stackFloor(0),
m_traceBudget(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--;
}
// ============================================================================
@@ -148,7 +216,9 @@ namespace Hal {
// 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* outPath, int maxLen,
char* outRelative, int relLen,
bool* outSearchable) {
int pathPos = 0;
bool isAbsolute = false;
int parentPrefixCount = 0;
@@ -220,6 +290,22 @@ namespace Hal {
}
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;
}
@@ -582,13 +668,17 @@ namespace Hal {
uint32_t fieldEnd = pkgStart + pkgLen;
if (fieldEnd > endOffset) fieldEnd = endOffset;
// Region name
// Region name. A Field almost always names a region
// declared in an enclosing scope, so the search rule is not
// optional here.
char regionPath[256];
ReadNameString(aml, pos, scopeNode, regionPath, 256);
char regionRel[64];
bool regionSearchable = false;
ReadNameString(aml, pos, scopeNode, regionPath, 256,
regionRel, sizeof(regionRel), &regionSearchable);
int32_t regionNode = m_ns.FindNode(regionPath);
if (regionNode < 0) {
regionNode = m_ns.ResolveName(regionPath, scopeNode);
}
if (regionNode < 0 && regionSearchable)
regionNode = m_ns.SearchUpward(regionRel, scopeNode);
// Field flags
uint8_t fieldFlags = aml[*pos];
@@ -833,34 +923,74 @@ namespace Hal {
auto* nsNode = m_ns.GetNode(node);
if (!nsNode || nsNode->Obj.Type != ObjectType::Method) return false;
ExecContext ctx{};
ctx.Aml = m_dsdt;
ctx.AmlBase = nsNode->Obj.Method.AmlOffset;
ctx.AmlLength = nsNode->Obj.Method.AmlLength;
ctx.ScopeNode = node;
ctx.Returned = false;
ctx.Broken = false;
ctx.Depth = 0;
// 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 now polls from a user
// thread while other subsystems may evaluate AML of their own.
bool topLevel = false;
m_evalLock.Acquire();
if (m_evalNesting == 0) {
topLevel = true;
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++;
ExecContext* ctx = AcquireContext();
if (!ctx) {
m_evalNesting--;
m_evalLock.Release();
return false;
}
ctx->Aml = m_dsdt;
ctx->AmlBase = nsNode->Obj.Method.AmlOffset;
ctx->AmlLength = nsNode->Obj.Method.AmlLength;
ctx->ScopeNode = node;
ctx->Returned = false;
ctx->Broken = false;
ctx->Depth = 0;
ctx->ExprDepth = 0;
// Set up arguments
for (int i = 0; i < argCount && i < MaxMethodArgs; i++) {
ctx.Args[i] = args[i];
ctx->Args[i] = args[i];
}
// Initialize locals to zero
for (int i = 0; i < MaxMethodLocals; i++) {
ctx.Locals[i].Type = ObjectType::Integer;
ctx.Locals[i].Integer = 0;
ctx->Locals[i].Type = ObjectType::Integer;
ctx->Locals[i].Integer = 0;
}
bool ok = ExecuteBlock(ctx, ctx.AmlBase, ctx.AmlBase + ctx.AmlLength);
bool ok = ExecuteBlock(*ctx, ctx->AmlBase, ctx->AmlBase + ctx->AmlLength);
result = ctx.ReturnValue;
result = ctx->ReturnValue;
if (result.Type == ObjectType::None) {
result.Type = ObjectType::Integer;
result.Integer = 0;
}
ReleaseContext();
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;
}
@@ -870,6 +1000,12 @@ namespace Hal {
// ============================================================================
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))
return false;
DepthGuard depthGuard(ctx.ExprDepth);
uint32_t pos = offset;
while (pos < endOffset && !ctx.Returned && !ctx.Broken) {
@@ -890,6 +1026,19 @@ namespace Hal {
uint8_t op = ctx.Aml[*pos];
if (m_traceBudget > 0) {
m_traceBudget--;
// Offsets are absolute into the DSDT, so they line up with a
// disassembly of the same table.
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)++;
@@ -988,8 +1137,11 @@ namespace Hal {
int32_t targetNode = -1;
bool isLocal = false, isArg = false;
int localIdx = 0, argIdx = 0;
EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx);
StoreToTarget(ctx, value, targetNode, isLocal, localIdx, isArg, argIdx);
int32_t pkgStore = -1; int pkgElement = 0;
EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx,
pkgStore, pkgElement);
StoreToTarget(ctx, value, targetNode, isLocal, localIdx, isArg, argIdx,
pkgStore, pkgElement);
return true;
}
@@ -999,7 +1151,9 @@ namespace Hal {
int32_t targetNode = -1;
bool isLocal = false, isArg = false;
int localIdx = 0, argIdx = 0;
EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx);
int32_t pkgStore = -1; int pkgElement = 0;
EvalTarget(ctx, pos, targetNode, isLocal, localIdx, isArg, argIdx,
pkgStore, pkgElement);
Object current{};
if (isLocal) current = ctx.Locals[localIdx];
@@ -1012,7 +1166,8 @@ namespace Hal {
if (current.Type == ObjectType::Integer) {
current.Integer += (op == IncrementOp) ? 1 : -1;
}
StoreToTarget(ctx, current, targetNode, isLocal, localIdx, isArg, argIdx);
StoreToTarget(ctx, current, targetNode, isLocal, localIdx, isArg, argIdx,
pkgStore, pkgElement);
return true;
}
@@ -1083,6 +1238,16 @@ namespace Hal {
// Evaluate an AML term that produces a value.
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 = Object{};
result.Type = ObjectType::Integer;
result.Integer = 0;
return false;
}
DepthGuard depthGuard(ctx.ExprDepth);
if (*pos >= endOffset) return false;
uint8_t op = ctx.Aml[*pos];
@@ -1136,6 +1301,14 @@ namespace Hal {
return true;
}
// DerefOf. Index() already yields the element's value here, so a
// dereference of one is just the value again; anything else passes
// through unchanged.
if (op == DerefOfOp) {
(*pos)++;
return EvalTerm(ctx, pos, endOffset, result);
}
// Package
if (op == PackageOp) {
(*pos)++;
@@ -1144,13 +1317,38 @@ namespace Hal {
uint32_t pkgEnd = pkgStart + pkgLen;
if (pkgEnd > endOffset) pkgEnd = endOffset;
// Store raw package as buffer for now
// Retain the raw bytes (constant elements are read straight
// out of them) 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.
result.Type = ObjectType::Package;
uint32_t dataLen = pkgEnd - *pos;
if (dataLen > MaxBufferLen) dataLen = MaxBufferLen;
result.Buffer.Length = dataLen;
memcpy(result.Buffer.Data, &ctx.Aml[*pos], dataLen);
uint32_t elemPos = *pos;
int count = (elemPos < pkgEnd) ? (int)ctx.Aml[elemPos] : 0;
elemPos++;
if (count > MaxPackageElements) count = MaxPackageElements;
if (count > 0) {
int32_t store = PackageStore::AllocTransient(count);
if (store > 0) {
for (int i = 0; i < count && elemPos < pkgEnd; i++) {
Object elem{};
uint32_t before = elemPos;
if (!EvalTerm(ctx, &elemPos, pkgEnd, elem)) break;
// An element that consumed nothing would spin here
// forever on a stream we decoded wrongly.
if (elemPos <= before) break;
Object* slot = PackageStore::Element(store, i);
if (slot) *slot = elem;
}
result.PkgIndex = store;
}
}
*pos = pkgEnd;
return true;
}
@@ -1259,14 +1457,16 @@ namespace Hal {
int32_t tn = -1;
bool il = false, ia = false;
int li = 0, ai = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai);
StoreToTarget(ctx, result, tn, il, li, ia, ai);
int32_t ps = -1; int pe = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe);
StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe);
} else if (IsLeadNameChar(next) || next == '\\' || next == '^') {
int32_t tn = -1;
bool il = false, ia = false;
int li = 0, ai = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai);
StoreToTarget(ctx, result, tn, il, li, ia, ai);
int32_t ps = -1; int pe = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe);
StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe);
}
// ZeroOp as "no target" — skip
else if (next == ZeroOp) {
@@ -1289,8 +1489,9 @@ namespace Hal {
int32_t tn = -1;
bool il = false, ia = false;
int li = 0, ai = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai);
StoreToTarget(ctx, result, tn, il, li, ia, ai);
int32_t ps = -1; int pe = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe);
StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe);
} else if (*pos < endOffset && ctx.Aml[*pos] == ZeroOp) {
(*pos)++;
}
@@ -1321,16 +1522,18 @@ namespace Hal {
int32_t tn = -1;
bool il = false, ia = false;
int li = 0, ai = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai);
StoreToTarget(ctx, remainder, tn, il, li, ia, ai);
int32_t ps = -1; int pe = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe);
StoreToTarget(ctx, remainder, tn, il, li, ia, ai, ps, pe);
}
// Result target
if (*pos < endOffset) {
int32_t tn = -1;
bool il = false, ia = false;
int li = 0, ai = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai);
StoreToTarget(ctx, result, tn, il, li, ia, ai);
int32_t ps = -1; int pe = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe);
StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe);
}
return true;
}
@@ -1345,6 +1548,8 @@ namespace Hal {
result.Integer = obj.String.Length;
else if (obj.Type == ObjectType::Buffer)
result.Integer = obj.Buffer.Length;
else if (obj.Type == ObjectType::Package)
result.Integer = (uint64_t)GetPackageCount(obj);
else
result.Integer = 0;
return true;
@@ -1357,12 +1562,23 @@ namespace Hal {
EvalTerm(ctx, pos, endOffset, source);
EvalTerm(ctx, pos, endOffset, index);
result.Type = ObjectType::Integer;
uint64_t idx = index.Integer;
if (source.Type == ObjectType::Buffer && idx < source.Buffer.Length) {
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.
if (!GetPackageElement(source, (int)idx, result)) {
result = Object{};
result.Type = ObjectType::Integer;
result.Integer = 0;
}
} else if (source.Type == ObjectType::Buffer && idx < source.Buffer.Length) {
result.Type = ObjectType::Integer;
result.Integer = source.Buffer.Data[idx];
} else {
result.Type = ObjectType::Integer;
result.Integer = 0;
}
@@ -1371,8 +1587,9 @@ namespace Hal {
int32_t tn = -1;
bool il = false, ia = false;
int li = 0, ai = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai);
StoreToTarget(ctx, result, tn, il, li, ia, ai);
int32_t ps = -1; int pe = 0;
EvalTarget(ctx, pos, tn, il, li, ia, ai, ps, pe);
StoreToTarget(ctx, result, tn, il, li, ia, ai, ps, pe);
} else if (*pos < endOffset && ctx.Aml[*pos] == ZeroOp) {
(*pos)++;
}
@@ -1405,12 +1622,14 @@ namespace Hal {
if (IsLeadNameChar(op) || op == '\\' || op == '^' ||
op == DualNamePrefix || op == MultiNamePrefix) {
char path[256];
ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256);
char relative[64];
bool searchable = false;
ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256,
relative, sizeof(relative), &searchable);
int32_t node = m_ns.FindNode(path);
if (node < 0) {
node = m_ns.ResolveName(path, ctx.ScopeNode);
}
if (node < 0 && searchable)
node = m_ns.SearchUpward(relative, ctx.ScopeNode);
if (node >= 0) {
auto* nsNode = m_ns.GetNode(node);
@@ -1430,7 +1649,13 @@ namespace Hal {
return true;
}
ExecContext subCtx{};
ExecContext* sub = AcquireContext();
if (!sub) {
result.Type = ObjectType::Integer;
result.Integer = 0;
return true;
}
ExecContext& subCtx = *sub;
subCtx.Aml = ctx.Aml;
subCtx.AmlBase = nsNode->Obj.Method.AmlOffset;
subCtx.AmlLength = nsNode->Obj.Method.AmlLength;
@@ -1438,6 +1663,7 @@ namespace Hal {
subCtx.Returned = false;
subCtx.Broken = false;
subCtx.Depth = ctx.Depth + 1;
subCtx.ExprDepth = ctx.ExprDepth;
for (int i = 0; i < argc; i++)
subCtx.Args[i] = args[i];
for (int i = 0; i < MaxMethodLocals; i++) {
@@ -1451,6 +1677,7 @@ namespace Hal {
result.Type = ObjectType::Integer;
result.Integer = 0;
}
ReleaseContext();
} else if (nsNode->Obj.Type == ObjectType::Field) {
uint64_t val = 0;
ReadField(node, val);
@@ -1483,12 +1710,25 @@ namespace Hal {
// ============================================================================
bool Interpreter::EvalTarget(ExecContext& ctx, uint32_t* pos,
int32_t& nodeIndex, bool& isLocal, int& localIdx,
bool& isArg, int& argIdx) {
bool& isArg, int& argIdx,
int32_t& pkgStore, int& pkgElement) {
nodeIndex = -1;
char stackMarker = 0;
if (ctx.ExprDepth >= MaxExprDepth) ReportDepthLimit();
if (ctx.ExprDepth >= MaxExprDepth || !HaveStackHeadroom(&stackMarker)) {
isLocal = false; isArg = false;
localIdx = 0; argIdx = 0;
pkgStore = -1; pkgElement = 0;
return false;
}
DepthGuard depthGuard(ctx.ExprDepth);
isLocal = false;
isArg = false;
localIdx = 0;
argIdx = 0;
pkgStore = -1;
pkgElement = 0;
if (*pos >= ctx.AmlBase + ctx.AmlLength) return false;
@@ -1520,10 +1760,73 @@ namespace Hal {
if (IsLeadNameChar(op) || op == '\\' || op == '^' ||
op == DualNamePrefix || op == MultiNamePrefix) {
char path[256];
ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256);
char relative[64];
bool searchable = false;
ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256,
relative, sizeof(relative), &searchable);
nodeIndex = m_ns.FindNode(path);
if (nodeIndex < 0)
nodeIndex = m_ns.ResolveName(path, ctx.ScopeNode);
if (nodeIndex < 0 && searchable)
nodeIndex = m_ns.SearchUpward(relative, ctx.ScopeNode);
return true;
}
// Index(source, n) names a package element. This is how firmware
// fills a result package -- Store(x, Index(BST0, 1)) -- so the
// element has to be addressable, not just readable.
if (op == IndexOp) {
(*pos)++;
// Resolve the source by name where we can, so the write lands
// in the namespace object rather than in a copy of it.
int32_t srcNode = -1;
uint8_t srcOp = (*pos < ctx.AmlBase + ctx.AmlLength) ? ctx.Aml[*pos] : 0;
if (IsLeadNameChar(srcOp) || srcOp == '\\' || srcOp == '^' ||
srcOp == DualNamePrefix || srcOp == MultiNamePrefix) {
char path[256];
char relative[64];
bool searchable = false;
ReadNameString(ctx.Aml, pos, ctx.ScopeNode, path, 256,
relative, sizeof(relative), &searchable);
srcNode = m_ns.FindNode(path);
if (srcNode < 0 && searchable)
srcNode = m_ns.SearchUpward(relative, ctx.ScopeNode);
} else {
// Locals and arguments can hold a package too.
Object src{};
EvalTerm(ctx, pos, ctx.AmlBase + ctx.AmlLength, src);
if (src.Type == ObjectType::Package && src.PkgIndex > 0)
pkgStore = src.PkgIndex;
}
Object idx{};
EvalTerm(ctx, pos, ctx.AmlBase + ctx.AmlLength, idx);
pkgElement = (int)idx.Integer;
if (srcNode >= 0) {
auto* srcObj = m_ns.GetNode(srcNode);
if (srcObj && srcObj->Obj.Type == ObjectType::Package)
pkgStore = MaterializeNodePackage(srcNode);
}
// 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 < ctx.AmlBase + ctx.AmlLength) {
int32_t innerNode = -1;
bool innerLocal = false, innerArg = false;
int innerLocalIdx = 0, innerArgIdx = 0;
int32_t innerStore = -1; int innerElement = 0;
EvalTarget(ctx, pos, innerNode, innerLocal, innerLocalIdx,
innerArg, innerArgIdx, innerStore, innerElement);
}
if (pkgStore <= 0) {
// Not a package we can write through (a buffer, or an
// unresolvable source): discard the store rather than
// corrupting an unrelated object.
pkgStore = -1;
nodeIndex = -1;
}
return true;
}
@@ -1539,7 +1842,14 @@ namespace Hal {
// ============================================================================
void Interpreter::StoreToTarget(ExecContext& ctx, const Object& value,
int32_t nodeIndex, bool isLocal, int localIdx,
bool isArg, int argIdx) {
bool isArg, int argIdx,
int32_t pkgStore, int pkgElement) {
if (pkgStore > 0) {
Object* slot = PackageStore::Element(pkgStore, pkgElement);
if (slot) *slot = value;
return;
}
if (isLocal && localIdx >= 0 && localIdx < MaxMethodLocals) {
ctx.Locals[localIdx] = value;
} else if (isArg && argIdx >= 0 && argIdx < MaxMethodArgs) {
@@ -1558,6 +1868,171 @@ namespace Hal {
// ============================================================================
// Package elements
// ============================================================================
bool Interpreter::DecodeConstElement(const uint8_t* data, uint32_t length,
uint32_t* pos, Object& out) {
out = Object{};
if (*pos >= length) return false;
uint8_t op = data[(*pos)++];
switch (op) {
case ZeroOp:
out.Type = ObjectType::Integer;
out.Integer = 0;
return true;
case OneOp:
out.Type = ObjectType::Integer;
out.Integer = 1;
return true;
case OnesOp:
out.Type = ObjectType::Integer;
out.Integer = ~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.Type = ObjectType::Integer;
out.Integer = 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 || op == PackageOp || op == VarPackageOp) {
uint32_t start = *pos;
uint32_t pkgLen = DecodePkgLength(data, pos);
uint32_t end = start + pkgLen;
if (end > length) end = length;
if (end < *pos) return false;
uint32_t dataLen = end - *pos;
if (dataLen > MaxBufferLen) dataLen = MaxBufferLen;
out.Type = (op == BufferOp) ? ObjectType::Buffer : ObjectType::Package;
out.Buffer.Length = dataLen;
memcpy(out.Buffer.Data, &data[*pos], dataLen);
*pos = end;
return true;
}
// A name reference, local, or anything else we cannot resolve from
// the bytes alone. Skip it and hand back zero.
if (IsLeadNameChar(op) || op == '\\' || op == '^' ||
op == DualNamePrefix || op == MultiNamePrefix) {
(*pos)--;
uint32_t p = *pos;
// Walk the NameString by hand: prefixes, then 4-char segments.
while (p < length && (data[p] == '\\' || data[p] == '^')) p++;
if (p < length && data[p] == DualNamePrefix) {
p += 1 + 8;
} else if (p < length && data[p] == MultiNamePrefix) {
uint8_t segs = (p + 1 < length) ? data[p + 1] : 0;
p += 2 + (uint32_t)segs * 4;
} else if (p < length && data[p] == 0x00) {
p += 1; // NullName
} else {
p += 4;
}
*pos = (p > length) ? length : p;
}
out.Type = ObjectType::Integer;
out.Integer = 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);
// Raw form: the first byte of the retained AML is NumElements.
if (package.Buffer.Length < 1) return 0;
return (int)package.Buffer.Data[0];
}
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;
}
uint32_t length = package.Buffer.Length;
if (length < 1) return false;
if (index >= (int)package.Buffer.Data[0]) return false;
uint32_t pos = 1; // past NumElements
for (int i = 0; i <= index; i++) {
if (!DecodeConstElement(package.Buffer.Data, length, &pos, result))
return false;
}
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) return node->Obj.PkgIndex;
int count = GetPackageCount(node->Obj);
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 the constant bytes the package was
// declared with, so a package that is only partly written through
// Index() keeps its declared values everywhere else.
uint32_t pos = 1;
for (int i = 0; i < count; i++) {
Object elem{};
if (!DecodeConstElement(node->Obj.Buffer.Data, node->Obj.Buffer.Length,
&pos, elem))
break;
Object* slot = PackageStore::Element(store, i);
if (slot) *slot = elem;
}
node->Obj.PkgIndex = store;
return store;
}
// ============================================================================
// ReadField
// ============================================================================
@@ -1578,7 +2053,19 @@ namespace Hal {
uint64_t byteAddr = regBase + (bitOff / 8);
uint32_t bitShift = bitOff % 8;
return ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, value);
uint64_t raw = 0;
if (!ReadRegion(regionNode->Obj.Region.Space, byteAddr, bitLen + bitShift, raw))
return false;
// ReadRegion hands back whole access units starting at byteAddr;
// narrow that to the field's own bits so sub-byte flags (common in
// EC and PM register layouts) don't carry their neighbours along.
raw >>= bitShift;
if (bitLen < 64)
raw &= (1ULL << bitLen) - 1;
value = raw;
return true;
}
// ============================================================================
@@ -1639,6 +2126,13 @@ namespace Hal {
}
if (space == 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. Dereferencing that through the HHDM
// is how a bad table takes the kernel down with a #GP, so the
// address has to be inside what Init() actually mapped.
if (!HhdmReadable(address, accessBytes)) return false;
volatile uint8_t* mmio = (volatile uint8_t*)Memory::HHDM(address);
if (accessBytes <= 1)
value = *mmio;
@@ -1651,6 +2145,19 @@ namespace Hal {
return true;
}
if (space == 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 < accessBytes; i++) {
uint8_t byte = 0;
if (!EmbeddedController::Read((uint8_t)(address + i), byte))
return false;
value |= (uint64_t)byte << (i * 8);
}
return true;
}
if (space == RegionSpace::PciConfig) {
// PCI config space access -- not yet implemented
return false;
@@ -1681,6 +2188,8 @@ namespace Hal {
}
if (space == RegionSpace::SystemMemory) {
if (!HhdmReadable(address, accessBytes)) return false;
volatile uint8_t* mmio = (volatile uint8_t*)Memory::HHDM(address);
if (accessBytes <= 1)
*mmio = (uint8_t)value;
@@ -1693,6 +2202,15 @@ namespace Hal {
return true;
}
if (space == RegionSpace::EmbeddedControl) {
for (uint32_t i = 0; i < accessBytes; i++) {
if (!EmbeddedController::Write((uint8_t)(address + i),
(uint8_t)(value >> (i * 8))))
return false;
}
return true;
}
return false;
}
+100 -4
View File
@@ -7,6 +7,7 @@
#pragma once
#include "AmlNamespace.hpp"
#include <CppLib/Spinlock.hpp>
#include <cstdint>
namespace Hal {
@@ -105,6 +106,24 @@ namespace Hal {
static constexpr int MaxCallDepth = 16;
static constexpr int MaxLoopIterations = 1024;
// Backstop cap on evaluation nesting. This counter is shared by
// expression evaluation AND block execution, and block nesting is
// legitimately deep -- every If/Else/While body is another level, so a
// long firmware method reaches well past a handful. The real protection
// against running off the stack is HaveStackHeadroom(), which measures
// actual usage; this only catches a decode that recurses without
// consuming input.
static constexpr int MaxExprDepth = 48;
// How much stack the interpreter may consume before it gives up. Note
// this is an ALLOWANCE, not a reserve: frames here are expensive (a
// name-resolving EvalTerm frame carries a 256-byte path buffer plus
// 272-byte Object temporaries, so ~800 bytes each), and a real
// firmware _BIF nests far enough to need tens of them. Sized against
// Sched::StackPages -- keep well under it, since the syscall path below
// has already spent some of that stack.
static constexpr uint64_t StackBudget = 32768;
// ============================================================================
// Interpreter
@@ -138,6 +157,31 @@ namespace Hal {
// Check if the interpreter has been initialized
bool IsInitialized() const { return m_initialized; }
// Log every opcode executed, with its offset, for the next
// `budget` opcodes. The only way to see where a method that runs to
// completion without returning a value actually goes.
void SetTrace(int budget) { m_traceBudget = budget; }
// ============================================================================
// Packages
// ============================================================================
// Read one element of a Package object. Works for both a package
// whose elements were computed at run time (materialized in the
// package store) and one that is still just the constant AML bytes
// it was declared with. Returns false when the object is not a
// package or the index is out of range.
bool GetPackageElement(const Object& package, int index, Object& result);
// Number of elements a Package declares.
int GetPackageCount(const Object& package);
// Give a package stored in a namespace node real per-element
// storage so Index() can be written through. Returns the store
// index, or -1 if the node does not hold a package.
int32_t MaterializeNodePackage(int32_t nodeIndex);
private:
// ============================================================================
// Parsing (table load)
@@ -158,8 +202,17 @@ namespace Hal {
// ============================================================================
// Read a NameString from AML and produce an absolute path.
// Advances *pos past the name.
// Produces the absolute path (rebased onto scopeNode for a
// relative name). outRelative, when given, receives the name as it
// actually appeared in the AML, and *outSearchable says whether the
// ACPI search rule applies to it -- true only when the name carried
// no root or parent prefix. Resolution must fall back to
// Namespace::SearchUpward() in that case: the absolute path is a
// guess at one scope, and the object usually lives in a parent.
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
char* outPath, int maxLen);
char* outPath, int maxLen,
char* outRelative = nullptr, int relLen = 0,
bool* outSearchable = nullptr);
// Read a single 4-char NameSeg from AML. Advances *pos.
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
@@ -170,6 +223,13 @@ namespace Hal {
// ============================================================================
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
// Decode one constant package element out of raw AML bytes and
// advance *pos past it. Non-constant elements (name references,
// locals) decode to integer 0, which is what they were worth
// before the package store existed.
bool DecodeConstElement(const uint8_t* data, uint32_t length,
uint32_t* pos, Object& out);
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
// ============================================================================
@@ -187,7 +247,16 @@ namespace Hal {
Object ReturnValue;
bool Returned;
bool Broken;
int Depth;
int Depth; // method-call nesting
int ExprDepth; // expression/block nesting
};
// Increments a depth counter for the duration of a scope; pairs
// with the MaxExprDepth check at each recursive entry point.
struct DepthGuard {
int& Value;
explicit DepthGuard(int& v) : Value(v) { Value++; }
~DepthGuard() { Value--; }
};
bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset);
@@ -199,14 +268,19 @@ namespace Hal {
// 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.
// A target may also be Index(package, n), which names a package
// element rather than a node, local or arg. pkgStore/pkgElement
// carry that case (pkgStore > 0 when it applies).
bool EvalTarget(ExecContext& ctx, uint32_t* pos,
int32_t& nodeIndex, bool& isLocal, int& localIdx,
bool& isArg, int& argIdx);
bool& isArg, int& argIdx,
int32_t& pkgStore, int& pkgElement);
// 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);
bool isArg, int argIdx,
int32_t pkgStore = -1, int pkgElement = 0);
// ============================================================================
@@ -221,10 +295,32 @@ namespace Hal {
// State
// ============================================================================
// ExecContext is ~4.4 KB (sixteen Objects, each carrying a
// 256-byte buffer inline). One per nested method call on a 16 KiB
// kernel stack overflows after three levels, so they come from a
// pool instead. The pool is only safe because evaluation is
// serialized -- see m_evalLock.
// Stack-exhaustion check; `frame` is the address of a local in
// the calling frame.
bool HaveStackHeadroom(const void* frame) const;
// One-shot warning when the nesting backstop trips.
static void ReportDepthLimit();
ExecContext* AcquireContext();
void ReleaseContext();
Namespace m_ns;
const uint8_t* m_dsdt;
uint32_t m_dsdtLength;
bool m_initialized;
ExecContext* m_ctxPool; // MaxCallDepth + 1 entries, heap
int m_ctxInUse;
int m_evalNesting; // top-level evaluations in progress
uint64_t m_stackFloor; // stack address the interpreter stops at
int m_traceBudget; // opcodes left to log, 0 = off
kcp::Mutex m_evalLock;
};
// ============================================================================
+196 -6
View File
@@ -7,10 +7,114 @@
#include "AmlNamespace.hpp"
#include <Libraries/Memory.hpp>
#include <Memory/Heap.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
using namespace Kt;
namespace Hal {
namespace AML {
// ============================================================================
// Package element store
// ============================================================================
namespace PackageStore {
struct Entry {
Object* Elements;
int Count;
};
static Entry s_persistent[MaxPersistent];
static Entry s_transient[MaxTransient];
static int s_persistentUsed = 0;
static int s_transientNext = 0;
// Indices handed out are 1-based so that a zeroed Object (nodes are
// memset before Clear() runs, and callers zero-init freely) never
// looks like a reference to entry 0.
static constexpr int32_t TransientBase = MaxPersistent;
static Object* AllocElements(int count) {
auto* mem = (Object*)Memory::g_heap->Request(sizeof(Object) * count);
if (!mem) return nullptr;
for (int i = 0; i < count; i++)
mem[i] = Object{};
return mem;
}
int32_t AllocPersistent(int count) {
if (count <= 0 || count > MaxPackageElements) return -1;
if (s_persistentUsed >= MaxPersistent) return -1;
Entry& e = s_persistent[s_persistentUsed];
e.Elements = AllocElements(count);
if (!e.Elements) return -1;
e.Count = count;
return (int32_t)(s_persistentUsed++) + 1;
}
int32_t AllocTransient(int count) {
if (count <= 0 || count > MaxPackageElements) return -1;
int slot = s_transientNext;
if (slot >= MaxTransient) {
// The ring is only rewound between top-level evaluations, so
// a method that builds more packages than this reuses the
// oldest slots rather than failing outright.
slot = 0;
s_transientNext = 0;
}
s_transientNext++;
Entry& e = s_transient[slot];
// Element arrays are sized on first use and reused afterwards;
// a larger request reallocates.
if (e.Elements == nullptr || e.Count < count) {
Object* mem = AllocElements(count);
if (!mem) return -1;
if (e.Elements) Memory::g_heap->Free(e.Elements);
e.Elements = mem;
} else {
for (int i = 0; i < count; i++)
e.Elements[i] = Object{};
}
e.Count = count;
return TransientBase + slot + 1;
}
void ResetTransient() {
s_transientNext = 0;
}
static Entry* Lookup(int32_t index) {
if (index <= 0) return nullptr;
int32_t i = index - 1;
if (i < MaxPersistent) {
if (i >= s_persistentUsed) return nullptr;
return s_persistent[i].Elements ? &s_persistent[i] : nullptr;
}
i -= TransientBase;
if (i < 0 || i >= MaxTransient) return nullptr;
return s_transient[i].Elements ? &s_transient[i] : nullptr;
}
Object* Element(int32_t index, int element) {
Entry* e = Lookup(index);
if (!e || element < 0 || element >= e->Count) return nullptr;
return &e->Elements[element];
}
int Count(int32_t index) {
Entry* e = Lookup(index);
return e ? e->Count : 0;
}
};
Namespace::Namespace() : m_chunkCount(0), m_nodeCount(0) {
for (int i = 0; i < MaxChunks; i++)
m_chunks[i] = nullptr;
@@ -30,7 +134,17 @@ namespace Hal {
}
bool Namespace::AllocChunk() {
if (m_chunkCount >= MaxChunks) return false;
if (m_chunkCount >= MaxChunks) {
static bool warned = false;
if (!warned) {
warned = true;
KernelLogStream(WARNING, "AML")
<< "Namespace node pool full at " << base::dec
<< (uint64_t)(MaxChunks * NodesPerChunk)
<< " nodes; part of the namespace will be missing";
}
return false;
}
auto* chunk = (NamespaceNode*)Memory::g_heap->Request(
sizeof(NamespaceNode) * NodesPerChunk);
@@ -45,6 +159,41 @@ namespace Hal {
return true;
}
bool Namespace::AppendChild(NamespaceNode* parent, int32_t child) {
if (parent->ChildCount >= parent->ChildCapacity) {
int32_t newCap = parent->ChildCapacity ? parent->ChildCapacity * 2
: InitialChildren;
auto* grown = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * newCap);
if (!grown) {
// Losing a child silently costs whole devices, so say it
// once rather than leaving a hole nobody can see.
static bool warned = false;
if (!warned) {
warned = true;
KernelLogStream(WARNING, "AML")
<< "Out of heap growing a namespace node's child list;"
<< " part of the namespace will be missing";
}
return false;
}
for (int32_t i = 0; i < parent->ChildCount; i++)
grown[i] = parent->ChildIndices[i];
for (int32_t i = parent->ChildCount; i < newCap; i++)
grown[i] = -1;
if (parent->ChildIndices)
Memory::g_heap->Free(parent->ChildIndices);
parent->ChildIndices = grown;
parent->ChildCapacity = newCap;
}
parent->ChildIndices[parent->ChildCount++] = child;
return true;
}
int32_t Namespace::AllocNode() {
// Check if we need a new chunk
int32_t capacity = m_chunkCount * NodesPerChunk;
@@ -165,11 +314,7 @@ namespace Hal {
// Add to parent's children
auto* parent = GetNode(current);
if (!parent) return -1;
if (parent->ChildCount < MaxChildren) {
parent->ChildIndices[parent->ChildCount++] = child;
} else {
return -1; // too many children
}
if (!AppendChild(parent, child)) return -1;
}
current = child;
}
@@ -188,6 +333,51 @@ namespace Hal {
return current;
}
int32_t Namespace::SearchUpward(const char* relative, int32_t scopeNodeIndex) const {
char segments[MaxPathDepth][MaxNameSegLen + 1];
int segCount = 0;
// Split on '.', padding each segment to four characters the way
// node names are stored.
int i = 0;
while (relative[i] && segCount < MaxPathDepth) {
char seg[MaxNameSegLen + 1];
int n = 0;
while (relative[i] && relative[i] != '.' && n < MaxNameSegLen)
seg[n++] = relative[i++];
seg[n] = '\0';
while (relative[i] && relative[i] != '.') i++; // overlong segment
if (relative[i] == '.') i++;
PadSegment(seg, segments[segCount]);
segCount++;
}
if (segCount == 0) return -1;
// Walk up from the current scope looking for the first segment.
int32_t scope = scopeNodeIndex;
while (scope >= 0) {
int32_t found = FindChildByName(scope, segments[0]);
if (found >= 0) {
// Descend the rest. A scope that matches the first segment
// but not the remainder does not win -- keep walking up.
int32_t cur = found;
bool ok = true;
for (int k = 1; k < segCount; k++) {
cur = FindChildByName(cur, segments[k]);
if (cur < 0) { ok = false; break; }
}
if (ok) return cur;
}
auto* node = GetNode(scope);
scope = node ? node->ParentIndex : -1;
}
return -1;
}
int32_t Namespace::ResolveName(const char* name, int32_t scopeNodeIndex) const {
// If it starts with '\', it's absolute
if (name[0] == '\\')
+64 -6
View File
@@ -55,10 +55,15 @@ namespace Hal {
// ============================================================================
static constexpr int MaxNameSegLen = 4;
static constexpr int MaxPathDepth = 16;
static constexpr int MaxChildren = 64;
// 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 = 16;
static constexpr int MaxPackageElements = 32;
static constexpr int MaxMethodArgs = 7;
static constexpr int MaxMethodLocals = 8;
@@ -76,6 +81,14 @@ namespace Hal {
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;
@@ -116,7 +129,36 @@ namespace Hal {
} Processor;
};
Object() : Type(ObjectType::None), Integer(0) {}
Object() : Type(ObjectType::None), PkgIndex(-1), Integer(0) {}
};
// ============================================================================
// 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();
// 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);
};
// ============================================================================
@@ -129,16 +171,19 @@ namespace Hal {
char Name[MaxNameSegLen + 1]; // null-terminated 4-char segment
Object Obj;
int32_t ParentIndex; // -1 for root
int32_t ChildIndices[MaxChildren];
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;
for (int i = 0; i < MaxChildren; i++)
ChildIndices[i] = -1;
ChildCapacity = 0;
}
};
@@ -166,6 +211,14 @@ namespace Hal {
// 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;
// Get a node by index.
NamespaceNode* GetNode(int32_t index);
const NamespaceNode* GetNode(int32_t index) const;
@@ -217,6 +270,11 @@ namespace Hal {
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;