feat: power down dGPUs on hybrid graphics laptops
This commit is contained in:
1 parent
7f12e1a150
commit
24f88ec4c6
23 files changed
+4325
-1130
No files matched your search
+2653
-1073
File diff suppressed because it is too large.
Load diff
@@ -97,6 +97,35 @@ namespace Hal {
|
||||
static constexpr uint8_t ConcatResOp = 0x84;
|
||||
static constexpr uint8_t ToHexStringOp = 0x98;
|
||||
static constexpr uint8_t ToDecimalStringOp = 0x97;
|
||||
static constexpr uint8_t ExternalOp = 0x15;
|
||||
static constexpr uint8_t RefOfOp = 0x71;
|
||||
static constexpr uint8_t FindSetLeftBitOp = 0x81;
|
||||
static constexpr uint8_t FindSetRightBitOp = 0x82;
|
||||
static constexpr uint8_t NotifyOp = 0x86;
|
||||
static constexpr uint8_t MatchOp = 0x89;
|
||||
static constexpr uint8_t ObjectTypeOp = 0x8E;
|
||||
static constexpr uint8_t CreateQWordFieldOp = 0x8F;
|
||||
static constexpr uint8_t ToStringOp = 0x9C;
|
||||
static constexpr uint8_t CopyObjectOp = 0x9D;
|
||||
static constexpr uint8_t MidOp = 0x9E;
|
||||
static constexpr uint8_t ContinueOp = 0x9F;
|
||||
static constexpr uint8_t BreakPointOp = 0xCC;
|
||||
|
||||
// More extended opcodes (after ExtOpPrefix)
|
||||
static constexpr uint8_t CondRefOfOp = 0x12;
|
||||
static constexpr uint8_t CreateFieldOp = 0x13;
|
||||
static constexpr uint8_t LoadTableOp = 0x1F;
|
||||
static constexpr uint8_t LoadOp = 0x20;
|
||||
static constexpr uint8_t SignalOp = 0x24;
|
||||
static constexpr uint8_t WaitOp = 0x25;
|
||||
static constexpr uint8_t ResetOp = 0x26;
|
||||
static constexpr uint8_t FromBCDOp = 0x28;
|
||||
static constexpr uint8_t ToBCDOp = 0x29;
|
||||
static constexpr uint8_t UnloadOp = 0x2A;
|
||||
static constexpr uint8_t DebugOp = 0x31;
|
||||
static constexpr uint8_t FatalOp = 0x32;
|
||||
static constexpr uint8_t TimerOp = 0x33;
|
||||
static constexpr uint8_t DataRegionOp = 0x88;
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -104,7 +133,11 @@ namespace Hal {
|
||||
|
||||
// ============================================================================
|
||||
static constexpr int MaxCallDepth = 16;
|
||||
static constexpr int MaxLoopIterations = 1024;
|
||||
|
||||
// ACPICA's ceiling. Firmware polls hardware in While loops with a
|
||||
// Sleep or Stall in the body (link training, power-good, L2/L3 ready)
|
||||
// and expects more than a token number of passes.
|
||||
static constexpr int MaxLoopIterations = 0xFFFF;
|
||||
|
||||
// Backstop cap on evaluation nesting. This counter is shared by
|
||||
// expression evaluation AND block execution, and block nesting is
|
||||
@@ -133,8 +166,10 @@ namespace Hal {
|
||||
public:
|
||||
Interpreter();
|
||||
|
||||
// Parse a DSDT or SSDT table into the namespace.
|
||||
// tableData points to the CommonSDTHeader (HHDM-mapped).
|
||||
// Parse a DSDT or SSDT table into the namespace. Tables may be
|
||||
// loaded one after another; each method remembers which table
|
||||
// its body lives in. tableData points to the CommonSDTHeader
|
||||
// (HHDM-mapped) and must stay mapped.
|
||||
bool LoadTable(void* tableData);
|
||||
|
||||
// Evaluate a named object, returning its value.
|
||||
@@ -144,6 +179,28 @@ namespace Hal {
|
||||
// Evaluate a method with arguments.
|
||||
bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result);
|
||||
|
||||
// Evaluate a node: a method is run with `args`, a field is read,
|
||||
// anything else hands back its value (non-data objects as a
|
||||
// Reference to the node).
|
||||
bool EvaluateNode(int32_t node, const Object* args, int argCount, Object& result);
|
||||
|
||||
// Find the child of `parent` named `seg` ("_PS3", "_ON"), -1 if
|
||||
// there is none. Short names are padded with '_'.
|
||||
int32_t FindChild(int32_t parent, const char* seg) const;
|
||||
|
||||
// EvaluateNode() on a named child. False when it does not exist.
|
||||
bool EvaluateChild(int32_t parent, const char* seg, const Object* args,
|
||||
int argCount, Object& result);
|
||||
|
||||
// Turn a Reference, or a NameRef/String naming an object relative
|
||||
// to `scopeNode`, into a node index. -1 when it names nothing.
|
||||
int32_t ResolveReference(const Object& obj, int32_t scopeNode) const;
|
||||
|
||||
// The PCI function a Device node describes: its _ADR on the bus
|
||||
// behind its parent (a root bridge's _BBN, or a PCI-to-PCI
|
||||
// bridge's secondary bus). False when it cannot be placed.
|
||||
bool GetPciAddress(int32_t deviceNode, uint8_t& bus, uint8_t& dev, uint8_t& fn);
|
||||
|
||||
// Read a field value. Returns integer.
|
||||
bool ReadField(int32_t nodeIndex, uint64_t& value);
|
||||
|
||||
@@ -157,6 +214,10 @@ namespace Hal {
|
||||
// Check if the interpreter has been initialized
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
|
||||
// Nodes [0, DsdtNodeCount()) came from the DSDT. Nodes past it were
|
||||
// added by SSDTs (or by methods run since).
|
||||
int32_t DsdtNodeCount() const { return m_dsdtNodeCount; }
|
||||
|
||||
// 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.
|
||||
@@ -170,8 +231,10 @@ namespace Hal {
|
||||
// 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.
|
||||
// it was declared with. A name in a constant package comes back as
|
||||
// a NameRef unless it resolves (from the root) to plain data, in
|
||||
// which case its value is returned. 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.
|
||||
@@ -183,17 +246,35 @@ namespace Hal {
|
||||
int32_t MaterializeNodePackage(int32_t nodeIndex);
|
||||
|
||||
private:
|
||||
struct ExecContext;
|
||||
|
||||
// ============================================================================
|
||||
// Parsing (table load)
|
||||
// ============================================================================
|
||||
// ctx is the method being executed when a definition appears in
|
||||
// a method body, nullptr at table load. Operands that are TermArgs
|
||||
// (region offsets, buffer sizes, bank values) are evaluated in it.
|
||||
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);
|
||||
int32_t scopeNode, ExecContext* ctx = nullptr);
|
||||
|
||||
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
||||
int32_t scopeNode);
|
||||
int32_t scopeNode, ExecContext* ctx = nullptr);
|
||||
|
||||
// The FieldList shared by Field, IndexField and BankField.
|
||||
void ParseFieldList(const uint8_t* aml, uint32_t* pos, uint32_t fieldEnd,
|
||||
int32_t scopeNode, uint8_t fieldFlags, const Object& proto);
|
||||
|
||||
// Evaluate a TermArg operand of a definition: in the running
|
||||
// method, or in the table-load context.
|
||||
bool EvalOperand(ExecContext* ctx, const uint8_t* aml, uint32_t* pos,
|
||||
uint32_t endOffset, int32_t scopeNode, Object& result);
|
||||
|
||||
// CreateBitField .. CreateQWordField and CreateField.
|
||||
bool CreateBufferField(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
|
||||
uint8_t op);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -214,6 +295,10 @@ namespace Hal {
|
||||
char* outRelative = nullptr, int relLen = 0,
|
||||
bool* outSearchable = nullptr);
|
||||
|
||||
// Read a NameString and resolve it with the search rule. -1 when
|
||||
// it names nothing.
|
||||
int32_t ReadAndResolveName(const uint8_t* aml, uint32_t* pos, int32_t scopeNode);
|
||||
|
||||
// Read a single 4-char NameSeg from AML. Advances *pos.
|
||||
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
|
||||
|
||||
@@ -225,11 +310,10 @@ 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.
|
||||
// advance *pos past it. Names come back as NameRef; locals and
|
||||
// anything else that needs a running method decode to integer 0.
|
||||
bool DecodeConstElement(const uint8_t* data, uint32_t length,
|
||||
uint32_t* pos, Object& out);
|
||||
uint32_t* pos, Object& out) const;
|
||||
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
|
||||
|
||||
// ============================================================================
|
||||
@@ -247,6 +331,8 @@ namespace Hal {
|
||||
Object ReturnValue;
|
||||
bool Returned;
|
||||
bool Broken;
|
||||
bool Continued;
|
||||
bool Aborted; // undecodable AML: unwind the method
|
||||
int Depth; // method-call nesting
|
||||
int ExprDepth; // expression/block nesting
|
||||
};
|
||||
@@ -259,36 +345,117 @@ namespace Hal {
|
||||
~DepthGuard() { Value--; }
|
||||
};
|
||||
|
||||
// Where a Store lands.
|
||||
struct Target {
|
||||
enum class Kind : uint8_t { None, Node, Local, Arg, PkgElement, BufByte };
|
||||
Kind What = Kind::None;
|
||||
int32_t Node = -1;
|
||||
int Index = 0; // local/arg number, or element index
|
||||
int32_t PkgStore = -1;
|
||||
Object* Buf = nullptr; // BufByte: the buffer being indexed
|
||||
};
|
||||
|
||||
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.
|
||||
// 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,
|
||||
int32_t& pkgStore, int& pkgElement);
|
||||
// Extended (0x5B-prefixed) opcodes in expression or statement position.
|
||||
bool EvalExtOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
|
||||
// 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,
|
||||
int32_t pkgStore = -1, int pkgElement = 0);
|
||||
// EvalTerm's opcode handlers. They live out of line so the
|
||||
// recursive path only carries the locals of the one running.
|
||||
bool EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalPackageOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalBinary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalUnary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalDivide(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalStoreOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalReference(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalIndexOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalMatchOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalMidOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool EvalNameRef(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
||||
bool CallMethod(ExecContext& ctx, int32_t node, uint32_t* pos, uint32_t endOffset,
|
||||
Object& result);
|
||||
bool IndexElement(const Object& source, uint64_t idx, Object& out);
|
||||
void ReportUnresolved(ExecContext& ctx, uint32_t namePos);
|
||||
|
||||
// Target forms that need operand evaluation of their own.
|
||||
bool EvalIndexTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
||||
bool EvalDerefTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
||||
|
||||
// A package element: like EvalTerm, except that a name is a
|
||||
// reference to the object rather than a call or a read.
|
||||
bool EvalPackageElement(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
|
||||
Object& result);
|
||||
|
||||
// Parse a SuperName/Target operand.
|
||||
bool EvalTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
||||
|
||||
// Store a value to a target, with ACPI's implicit conversion for
|
||||
// named data objects.
|
||||
void StoreToTarget(ExecContext& ctx, const Object& value, const Target& target);
|
||||
|
||||
// Parse a Target operand and store `value` there.
|
||||
void StoreResult(ExecContext& ctx, uint32_t* pos, const Object& value);
|
||||
|
||||
// The current value behind a target (for Increment and friends).
|
||||
Object ReadTarget(ExecContext& ctx, const Target& target);
|
||||
|
||||
// Run a method node. Caller holds m_evalLock.
|
||||
bool InvokeMethodNode(int32_t node, const Object* args, int argCount,
|
||||
Object& result, int depth, int exprDepth);
|
||||
|
||||
// Run a method in a context already acquired and holding its
|
||||
// arguments; releases the context.
|
||||
bool RunMethodIn(ExecContext* ctx, int32_t node, int depth, int exprDepth,
|
||||
Object& result);
|
||||
|
||||
// The value of a node as an operand: fields read, methods run
|
||||
// without arguments, non-data objects become References.
|
||||
bool NodeValue(int32_t node, Object& result, int depth, int exprDepth);
|
||||
|
||||
// Log an opcode we cannot decode and stop the method: carrying on
|
||||
// past it would execute operand bytes as code.
|
||||
bool Abort(ExecContext& ctx, uint32_t pos, const char* why);
|
||||
|
||||
// Native methods (\_OSI).
|
||||
bool RunNativeMethod(uint8_t which, const Object* args, int argCount, Object& result);
|
||||
|
||||
// Install the OS-defined objects (\_OSI, \_REV, \_OS_, \_GL_).
|
||||
void InstallPredefined();
|
||||
|
||||
// Device identification (for PCI_Config regions).
|
||||
bool IntegerChild(int32_t device, const char* seg, uint64_t fallback,
|
||||
uint64_t& value, int depth);
|
||||
bool IsPciRootBridge(int32_t device, int depth);
|
||||
bool ResolvePciAddress(int32_t node, uint8_t& bus, uint8_t& dev,
|
||||
uint8_t& fn, int depth);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// 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);
|
||||
// Access `bits` (little-endian, bitLength long) of a field unit.
|
||||
bool AccessField(int32_t fieldNode, bool write, uint8_t* bits);
|
||||
bool ReadFieldObject(int32_t fieldNode, Object& value);
|
||||
bool WriteFieldObject(int32_t fieldNode, const Object& value);
|
||||
|
||||
// One aligned access unit of a field's backing store.
|
||||
bool FieldUnitIo(const Object& field, uint64_t byteOffset, uint32_t width,
|
||||
bool write, uint64_t& value);
|
||||
|
||||
// Region access at byte `offset` into the region, `width` bytes.
|
||||
bool ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t& value);
|
||||
bool WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t value);
|
||||
bool RegionIo(int32_t regionNode, uint64_t offset, uint32_t width,
|
||||
bool write, uint64_t& value);
|
||||
|
||||
bool ReadBufferField(const Object& field, Object& value);
|
||||
bool WriteBufferField(const Object& field, const Object& value);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -310,16 +477,23 @@ namespace Hal {
|
||||
ExecContext* AcquireContext();
|
||||
void ReleaseContext();
|
||||
|
||||
// Run `body` as a top-level evaluation: serialized, with the stack
|
||||
// budget anchored here.
|
||||
template<typename Fn> bool TopLevel(Fn body);
|
||||
|
||||
Namespace m_ns;
|
||||
const uint8_t* m_dsdt;
|
||||
uint32_t m_dsdtLength;
|
||||
bool m_initialized;
|
||||
bool m_predefined;
|
||||
int32_t m_dsdtNodeCount;
|
||||
|
||||
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
|
||||
ExecContext* m_loadCtx; // table-load operand context
|
||||
uint64_t m_delayedUs; // total Sleep/Stall time requested
|
||||
int m_methodScan; // >0 while scanning a method body at load
|
||||
kcp::Mutex m_evalLock;
|
||||
};
|
||||
|
||||
|
||||
@@ -89,6 +89,10 @@ namespace Hal {
|
||||
s_transientNext = 0;
|
||||
}
|
||||
|
||||
bool IsPersistent(int32_t index) {
|
||||
return index > 0 && index <= MaxPersistent;
|
||||
}
|
||||
|
||||
static Entry* Lookup(int32_t index) {
|
||||
if (index <= 0) return nullptr;
|
||||
int32_t i = index - 1;
|
||||
@@ -293,6 +297,12 @@ namespace Hal {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t Namespace::FindChild(int32_t parentIndex, const char* seg) const {
|
||||
char padded[MaxNameSegLen + 1];
|
||||
PadSegment(seg, padded);
|
||||
return FindChildByName(parentIndex, padded);
|
||||
}
|
||||
|
||||
int32_t Namespace::CreateNode(const char* absolutePath) {
|
||||
EnsureRoot();
|
||||
char segments[MaxPathDepth][MaxNameSegLen + 1];
|
||||
|
||||
@@ -31,6 +31,16 @@ namespace Hal {
|
||||
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,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -63,7 +73,7 @@ namespace Hal {
|
||||
static constexpr int InitialChildren = 8;
|
||||
static constexpr int MaxStringLen = 64;
|
||||
static constexpr int MaxBufferLen = 256;
|
||||
static constexpr int MaxPackageElements = 32;
|
||||
static constexpr int MaxPackageElements = 64;
|
||||
static constexpr int MaxMethodArgs = 7;
|
||||
static constexpr int MaxMethodLocals = 8;
|
||||
|
||||
@@ -102,10 +112,23 @@ namespace Hal {
|
||||
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
|
||||
uint32_t AmlOffset; // offset into DSDT AML where the method body starts
|
||||
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;
|
||||
|
||||
@@ -116,12 +139,33 @@ namespace Hal {
|
||||
} Region;
|
||||
|
||||
struct {
|
||||
uint32_t RegionNodeIndex; // index of the parent OperationRegion node
|
||||
// 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;
|
||||
@@ -132,6 +176,18 @@ namespace Hal {
|
||||
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
|
||||
@@ -155,6 +211,9 @@ namespace Hal {
|
||||
// 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);
|
||||
@@ -219,6 +278,10 @@ namespace Hal {
|
||||
// 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;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <ACPI/ACPI.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -239,15 +241,144 @@ namespace Hal {
|
||||
// Full interpreter initialization
|
||||
|
||||
// ============================================================================
|
||||
void InitializeInterpreter(void* dsdtData) {
|
||||
void InitializeInterpreter(void* dsdtData, ACPI::CommonSDTHeader* xsdt) {
|
||||
auto& interp = GetInterpreter();
|
||||
if (!interp.LoadTable(dsdtData)) {
|
||||
KernelLogStream(ERROR, "AML") << "Failed to load DSDT into AML interpreter";
|
||||
}
|
||||
|
||||
// SSDTs extend the DSDT's namespace and are loaded after it, in
|
||||
// XSDT order. They are where most laptops keep CPU power tables,
|
||||
// and -- the reason they matter here -- the discrete GPU's power
|
||||
// control (_PR3 power resources, _PS3, NVIDIA/AMD _DSM).
|
||||
int ssdts = 0;
|
||||
if (xsdt) {
|
||||
uint32_t entryCount = (xsdt->Length - sizeof(ACPI::CommonSDTHeader)) / 8;
|
||||
auto* entries = (uint64_t*)((uint64_t)xsdt + sizeof(ACPI::CommonSDTHeader));
|
||||
for (uint32_t i = 0; i < entryCount; i++) {
|
||||
if (entries[i] == 0) continue;
|
||||
auto* table = (ACPI::CommonSDTHeader*)Memory::HHDM(entries[i]);
|
||||
const char* sig = table->Signature;
|
||||
bool isSsdt = sig[0] == 'S' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T';
|
||||
bool isPsdt = sig[0] == 'P' && sig[1] == 'S' && sig[2] == 'D' && sig[3] == 'T';
|
||||
if (!isSsdt && !isPsdt) continue;
|
||||
if (table->Length <= sizeof(ACPI::CommonSDTHeader)) continue;
|
||||
if (interp.LoadTable(table)) ssdts++;
|
||||
}
|
||||
}
|
||||
KernelLogStream(OK, "AML") << "Loaded DSDT + " << base::dec << (uint64_t)ssdts
|
||||
<< " SSDT(s)";
|
||||
|
||||
auto& ns = interp.GetNamespace();
|
||||
KernelLogStream(OK, "AML") << "Namespace: " << base::dec
|
||||
<< (uint64_t)ns.NodeCount() << " / " << (uint64_t)ns.MaxCapacity() << " nodes (dynamic)";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Namespace initialization (_REG, _INI)
|
||||
|
||||
// ============================================================================
|
||||
static bool SegIs(const char* name, const char* seg) {
|
||||
return name[0] == seg[0] && name[1] == seg[1] &&
|
||||
name[2] == seg[2] && name[3] == seg[3];
|
||||
}
|
||||
|
||||
void InitializeNamespaceObjects() {
|
||||
auto& interp = GetInterpreter();
|
||||
if (!interp.IsInitialized()) return;
|
||||
auto& ns = interp.GetNamespace();
|
||||
|
||||
// Collect first, evaluate second: executing AML creates nodes,
|
||||
// which reallocates a parent's child array under a live walk.
|
||||
int32_t capacity = ns.NodeCount();
|
||||
auto* regs = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||
auto* devices = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||
if (!regs || !devices) {
|
||||
if (regs) Memory::g_heap->Free(regs);
|
||||
if (devices) Memory::g_heap->Free(devices);
|
||||
return;
|
||||
}
|
||||
int regCount = 0, deviceCount = 0;
|
||||
|
||||
ns.WalkDescendants(ns.RootIndex(), ObjectType::None,
|
||||
[&](int32_t idx, const NamespaceNode* node) {
|
||||
if (node->Obj.Type == ObjectType::Method && SegIs(node->Name, "_REG") &&
|
||||
regCount < capacity)
|
||||
regs[regCount++] = idx;
|
||||
if (node->Obj.Type == ObjectType::Device && deviceCount < capacity)
|
||||
devices[deviceCount++] = idx;
|
||||
});
|
||||
|
||||
// _REG(PCI_Config, connect). A _REG for another space checks Arg0
|
||||
// and ignores the call, as with the EC's.
|
||||
Object args[2]{};
|
||||
args[0].Type = ObjectType::Integer;
|
||||
args[0].Integer = (uint64_t)RegionSpace::PciConfig;
|
||||
args[1].Type = ObjectType::Integer;
|
||||
args[1].Integer = 1;
|
||||
for (int i = 0; i < regCount; i++) {
|
||||
Object result{};
|
||||
interp.EvaluateNode(regs[i], args, 2, result);
|
||||
}
|
||||
|
||||
// \_SB._INI runs before any device's.
|
||||
int32_t sb = ns.FindNode("\\_SB_");
|
||||
if (sb >= 0) {
|
||||
Object result{};
|
||||
if (interp.EvaluateChild(sb, "_INI", nullptr, 0, result))
|
||||
KernelLogStream(INFO, "AML") << "Ran \\_SB._INI";
|
||||
}
|
||||
|
||||
// Then each device, parents first (WalkDescendants is pre-order).
|
||||
// A device that is neither present nor functional takes its whole
|
||||
// subtree with it; one that is functional but absent skips only
|
||||
// its own _INI.
|
||||
static constexpr int MaxSkipped = 256;
|
||||
int32_t skipped[MaxSkipped];
|
||||
int skippedCount = 0;
|
||||
int ran = 0;
|
||||
|
||||
for (int i = 0; i < deviceCount; i++) {
|
||||
int32_t dev = devices[i];
|
||||
|
||||
bool underSkipped = false;
|
||||
for (int32_t p = ns.GetNode(dev) ? ns.GetNode(dev)->ParentIndex : -1;
|
||||
p > 0 && !underSkipped; p = ns.GetNode(p) ? ns.GetNode(p)->ParentIndex : -1) {
|
||||
for (int k = 0; k < skippedCount; k++)
|
||||
if (skipped[k] == p) { underSkipped = true; break; }
|
||||
}
|
||||
if (underSkipped) continue;
|
||||
|
||||
uint64_t sta = 0x0F;
|
||||
Object staObj{};
|
||||
if (interp.EvaluateChild(dev, "_STA", nullptr, 0, staObj) &&
|
||||
staObj.Type == ObjectType::Integer)
|
||||
sta = staObj.Integer;
|
||||
|
||||
if (!(sta & 0x01)) {
|
||||
if (!(sta & 0x08) && skippedCount < MaxSkipped)
|
||||
skipped[skippedCount++] = dev;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (interp.FindChild(dev, "_INI") < 0) continue;
|
||||
Object result{};
|
||||
bool ok = interp.EvaluateChild(dev, "_INI", nullptr, 0, result);
|
||||
ran++;
|
||||
|
||||
char path[128];
|
||||
ns.GetNodePath(dev, path, sizeof(path));
|
||||
KernelLogStream(DEBUG, "AML") << " _INI " << path << (ok ? "" : " FAILED");
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "AML") << "Namespace initialized: " << base::dec
|
||||
<< (uint64_t)regCount << " _REG, " << (uint64_t)ran << " device _INI";
|
||||
|
||||
Memory::g_heap->Free(regs);
|
||||
Memory::g_heap->Free(devices);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <ACPI/ACPI.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hal {
|
||||
@@ -27,11 +28,20 @@ namespace Hal {
|
||||
// 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
|
||||
// Initialize the AML interpreter with the DSDT, then every SSDT the
|
||||
// XSDT lists (xsdt may be null to load the DSDT alone).
|
||||
// This loads the full tables 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);
|
||||
void InitializeInterpreter(void* dsdtData, ACPI::CommonSDTHeader* xsdt);
|
||||
|
||||
// Run the namespace initialization the OS owes the firmware once its
|
||||
// address-space handlers are up: _REG for PCI_Config, then \_SB._INI
|
||||
// and every present device's _INI, parents first. Firmware works out
|
||||
// which OS it is running under (OSYS, from _OSI) in these, and gates
|
||||
// much of its later behaviour -- device power control included -- on
|
||||
// the answer. Call once the Embedded Controller and PCI are up.
|
||||
void InitializeNamespaceObjects();
|
||||
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user