feat: power down dGPUs on hybrid graphics laptops
This commit is contained in:
1 parent
7f12e1a150
commit
24f88ec4c6
23 files changed
+4256
-1061
No files matched your search
+2584
-1004
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();
|
||||
|
||||
};
|
||||
};
|
||||
@@ -453,15 +453,14 @@ namespace Hal {
|
||||
auto* nsNode = ns.GetNode(node);
|
||||
if (!nsNode) return result;
|
||||
|
||||
if (nsNode->Obj.Type == AML::ObjectType::Package) {
|
||||
// Parse the raw package buffer to extract SLP_TYP values
|
||||
const uint8_t* data = nsNode->Obj.Buffer.Data;
|
||||
uint32_t len = nsNode->Obj.Buffer.Length;
|
||||
if (len < 2) return result;
|
||||
if (nsNode->Obj.Type == AML::ObjectType::Package && nsNode->Obj.Package.Data) {
|
||||
// Parse the raw package elements to extract SLP_TYP values
|
||||
const uint8_t* data = nsNode->Obj.Package.Data;
|
||||
uint32_t len = nsNode->Obj.Package.Length;
|
||||
if (len < 1) return result;
|
||||
|
||||
// NumElements
|
||||
uint8_t numElements = data[0];
|
||||
uint32_t pos = 1;
|
||||
uint32_t numElements = nsNode->Obj.Package.Count;
|
||||
uint32_t pos = 0;
|
||||
|
||||
if (numElements >= 1 && pos < len) {
|
||||
// Decode first element
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* AcpiPower.cpp
|
||||
* ACPI device power management: _PSx methods, _PRx power resources,
|
||||
* _DSM calls, and finding the namespace node behind a PCI function
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "AcpiPower.hpp"
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiPower {
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// PCI <-> ACPI
|
||||
|
||||
// ============================================================================
|
||||
struct PciMapEntry {
|
||||
int32_t Node;
|
||||
uint8_t Bus, Dev, Fn;
|
||||
uint8_t Score;
|
||||
};
|
||||
|
||||
static PciMapEntry* g_map = nullptr;
|
||||
static int g_mapCount = 0;
|
||||
static bool g_mapBuilt = false;
|
||||
|
||||
bool HasObject(int32_t device, const char* name) {
|
||||
return AML::GetInterpreter().FindChild(device, name) >= 0;
|
||||
}
|
||||
|
||||
// How much power management a node carries. Picks the real node out
|
||||
// of duplicates at one address.
|
||||
static uint8_t PowerScore(int32_t node) {
|
||||
uint8_t score = 0;
|
||||
if (HasObject(node, "_PR0") || HasObject(node, "_PR3")) score += 8;
|
||||
if (HasObject(node, "_PS3")) score += 4;
|
||||
if (HasObject(node, "_DSM")) score += 2;
|
||||
if (HasObject(node, "_OFF") || HasObject(node, "_PS0")) score += 1;
|
||||
return score;
|
||||
}
|
||||
|
||||
static void BuildPciMap() {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
auto& ns = interp.GetNamespace();
|
||||
g_mapBuilt = true;
|
||||
g_mapCount = 0;
|
||||
if (!interp.IsInitialized()) return;
|
||||
|
||||
// Device nodes with an _ADR, collected before anything is
|
||||
// evaluated (evaluation may add nodes).
|
||||
int32_t capacity = ns.NodeCount();
|
||||
auto* nodes = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||
if (!nodes) return;
|
||||
int count = 0;
|
||||
int32_t sb = ns.FindNode("\\_SB_");
|
||||
if (sb < 0) sb = ns.RootIndex();
|
||||
ns.WalkDescendants(sb, AML::ObjectType::Device,
|
||||
[&](int32_t idx, const AML::NamespaceNode*) {
|
||||
if (count < capacity && ns.FindChild(idx, "_ADR") >= 0)
|
||||
nodes[count++] = idx;
|
||||
});
|
||||
|
||||
if (g_map) Memory::g_heap->Free(g_map);
|
||||
g_map = (PciMapEntry*)Memory::g_heap->Request(sizeof(PciMapEntry) * (count ? count : 1));
|
||||
if (!g_map) { Memory::g_heap->Free(nodes); return; }
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint8_t bus, dev, fn;
|
||||
if (!interp.GetPciAddress(nodes[i], bus, dev, fn)) continue;
|
||||
g_map[g_mapCount++] = { nodes[i], bus, dev, fn, PowerScore(nodes[i]) };
|
||||
}
|
||||
|
||||
Memory::g_heap->Free(nodes);
|
||||
}
|
||||
|
||||
void InvalidatePciMap() {
|
||||
g_mapBuilt = false;
|
||||
}
|
||||
|
||||
int32_t FindPciDevice(uint8_t bus, uint8_t dev, uint8_t fn) {
|
||||
if (!g_mapBuilt) BuildPciMap();
|
||||
|
||||
int32_t best = -1;
|
||||
int bestScore = -1;
|
||||
for (int i = 0; i < g_mapCount; i++) {
|
||||
const auto& e = g_map[i];
|
||||
if (e.Bus != bus || e.Dev != dev || e.Fn != fn) continue;
|
||||
if (e.Score > bestScore) {
|
||||
best = e.Node;
|
||||
bestScore = e.Score;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Power resources
|
||||
|
||||
// ============================================================================
|
||||
static int AddUnique(int32_t* out, int count, int max, int32_t node) {
|
||||
if (node < 0) return count;
|
||||
for (int i = 0; i < count; i++)
|
||||
if (out[i] == node) return count;
|
||||
if (count < max) out[count++] = node;
|
||||
return count;
|
||||
}
|
||||
|
||||
// The resources one _PRx object lists. The list is copied out before
|
||||
// anything else is evaluated: a computed package lives in storage the
|
||||
// next evaluation reuses.
|
||||
static int CollectList(int32_t device, const char* name, int32_t* out, int count, int max) {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
auto& ns = interp.GetNamespace();
|
||||
|
||||
AML::Object pkg{};
|
||||
if (!interp.EvaluateChild(device, name, nullptr, 0, pkg)) return count;
|
||||
if (pkg.Type != AML::ObjectType::Package) return count;
|
||||
|
||||
int n = interp.GetPackageCount(pkg);
|
||||
int32_t found[MaxResources];
|
||||
int foundCount = 0;
|
||||
for (int i = 0; i < n && foundCount < MaxResources; i++) {
|
||||
AML::Object elem{};
|
||||
if (!interp.GetPackageElement(pkg, i, elem)) break;
|
||||
int32_t node = interp.ResolveReference(elem, device);
|
||||
auto* nn = ns.GetNode(node);
|
||||
if (nn && nn->Obj.Type == AML::ObjectType::PowerResource)
|
||||
found[foundCount++] = node;
|
||||
}
|
||||
|
||||
for (int i = 0; i < foundCount; i++)
|
||||
count = AddUnique(out, count, max, found[i]);
|
||||
return count;
|
||||
}
|
||||
|
||||
int CollectResources(int32_t device, int32_t* out, int max) {
|
||||
int count = 0;
|
||||
count = CollectList(device, "_PR0", out, count, max);
|
||||
count = CollectList(device, "_PR1", out, count, max);
|
||||
count = CollectList(device, "_PR2", out, count, max);
|
||||
count = CollectList(device, "_PR3", out, count, max);
|
||||
return count;
|
||||
}
|
||||
|
||||
int ResourceState(int32_t resource) {
|
||||
AML::Object sta{};
|
||||
if (!AML::GetInterpreter().EvaluateChild(resource, "_STA", nullptr, 0, sta))
|
||||
return -1;
|
||||
return (ResultInteger(sta) & 1) ? 1 : 0;
|
||||
}
|
||||
|
||||
// Does a present device outside `owners` list this resource for D0?
|
||||
// Turning off a rail another device is running on is how a GPU
|
||||
// power-down would take out, say, an NVMe drive sharing a slot rail.
|
||||
static bool NeededElsewhere(int32_t resource, const int32_t* owners, int ownerCount) {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
auto& ns = interp.GetNamespace();
|
||||
|
||||
int32_t capacity = ns.NodeCount();
|
||||
auto* users = (int32_t*)Memory::g_heap->Request(sizeof(int32_t) * capacity);
|
||||
if (!users) return true; // cannot tell: assume shared
|
||||
int count = 0;
|
||||
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Device,
|
||||
[&](int32_t idx, const AML::NamespaceNode*) {
|
||||
if (count < capacity && ns.FindChild(idx, "_PR0") >= 0)
|
||||
users[count++] = idx;
|
||||
});
|
||||
|
||||
bool shared = false;
|
||||
for (int i = 0; i < count && !shared; i++) {
|
||||
int32_t dev = users[i];
|
||||
|
||||
// Everything at or below an owner goes down with it: the
|
||||
// card's own child nodes, a duplicate placeholder node at the
|
||||
// same address, a function the firmware hid from PCI.
|
||||
bool own = false;
|
||||
for (int32_t a = dev; a > 0 && !own; ) {
|
||||
for (int k = 0; k < ownerCount; k++)
|
||||
if (owners[k] == a) { own = true; break; }
|
||||
auto* an = ns.GetNode(a);
|
||||
a = an ? an->ParentIndex : -1;
|
||||
}
|
||||
if (own) continue;
|
||||
|
||||
int32_t list[MaxResources];
|
||||
int n = CollectList(dev, "_PR0", list, 0, MaxResources);
|
||||
bool listed = false;
|
||||
for (int k = 0; k < n; k++)
|
||||
if (list[k] == resource) { listed = true; break; }
|
||||
if (!listed) continue;
|
||||
|
||||
// Only a device that is actually there counts.
|
||||
AML::Object sta{};
|
||||
uint64_t status = 0x0F;
|
||||
if (interp.EvaluateChild(dev, "_STA", nullptr, 0, sta))
|
||||
status = ResultInteger(sta);
|
||||
if (status & 0x01) {
|
||||
char path[128];
|
||||
ns.GetNodePath(dev, path, sizeof(path));
|
||||
KernelLogStream(INFO, "AcpiPower") << " power resource also used by "
|
||||
<< path << "; leaving it on";
|
||||
shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
Memory::g_heap->Free(users);
|
||||
return shared;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Device power transitions
|
||||
|
||||
// ============================================================================
|
||||
void EnterD3Cold(int32_t device, const int32_t* owners, int ownerCount,
|
||||
D3ColdReport& report) {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
auto& ns = interp.GetNamespace();
|
||||
report = D3ColdReport{};
|
||||
if (device < 0) return;
|
||||
|
||||
char path[128];
|
||||
ns.GetNodePath(device, path, sizeof(path));
|
||||
|
||||
// Gather the resources first: _PS3 may change what _PRx reports.
|
||||
int32_t resources[MaxResources];
|
||||
int count = CollectResources(device, resources, MaxResources);
|
||||
|
||||
if (HasObject(device, "_PS3")) {
|
||||
AML::Object result{};
|
||||
bool ok = interp.EvaluateChild(device, "_PS3", nullptr, 0, result);
|
||||
report.RanPs3 = true;
|
||||
KernelLogStream(INFO, "AcpiPower") << " " << path << "._PS3"
|
||||
<< (ok ? "" : " (failed)");
|
||||
}
|
||||
|
||||
// Off in reverse order of listing (ACPI turns resources off in
|
||||
// reverse ResourceOrder; firmware lists them in that order).
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
int32_t res = resources[i];
|
||||
char rpath[128];
|
||||
ns.GetNodePath(res, rpath, sizeof(rpath));
|
||||
|
||||
if (ResourceState(res) == 0) {
|
||||
KernelLogStream(INFO, "AcpiPower") << " " << rpath << " already off";
|
||||
continue;
|
||||
}
|
||||
if (NeededElsewhere(res, owners, ownerCount)) {
|
||||
report.ResourcesShared++;
|
||||
continue;
|
||||
}
|
||||
|
||||
AML::Object result{};
|
||||
bool ok = interp.EvaluateChild(res, "_OFF", nullptr, 0, result);
|
||||
int after = ResourceState(res);
|
||||
if (ok) report.ResourcesOff++;
|
||||
if (after == 1) report.ResourcesStillOn++;
|
||||
KernelLogStream(INFO, "AcpiPower") << " " << rpath << "._OFF"
|
||||
<< (ok ? "" : " (failed)")
|
||||
<< (after == 0 ? ", now off" : after == 1 ? ", still on" : "");
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// _DSM
|
||||
|
||||
// ============================================================================
|
||||
AML::Object IntegerArg(uint64_t value) {
|
||||
AML::Object o{};
|
||||
o.Type = AML::ObjectType::Integer;
|
||||
o.Integer = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
AML::Object BufferArg(const uint8_t* bytes, uint32_t length) {
|
||||
AML::Object o{};
|
||||
o.Type = AML::ObjectType::Buffer;
|
||||
if (length > AML::MaxBufferLen) length = AML::MaxBufferLen;
|
||||
memcpy(o.Buffer.Data, bytes, length);
|
||||
o.Buffer.Length = length;
|
||||
return o;
|
||||
}
|
||||
|
||||
AML::Object EmptyPackageArg() {
|
||||
AML::Object o{};
|
||||
o.Type = AML::ObjectType::Package;
|
||||
o.Package.Data = nullptr;
|
||||
o.Package.Length = 0;
|
||||
o.Package.Count = 0;
|
||||
return o;
|
||||
}
|
||||
|
||||
uint64_t ResultInteger(const AML::Object& result) {
|
||||
if (result.Type == AML::ObjectType::Integer) return result.Integer;
|
||||
if (result.Type == AML::ObjectType::Buffer) {
|
||||
uint64_t v = 0;
|
||||
for (uint32_t i = 0; i < result.Buffer.Length && i < 8; i++)
|
||||
v |= (uint64_t)result.Buffer.Data[i] << (8 * i);
|
||||
return v;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EvaluateDsm(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||
uint64_t function, const AML::Object& arg3, AML::Object& result) {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
if (device < 0 || interp.FindChild(device, "_DSM") < 0) return false;
|
||||
|
||||
AML::Object args[4];
|
||||
args[0] = BufferArg(uuid, 16);
|
||||
args[1] = IntegerArg(revision);
|
||||
args[2] = IntegerArg(function);
|
||||
args[3] = arg3;
|
||||
return interp.EvaluateChild(device, "_DSM", args, 4, result);
|
||||
}
|
||||
|
||||
uint64_t DsmFunctions(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||
const AML::Object& arg3) {
|
||||
AML::Object result{};
|
||||
if (!EvaluateDsm(device, uuid, revision, 0, arg3, result)) return 0;
|
||||
uint64_t mask = ResultInteger(result);
|
||||
// Bit 0 clear means "no functions for this UUID/revision".
|
||||
return (mask & 1) ? mask : 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* AcpiPower.hpp
|
||||
* ACPI device power management: _PSx methods, _PRx power resources,
|
||||
* _DSM calls, and finding the namespace node behind a PCI function
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <ACPI/AML/AmlNamespace.hpp>
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiPower {
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// PCI <-> ACPI
|
||||
|
||||
// ============================================================================
|
||||
// The Device node describing PCI function bus:dev.fn, or -1. When
|
||||
// firmware declares more than one node at the same address (a generic
|
||||
// placeholder plus a vendor SSDT's real one is common for GPUs), the
|
||||
// one carrying power-management objects wins.
|
||||
int32_t FindPciDevice(uint8_t bus, uint8_t dev, uint8_t fn);
|
||||
|
||||
// Forget the PCI address map built by FindPciDevice (bus numbers may
|
||||
// change across a resume).
|
||||
void InvalidatePciMap();
|
||||
|
||||
// True when `device` has a child object named `name` ("_PS3").
|
||||
bool HasObject(int32_t device, const char* name);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Power resources
|
||||
|
||||
// ============================================================================
|
||||
static constexpr int MaxResources = 8;
|
||||
|
||||
// The power resources the device lists in _PR0.._PR3 (deduplicated).
|
||||
int CollectResources(int32_t device, int32_t* out, int max);
|
||||
|
||||
// _STA of a power resource: 1 on, 0 off, -1 unknown.
|
||||
int ResourceState(int32_t resource);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Device power transitions
|
||||
|
||||
// ============================================================================
|
||||
struct D3ColdReport {
|
||||
bool RanPs3;
|
||||
int ResourcesOff; // resources whose _OFF was run
|
||||
int ResourcesShared; // left on: some other present device needs them
|
||||
int ResourcesStillOn; // _OFF ran but _STA still says on
|
||||
};
|
||||
|
||||
// Put an ACPI device into D3cold the way the spec orders it: _PS3,
|
||||
// then every power resource it depends on off, last-listed first. A
|
||||
// resource that a device outside `owners` also depends on is left
|
||||
// on -- `owners` lists the nodes being powered down together (the
|
||||
// functions of one card and the port above them), which may freely
|
||||
// share, as may anything below them in the namespace. The PCI side
|
||||
// (PMCSR to D3hot) is the caller's to do first.
|
||||
void EnterD3Cold(int32_t device, const int32_t* owners, int ownerCount,
|
||||
D3ColdReport& report);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// _DSM
|
||||
|
||||
// ============================================================================
|
||||
// Evaluate _DSM(uuid, revision, function, arg3) on a device. False
|
||||
// when the device has no _DSM or it failed.
|
||||
bool EvaluateDsm(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||
uint64_t function, const AML::Object& arg3, AML::Object& result);
|
||||
|
||||
// The function bitmap _DSM function 0 reports for (uuid, revision),
|
||||
// 0 when unsupported. Bit 0 set means "some functions exist".
|
||||
uint64_t DsmFunctions(int32_t device, const uint8_t uuid[16], uint64_t revision,
|
||||
const AML::Object& arg3);
|
||||
|
||||
// Helpers for building _DSM / method arguments.
|
||||
AML::Object IntegerArg(uint64_t value);
|
||||
AML::Object BufferArg(const uint8_t* bytes, uint32_t length);
|
||||
AML::Object EmptyPackageArg();
|
||||
|
||||
// Integer value of a method result: an integer as is, a buffer read
|
||||
// little-endian (up to 8 bytes).
|
||||
uint64_t ResultInteger(const AML::Object& result);
|
||||
};
|
||||
};
|
||||
@@ -58,7 +58,7 @@ namespace Hal {
|
||||
// Initialize the full AML interpreter with the DSDT.
|
||||
// This loads the entire DSDT namespace, enabling device enumeration,
|
||||
// method evaluation, and field I/O for the rest of the kernel.
|
||||
AML::InitializeInterpreter(dsdt);
|
||||
AML::InitializeInterpreter(dsdt, xsdt);
|
||||
|
||||
// Enumerate ACPI devices now that the namespace is loaded
|
||||
if (AML::GetInterpreter().IsInitialized()) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/Graphics/DgpuPower.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Graphics/Framebuffer.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
@@ -128,9 +129,9 @@ namespace Hal {
|
||||
const char* stepNames[] = {
|
||||
"?", "GDT", "TSS", "Syscalls", "PAT", "PIC",
|
||||
"LocalAPIC", "IoAPIC", "APICTimer", "PM1Clear",
|
||||
"WAK", "PS2", "IntelGPU", "sti"
|
||||
"WAK", "PS2", "IntelGPU", "dGPU", "sti"
|
||||
};
|
||||
const char* stepName = reinitStep <= 0x0D ? stepNames[reinitStep] : "?";
|
||||
const char* stepName = reinitStep <= 0x0E ? stepNames[reinitStep] : "?";
|
||||
KernelLogStream(WARNING, "S3") << "Reinit stopped after step 0x"
|
||||
<< base::hex << (uint64_t)reinitStep << " (" << stepName
|
||||
<< ") - next step crashed/hung";
|
||||
@@ -459,7 +460,9 @@ namespace Hal {
|
||||
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
|
||||
Drivers::Graphics::IntelGPU::Reinitialize();
|
||||
}
|
||||
reinitProgress(0x0D); // sti
|
||||
reinitProgress(0x0D); // discrete GPU back off
|
||||
Drivers::Graphics::DgpuPower::Resume();
|
||||
reinitProgress(0x0E); // sti
|
||||
asm volatile("sti");
|
||||
|
||||
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
||||
|
||||
@@ -249,13 +249,17 @@ namespace Hal {
|
||||
}
|
||||
|
||||
static bool ParseCstObject(const char* path, const AML::Object& obj) {
|
||||
if (obj.Type != AML::ObjectType::Package || obj.Buffer.Length < 2) {
|
||||
if (obj.Type != AML::ObjectType::Package || !obj.Package.Data ||
|
||||
obj.Package.Length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t pos = 1; // package element count byte
|
||||
// Package.Data starts at the first element (the state count).
|
||||
const uint8_t* data = obj.Package.Data;
|
||||
uint32_t length = obj.Package.Length;
|
||||
uint32_t pos = 0;
|
||||
uint64_t declaredStateCount = 0;
|
||||
if (!DecodeInteger(obj.Buffer.Data, obj.Buffer.Length, pos, declaredStateCount)) {
|
||||
if (!DecodeInteger(data, length, pos, declaredStateCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -263,7 +267,7 @@ namespace Hal {
|
||||
for (uint64_t i = 0; i < declaredStateCount && parsedCount < MaxIdleStates; i++) {
|
||||
const uint8_t* nestedData = nullptr;
|
||||
uint32_t nestedLength = 0;
|
||||
if (!DecodePackageObject(obj.Buffer.Data, obj.Buffer.Length, pos, nestedData, nestedLength)) {
|
||||
if (!DecodePackageObject(data, length, pos, nestedData, nestedLength)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -310,7 +314,10 @@ namespace Hal {
|
||||
auto& ns = interp.GetNamespace();
|
||||
char path[256];
|
||||
|
||||
for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) {
|
||||
// DSDT processors only, for the same reason as the _CST scan.
|
||||
int32_t limit = interp.DsdtNodeCount();
|
||||
if (limit == 0) limit = ns.NodeCount();
|
||||
for (int32_t nodeIndex = 0; nodeIndex < limit; nodeIndex++) {
|
||||
auto* node = ns.GetNode(nodeIndex);
|
||||
if (node == nullptr || node->Obj.Type != AML::ObjectType::Processor) {
|
||||
continue;
|
||||
@@ -522,7 +529,13 @@ namespace Hal {
|
||||
auto& ns = interp.GetNamespace();
|
||||
char path[256];
|
||||
|
||||
for (int32_t nodeIndex = 0; nodeIndex < ns.NodeCount(); nodeIndex++) {
|
||||
// DSDT objects only. SSDTs are loaded too now (for device power
|
||||
// methods), and many carry processor _CST tables; those C-state
|
||||
// lists have never been exercised against the deep-idle timer
|
||||
// behaviour on real laptops, so they stay out until they have.
|
||||
int32_t limit = interp.DsdtNodeCount();
|
||||
if (limit == 0) limit = ns.NodeCount();
|
||||
for (int32_t nodeIndex = 0; nodeIndex < limit; nodeIndex++) {
|
||||
auto* node = ns.GetNode(nodeIndex);
|
||||
if (node == nullptr) continue;
|
||||
if (!IsName(node->Name, "_CST")) continue;
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 270
|
||||
#define MONTAUK_BUILD_NUMBER 276
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <ACPI/AcpiEvents.hpp>
|
||||
#include <ACPI/EmbeddedController.hpp>
|
||||
#include <ACPI/Battery.hpp>
|
||||
#include <ACPI/AML/AmlParser.hpp>
|
||||
#include <Hal/Apic/ApicInit.hpp>
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
@@ -153,6 +154,12 @@ extern "C" void kmain() {
|
||||
|
||||
Pci::Initialize(g_acpi.GetXSDT());
|
||||
|
||||
// _REG and _INI, once the EC and PCI config space (ECAM, for the
|
||||
// extended registers PCI_Config regions reach) are both available:
|
||||
// firmware settles which OS it is talking to here, and gates device
|
||||
// power control on the answer.
|
||||
Hal::AML::InitializeNamespaceObjects();
|
||||
|
||||
Drivers::ProbeEarly();
|
||||
Drivers::InitializeGraphics();
|
||||
|
||||
@@ -162,6 +169,10 @@ extern "C" void kmain() {
|
||||
Drivers::PS2::Keyboard::Initialize();
|
||||
Drivers::PS2::Mouse::Initialize();
|
||||
|
||||
// Hybrid graphics: switch off a discrete GPU nothing drives before
|
||||
// the normal probe can claim its USB-C/audio functions.
|
||||
Drivers::PowerDownUnusedGpus();
|
||||
|
||||
Drivers::ProbeNormal();
|
||||
Drivers::InitializeNetwork();
|
||||
Drivers::InitializeStorage();
|
||||
|
||||
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* DgpuPower.cpp
|
||||
* Powers down discrete GPUs that no MontaukOS driver runs
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "DgpuPower.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <ACPI/AcpiPower.hpp>
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
#include <Boot/Boot.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
namespace AcpiPower = Hal::AcpiPower;
|
||||
namespace AML = Hal::AML;
|
||||
|
||||
namespace Drivers::Graphics::DgpuPower {
|
||||
|
||||
// =========================================================================
|
||||
// Vendor interfaces
|
||||
// =========================================================================
|
||||
|
||||
static constexpr uint16_t VendorNvidia = 0x10DE;
|
||||
static constexpr uint16_t VendorAmd = 0x1002;
|
||||
|
||||
// NVIDIA Optimus _DSM (GUID a486d8f8-0bda-471b-a72b-6042a6b5bee0, rev
|
||||
// 0x100). Function 0x1A (OPTIMUS_CAPS) with "power down in PS3" set
|
||||
// arms the GPU's _PS3 to cut its power; 0x1B (OPTIMUS_FLAGS) 0x3 is
|
||||
// what the drivers send alongside it.
|
||||
static constexpr uint8_t OptimusUuid[16] = {
|
||||
0xF8, 0xD8, 0x86, 0xA4, 0xDA, 0x0B, 0x1B, 0x47,
|
||||
0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0,
|
||||
};
|
||||
static constexpr uint64_t OptimusRevision = 0x100;
|
||||
static constexpr uint64_t OptimusCaps = 0x1A;
|
||||
static constexpr uint64_t OptimusFlags = 0x1B;
|
||||
static constexpr uint32_t OptimusPowerdown = (3u << 24) | 1u; // POWERDOWN_PS3 | FLAGS_CHANGED
|
||||
|
||||
// The older NVIDIA hybrid _DSM (GUID d4a50b75-65c7-46f7-bfb7-41514cea0244,
|
||||
// rev 0x102): function 3 (POWER) with 2 (STAMINA) switches the discrete
|
||||
// GPU off on pre-Optimus muxed laptops.
|
||||
static constexpr uint8_t NvidiaDsmUuid[16] = {
|
||||
0x75, 0x0B, 0xA5, 0xD4, 0xC7, 0x65, 0xF7, 0x46,
|
||||
0xBF, 0xB7, 0x41, 0x51, 0x4C, 0xEA, 0x02, 0x44,
|
||||
};
|
||||
static constexpr uint64_t NvidiaDsmRevision = 0x102;
|
||||
static constexpr uint64_t NvidiaDsmPower = 0x03;
|
||||
static constexpr uint64_t NvidiaDsmStamina = 0x02;
|
||||
|
||||
// AMD PowerXpress ATPX(function, buffer).
|
||||
static constexpr uint64_t AtpxVerifyInterface = 0;
|
||||
static constexpr uint64_t AtpxGetParameters = 1;
|
||||
static constexpr uint64_t AtpxPowerControl = 2;
|
||||
static constexpr uint32_t AtpxParamsSupported = 1u << 0; // function mask bits
|
||||
static constexpr uint32_t AtpxPowerSupported = 1u << 1;
|
||||
static constexpr uint32_t AtpxHybridGraphics = 1u << 14; // PX parameter flag
|
||||
|
||||
// =========================================================================
|
||||
// State
|
||||
// =========================================================================
|
||||
|
||||
static constexpr int MaxCards = 4;
|
||||
static constexpr int MaxFunctions = 8;
|
||||
static constexpr int MaxChain = 4;
|
||||
|
||||
struct PciAddr {
|
||||
uint8_t Bus, Dev, Fn;
|
||||
};
|
||||
|
||||
struct Card {
|
||||
uint8_t Bus, Dev, DisplayFn;
|
||||
uint16_t Vendor, DeviceId;
|
||||
uint8_t Functions[MaxFunctions];
|
||||
int FunctionCount;
|
||||
PciAddr Chain[MaxChain]; // bridges above it, nearest first
|
||||
int ChainCount;
|
||||
};
|
||||
|
||||
static Card g_cards[MaxCards];
|
||||
static int g_cardCount = 0;
|
||||
|
||||
int PoweredDownCount() {
|
||||
return g_cardCount;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PCI helpers
|
||||
// =========================================================================
|
||||
|
||||
static uint32_t Cfg(uint8_t bus, uint8_t dev, uint8_t fn, uint16_t off, uint8_t width) {
|
||||
return Pci::ConfigRead(bus, dev, fn, off, width);
|
||||
}
|
||||
|
||||
static bool Present(uint8_t bus, uint8_t dev, uint8_t fn) {
|
||||
return (Cfg(bus, dev, fn, 0x00, 2) & 0xFFFF) != 0xFFFF;
|
||||
}
|
||||
|
||||
// The bridge whose secondary bus is `bus`, or nullptr for a root bus.
|
||||
static const Pci::PciDevice* BridgeAbove(uint8_t bus) {
|
||||
auto& devices = Pci::GetDevices();
|
||||
for (uint64_t i = 0; i < devices.size(); i++) {
|
||||
const auto& d = devices[i];
|
||||
if (!Pci::IsBridge(d)) continue;
|
||||
if (Cfg(d.Bus, d.Device, d.Function, Pci::PCI_REG_SECONDARY_BUS, 1) == bus)
|
||||
return &d;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void BusRange(const Pci::PciDevice& b, uint8_t& secondary, uint8_t& subordinate) {
|
||||
secondary = (uint8_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_SECONDARY_BUS, 1);
|
||||
subordinate = (uint8_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_SUBORDINATE_BUS, 1);
|
||||
}
|
||||
|
||||
// Does anything other than the card and its own bridges live below b?
|
||||
static bool SubtreeIsCard(const Pci::PciDevice& b, const Card& card) {
|
||||
uint8_t sec, sub;
|
||||
BusRange(b, sec, sub);
|
||||
auto& devices = Pci::GetDevices();
|
||||
for (uint64_t i = 0; i < devices.size(); i++) {
|
||||
const auto& d = devices[i];
|
||||
if (d.Bus < sec || d.Bus > sub) continue;
|
||||
if (d.Bus == card.Bus && d.Device == card.Dev) continue;
|
||||
bool chain = false;
|
||||
for (int k = 0; k < card.ChainCount; k++) {
|
||||
if (card.Chain[k].Bus == d.Bus && card.Chain[k].Dev == d.Device &&
|
||||
card.Chain[k].Fn == d.Function) { chain = true; break; }
|
||||
}
|
||||
if (!chain) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Does the bridge forward this physical address downstream?
|
||||
static bool WindowContains(const Pci::PciDevice& b, uint64_t phys) {
|
||||
uint32_t mem = Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_MEM_BASE, 4);
|
||||
uint64_t base = (uint64_t)(mem & 0xFFF0) << 16;
|
||||
uint64_t limit = ((uint64_t)((mem >> 16) & 0xFFF0) << 16) | 0xFFFFF;
|
||||
if (base <= limit && phys >= base && phys <= limit) return true;
|
||||
|
||||
uint32_t pref = Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_PREF_BASE, 4);
|
||||
uint64_t pbase = (uint64_t)(pref & 0xFFF0) << 16;
|
||||
uint64_t plimit = ((uint64_t)((pref >> 16) & 0xFFF0) << 16) | 0xFFFFF;
|
||||
if ((pref & 0xF) == 0x1) {
|
||||
pbase |= (uint64_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_PREF_BASE_UPPER, 4) << 32;
|
||||
plimit |= (uint64_t)Cfg(b.Bus, b.Device, b.Function, Pci::PCI_REG_PREF_LIMIT_UPPER, 4) << 32;
|
||||
}
|
||||
return pbase <= plimit && phys >= pbase && phys <= plimit;
|
||||
}
|
||||
|
||||
// Functions that ship on a graphics card: the GPU itself, its HDMI/DP
|
||||
// audio, and (NVIDIA Turing+, AMD Navi) a USB-C xHCI and UCSI controller.
|
||||
// An integrated GPU behind an internal bridge -- AMD APUs -- shares its
|
||||
// device with the platform security processor, audio DSP and the
|
||||
// machine's own USB, and must never be mistaken for a card.
|
||||
static bool IsCardFunction(const Pci::PciDevice& d) {
|
||||
if (d.ClassCode == 0x03) return true; // display
|
||||
if (d.ClassCode == 0x04 && d.SubClass == 0x03) return true; // HD audio
|
||||
if (d.ClassCode == 0x0C && d.SubClass == 0x03) return true; // USB
|
||||
if (d.ClassCode == 0x0C && d.SubClass == 0x80) return true; // UCSI
|
||||
return false;
|
||||
}
|
||||
|
||||
static int PciePortType(uint8_t bus, uint8_t dev, uint8_t fn) {
|
||||
uint8_t cap = Pci::FindCapability(bus, dev, fn, Pci::PCI_CAP_PCIE);
|
||||
if (cap == 0) return -1;
|
||||
return (int)((Cfg(bus, dev, fn, cap + 2, 2) >> 4) & 0xF);
|
||||
}
|
||||
static constexpr int PortTypeRootPort = 0x4;
|
||||
|
||||
static void LogAddr(KernelLogStream& log, uint8_t bus, uint8_t dev, uint8_t fn) {
|
||||
log << base::hex << (uint64_t)bus << ":" << (uint64_t)dev << "." << (uint64_t)fn;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Detection
|
||||
// =========================================================================
|
||||
|
||||
static bool BootFramebuffer(uint64_t& phys) {
|
||||
const auto& info = montauk::boot::Info();
|
||||
if (!info.has(montauk::boot::FeatureFramebuffer) || info.framebuffer.address == 0)
|
||||
return false;
|
||||
phys = Memory::SubHHDM(info.framebuffer.address);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decide whether `gpu` is a discrete card we may switch off, filling in
|
||||
// `card` if so. Every reason to leave it alone is logged.
|
||||
static bool Qualifies(const Pci::PciDevice& gpu, Card& card) {
|
||||
card = Card{};
|
||||
card.Bus = gpu.Bus;
|
||||
card.Dev = gpu.Device;
|
||||
card.DisplayFn = gpu.Function;
|
||||
card.Vendor = gpu.VendorId;
|
||||
card.DeviceId = gpu.DeviceId;
|
||||
|
||||
auto reject = [&](const char* why) {
|
||||
KernelLogStream log(INFO, "dGPU");
|
||||
log << "Leaving ";
|
||||
LogAddr(log, gpu.Bus, gpu.Device, gpu.Function);
|
||||
log << " (" << base::hex << (uint64_t)gpu.VendorId << ":" << (uint64_t)gpu.DeviceId
|
||||
<< ") on: " << why;
|
||||
return false;
|
||||
};
|
||||
|
||||
if (gpu.Claimed) return reject("a driver runs it");
|
||||
|
||||
// An integrated GPU sits on the root bus. A card is behind a port.
|
||||
const Pci::PciDevice* port = BridgeAbove(gpu.Bus);
|
||||
if (!port) return reject("on the root bus (integrated)");
|
||||
|
||||
// The whole device: every function must be one a card carries, and
|
||||
// none may already belong to a driver.
|
||||
auto& devices = Pci::GetDevices();
|
||||
bool otherDisplay = false;
|
||||
for (uint64_t i = 0; i < devices.size(); i++) {
|
||||
const auto& d = devices[i];
|
||||
if (d.Bus == gpu.Bus && d.Device == gpu.Device) {
|
||||
if (!IsCardFunction(d)) return reject("shares its device with platform functions");
|
||||
if (d.Claimed) return reject("a driver runs one of its functions");
|
||||
if (card.FunctionCount < MaxFunctions)
|
||||
card.Functions[card.FunctionCount++] = d.Function;
|
||||
} else if (d.ClassCode == 0x03) {
|
||||
otherDisplay = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hybrid graphics only: something else has to drive the screen.
|
||||
if (!otherDisplay) return reject("it is the only display controller");
|
||||
|
||||
// The firmware's boot display (the GOP framebuffer, or legacy VGA)
|
||||
// must not be scanned out by this card -- on a muxed laptop switched
|
||||
// to discrete mode it is, and the panel would go dark.
|
||||
uint64_t fb = 0;
|
||||
if (BootFramebuffer(fb)) {
|
||||
if (WindowContains(*port, fb)) return reject("it scans out the boot display");
|
||||
} else {
|
||||
uint16_t ctl = (uint16_t)Cfg(port->Bus, port->Device, port->Function,
|
||||
Pci::PCI_REG_BRIDGE_CONTROL, 2);
|
||||
if (ctl & Pci::PCI_BRIDGE_CTL_VGA) return reject("legacy VGA is routed to it");
|
||||
}
|
||||
|
||||
// The port, and any switch above it that serves nothing else, power
|
||||
// down with the card. The first bridge must serve only the card.
|
||||
const Pci::PciDevice* b = port;
|
||||
while (b && card.ChainCount < MaxChain) {
|
||||
card.Chain[card.ChainCount++] = { b->Bus, b->Device, b->Function };
|
||||
if (!SubtreeIsCard(*b, card)) {
|
||||
card.ChainCount--;
|
||||
break;
|
||||
}
|
||||
if (b->Bus == 0) break;
|
||||
b = BridgeAbove(b->Bus);
|
||||
}
|
||||
if (card.ChainCount == 0) return reject("its port also serves other devices");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Vendor methods
|
||||
// =========================================================================
|
||||
|
||||
// The ACPI nodes of every display controller, where the vendor _DSM or
|
||||
// ATPX may live (the integrated GPU's node, on some muxed designs).
|
||||
static int DisplayNodes(int32_t* out, int max) {
|
||||
int count = 0;
|
||||
auto& devices = Pci::GetDevices();
|
||||
for (uint64_t i = 0; i < devices.size() && count < max; i++) {
|
||||
const auto& d = devices[i];
|
||||
if (d.ClassCode != 0x03) continue;
|
||||
int32_t node = AcpiPower::FindPciDevice(d.Bus, d.Device, d.Function);
|
||||
if (node >= 0) out[count++] = node;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static AML::Object Dword(uint32_t v) {
|
||||
uint8_t b[4] = { (uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), (uint8_t)(v >> 24) };
|
||||
return AcpiPower::BufferArg(b, 4);
|
||||
}
|
||||
|
||||
// Arm Optimus power-down in _PS3. Returns true when firmware took it.
|
||||
static bool ArmOptimus(int32_t gpuNode) {
|
||||
if (gpuNode < 0) return false;
|
||||
uint64_t funcs = AcpiPower::DsmFunctions(gpuNode, OptimusUuid, OptimusRevision,
|
||||
AcpiPower::EmptyPackageArg());
|
||||
if (!(funcs & (1ULL << OptimusCaps))) return false;
|
||||
|
||||
AML::Object result{};
|
||||
AcpiPower::EvaluateDsm(gpuNode, OptimusUuid, OptimusRevision, OptimusCaps,
|
||||
Dword(OptimusPowerdown), result);
|
||||
if (funcs & (1ULL << OptimusFlags))
|
||||
AcpiPower::EvaluateDsm(gpuNode, OptimusUuid, OptimusRevision, OptimusFlags,
|
||||
Dword(0x3), result);
|
||||
KernelLogStream(INFO, "dGPU") << " Optimus _DSM: power-down on _PS3 armed";
|
||||
return true;
|
||||
}
|
||||
|
||||
// The node offering the legacy NVIDIA power _DSM, or -1.
|
||||
static int32_t FindNvidiaDsm(int32_t gpuNode) {
|
||||
int32_t nodes[8];
|
||||
int count = 0;
|
||||
if (gpuNode >= 0) nodes[count++] = gpuNode;
|
||||
count += DisplayNodes(nodes + count, 8 - count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint64_t funcs = AcpiPower::DsmFunctions(nodes[i], NvidiaDsmUuid, NvidiaDsmRevision,
|
||||
AcpiPower::EmptyPackageArg());
|
||||
if (funcs & (1ULL << NvidiaDsmPower)) return nodes[i];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The ATPX method, if the platform offers one with power control that
|
||||
// is meant to be used (not on "hybrid" designs, which use _PR3).
|
||||
static int32_t FindAtpx() {
|
||||
auto& interp = AML::GetInterpreter();
|
||||
auto& ns = interp.GetNamespace();
|
||||
|
||||
int32_t atpx = -1;
|
||||
int32_t nodes[8];
|
||||
int count = DisplayNodes(nodes, 8);
|
||||
for (int i = 0; i < count && atpx < 0; i++)
|
||||
atpx = interp.FindChild(nodes[i], "ATPX");
|
||||
if (atpx < 0) {
|
||||
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Method,
|
||||
[&](int32_t idx, const AML::NamespaceNode* n) {
|
||||
if (atpx < 0 && n->Name[0] == 'A' && n->Name[1] == 'T' &&
|
||||
n->Name[2] == 'P' && n->Name[3] == 'X')
|
||||
atpx = idx;
|
||||
});
|
||||
}
|
||||
if (atpx < 0) return -1;
|
||||
|
||||
AML::Object args[2] = { AcpiPower::IntegerArg(AtpxVerifyInterface),
|
||||
AcpiPower::IntegerArg(0) };
|
||||
AML::Object result{};
|
||||
if (!interp.EvaluateNode(atpx, args, 2, result) ||
|
||||
result.Type != AML::ObjectType::Buffer || result.Buffer.Length < 8)
|
||||
return -1;
|
||||
const uint8_t* r = result.Buffer.Data;
|
||||
uint32_t functions = r[4] | (r[5] << 8) | (r[6] << 16) | ((uint32_t)r[7] << 24);
|
||||
|
||||
if (functions & AtpxParamsSupported) {
|
||||
args[0] = AcpiPower::IntegerArg(AtpxGetParameters);
|
||||
if (interp.EvaluateNode(atpx, args, 2, result) &&
|
||||
result.Type == AML::ObjectType::Buffer && result.Buffer.Length >= 10) {
|
||||
const uint8_t* p = result.Buffer.Data;
|
||||
uint32_t valid = p[2] | (p[3] << 8) | (p[4] << 16) | ((uint32_t)p[5] << 24);
|
||||
uint32_t flags = p[6] | (p[7] << 8) | (p[8] << 16) | ((uint32_t)p[9] << 24);
|
||||
if (valid & flags & AtpxHybridGraphics) {
|
||||
KernelLogStream(INFO, "dGPU") << " ATPX: hybrid design, power via _PR3";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(functions & AtpxPowerSupported)) return -1;
|
||||
return atpx;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Power-down sequence
|
||||
// =========================================================================
|
||||
|
||||
static void PowerDownCard(const Card& card) {
|
||||
{
|
||||
KernelLogStream log(INFO, "dGPU");
|
||||
log << "Powering down ";
|
||||
LogAddr(log, card.Bus, card.Dev, card.DisplayFn);
|
||||
log << " (" << base::hex << (uint64_t)card.Vendor << ":"
|
||||
<< (uint64_t)card.DeviceId << ")";
|
||||
}
|
||||
|
||||
bool haveAcpi = AML::GetInterpreter().IsInitialized();
|
||||
|
||||
// 1. Quiesce every function: no decode, no DMA, no interrupts.
|
||||
for (int i = 0; i < card.FunctionCount; i++) {
|
||||
uint8_t fn = card.Functions[i];
|
||||
if (!Present(card.Bus, card.Dev, fn)) continue;
|
||||
uint16_t cmd = (uint16_t)Cfg(card.Bus, card.Dev, fn, Pci::PCI_REG_COMMAND, 2);
|
||||
cmd &= (uint16_t)~(0x7u); // I/O, memory, bus master
|
||||
Pci::ConfigWrite(card.Bus, card.Dev, fn, Pci::PCI_REG_COMMAND, 2, cmd);
|
||||
Pci::DisableInterruptDelivery(card.Bus, card.Dev, fn);
|
||||
}
|
||||
|
||||
// 2. The namespace side of the card and its ports.
|
||||
int32_t gpuNode = -1;
|
||||
int32_t owners[MaxFunctions + MaxChain];
|
||||
int ownerCount = 0;
|
||||
int32_t chainNodes[MaxChain];
|
||||
bool portHasPr3 = false;
|
||||
|
||||
if (haveAcpi) {
|
||||
AcpiPower::InvalidatePciMap();
|
||||
gpuNode = AcpiPower::FindPciDevice(card.Bus, card.Dev, card.DisplayFn);
|
||||
for (int i = 0; i < card.FunctionCount; i++) {
|
||||
int32_t n = AcpiPower::FindPciDevice(card.Bus, card.Dev, card.Functions[i]);
|
||||
if (n >= 0) owners[ownerCount++] = n;
|
||||
}
|
||||
for (int i = 0; i < card.ChainCount; i++) {
|
||||
chainNodes[i] = AcpiPower::FindPciDevice(card.Chain[i].Bus, card.Chain[i].Dev,
|
||||
card.Chain[i].Fn);
|
||||
if (chainNodes[i] >= 0) {
|
||||
owners[ownerCount++] = chainNodes[i];
|
||||
if (AcpiPower::HasObject(chainNodes[i], "_PR3")) portHasPr3 = true;
|
||||
}
|
||||
}
|
||||
|
||||
char path[128];
|
||||
if (gpuNode >= 0) {
|
||||
AML::GetInterpreter().GetNamespace().GetNodePath(gpuNode, path, sizeof(path));
|
||||
KernelLogStream(INFO, "dGPU") << " ACPI node " << path;
|
||||
} else {
|
||||
KernelLogStream(INFO, "dGPU") << " no ACPI node for the GPU";
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < card.ChainCount; i++) chainNodes[i] = -1;
|
||||
}
|
||||
|
||||
// 3. Vendor methods. With _PR3 on the port the platform does runtime
|
||||
// D3cold through power resources, and the drivers leave the vendor
|
||||
// methods alone there (they would make _PS3 cut power underneath
|
||||
// the power resources' own sequencing).
|
||||
int32_t nvidiaDsm = -1;
|
||||
int32_t atpx = -1;
|
||||
if (haveAcpi && !portHasPr3) {
|
||||
if (card.Vendor == VendorNvidia) {
|
||||
if (!ArmOptimus(gpuNode)) nvidiaDsm = FindNvidiaDsm(gpuNode);
|
||||
} else if (card.Vendor == VendorAmd) {
|
||||
atpx = FindAtpx();
|
||||
}
|
||||
}
|
||||
|
||||
// 4. PCI D3hot, companions first, the GPU last.
|
||||
int d3hot = 0;
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
for (int i = 0; i < card.FunctionCount; i++) {
|
||||
uint8_t fn = card.Functions[i];
|
||||
if ((fn == card.DisplayFn) != (pass == 1)) continue;
|
||||
if (!Present(card.Bus, card.Dev, fn)) continue;
|
||||
if (Pci::SetPowerState(card.Bus, card.Dev, fn, 3)) d3hot++;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Vendor power switch.
|
||||
if (nvidiaDsm >= 0) {
|
||||
AML::Object result{};
|
||||
AcpiPower::EvaluateDsm(nvidiaDsm, NvidiaDsmUuid, NvidiaDsmRevision, NvidiaDsmPower,
|
||||
AcpiPower::IntegerArg(NvidiaDsmStamina), result);
|
||||
KernelLogStream(INFO, "dGPU") << " NVIDIA _DSM: discrete GPU switched off";
|
||||
}
|
||||
if (atpx >= 0) {
|
||||
uint8_t params[3] = { 3, 0, 0 }; // size = 3, dGPU state = off
|
||||
AML::Object args[2] = { AcpiPower::IntegerArg(AtpxPowerControl),
|
||||
AcpiPower::BufferArg(params, 3) };
|
||||
AML::Object result{};
|
||||
AML::GetInterpreter().EvaluateNode(atpx, args, 2, result);
|
||||
Timekeeping::BusyWaitMs(200); // the off transition needs it
|
||||
KernelLogStream(INFO, "dGPU") << " ATPX: discrete GPU switched off";
|
||||
}
|
||||
|
||||
// 6. The GPU's own ACPI D3cold: _PS3 and its power resources.
|
||||
bool d3cold = false;
|
||||
int shared = 0;
|
||||
if (gpuNode >= 0) {
|
||||
AcpiPower::D3ColdReport r{};
|
||||
AcpiPower::EnterD3Cold(gpuNode, owners, ownerCount, r);
|
||||
if (r.ResourcesOff > 0 && r.ResourcesStillOn == 0) d3cold = true;
|
||||
shared += r.ResourcesShared;
|
||||
|
||||
// Older Optimus firmware hangs the power switch on a _OFF method
|
||||
// of the GPU device itself and only calls it from _PS3 when the
|
||||
// driver's flags say so. Call it directly if the card is still
|
||||
// answering.
|
||||
if (Present(card.Bus, card.Dev, card.DisplayFn) && !portHasPr3) {
|
||||
int32_t off = AML::GetInterpreter().FindChild(gpuNode, "_OFF");
|
||||
auto* n = AML::GetInterpreter().GetNamespace().GetNode(off);
|
||||
if (n && n->Obj.Type == AML::ObjectType::Method) {
|
||||
AML::Object result{};
|
||||
AML::GetInterpreter().EvaluateNode(off, nullptr, 0, result);
|
||||
KernelLogStream(INFO, "dGPU") << " ran the GPU's _OFF";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Present(card.Bus, card.Dev, card.DisplayFn)) d3cold = true;
|
||||
|
||||
// 7. The ports above it, nearest first. A switch port belongs to the
|
||||
// card outright; a root port only goes down when the platform says
|
||||
// it can (power resources or _PS3), as Linux requires.
|
||||
for (int i = 0; i < card.ChainCount; i++) {
|
||||
const PciAddr& p = card.Chain[i];
|
||||
int32_t node = chainNodes[i];
|
||||
bool rootPort = PciePortType(p.Bus, p.Dev, p.Fn) == PortTypeRootPort;
|
||||
bool acpiPm = node >= 0 && (AcpiPower::HasObject(node, "_PR3") ||
|
||||
AcpiPower::HasObject(node, "_PR0") ||
|
||||
AcpiPower::HasObject(node, "_PS3"));
|
||||
if (rootPort && !acpiPm) break;
|
||||
|
||||
Pci::SetPowerState(p.Bus, p.Dev, p.Fn, 3);
|
||||
if (node >= 0) {
|
||||
AcpiPower::D3ColdReport r{};
|
||||
AcpiPower::EnterD3Cold(node, owners, ownerCount, r);
|
||||
if (r.ResourcesOff > 0 && r.ResourcesStillOn == 0) d3cold = true;
|
||||
shared += r.ResourcesShared;
|
||||
}
|
||||
if (rootPort) break;
|
||||
}
|
||||
|
||||
KernelLogStream log(d3cold || d3hot ? OK : WARNING, "dGPU");
|
||||
LogAddr(log, card.Bus, card.Dev, card.DisplayFn);
|
||||
if (d3cold)
|
||||
log << " is powered off (D3cold)";
|
||||
else if (d3hot && shared)
|
||||
log << " is in D3hot; its power rail also feeds another device, so it stays on";
|
||||
else if (d3hot)
|
||||
log << " is in D3hot; the platform offered no way to cut its power";
|
||||
else
|
||||
log << " could not be powered down (no PCI or ACPI power management)";
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Public API
|
||||
// =========================================================================
|
||||
|
||||
void PowerDownUnused() {
|
||||
auto& devices = Pci::GetDevices();
|
||||
g_cardCount = 0;
|
||||
|
||||
for (uint64_t i = 0; i < devices.size() && g_cardCount < MaxCards; i++) {
|
||||
const auto& d = devices[i];
|
||||
if (d.ClassCode != 0x03 || d.Reserved) continue;
|
||||
|
||||
Card card;
|
||||
if (!Qualifies(d, card)) continue;
|
||||
|
||||
// Withhold all of its functions from the driver probe that
|
||||
// follows: the xHCI driver would otherwise claim the card's
|
||||
// USB-C controller and lose it when the card powers off.
|
||||
for (uint64_t k = 0; k < devices.size(); k++) {
|
||||
if (devices[k].Bus == card.Bus && devices[k].Device == card.Dev)
|
||||
devices[k].Reserved = true;
|
||||
}
|
||||
|
||||
g_cards[g_cardCount++] = card;
|
||||
PowerDownCard(card);
|
||||
}
|
||||
}
|
||||
|
||||
void Resume() {
|
||||
for (int i = 0; i < g_cardCount; i++) {
|
||||
KernelLogStream(INFO, "dGPU") << "Re-applying power-down after resume";
|
||||
PowerDownCard(g_cards[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* DgpuPower.hpp
|
||||
* Powers down discrete GPUs that no MontaukOS driver runs
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::Graphics::DgpuPower {
|
||||
|
||||
// A hybrid-graphics laptop boots with its discrete GPU (NVIDIA Optimus,
|
||||
// AMD PowerXpress / hybrid, Intel Arc) powered and idling at boot clocks.
|
||||
// With no driver to manage it, it stays there: several watts of heat and
|
||||
// a fan that never settles. This finds every display controller that
|
||||
// nothing claimed and that does not carry the boot display, takes its
|
||||
// whole card (GPU, HDMI audio, USB-C controller) away from the driver
|
||||
// probe, and powers it down: PCI D3hot, then the platform's D3cold path
|
||||
// through ACPI (_PR3 power resources on the port above it, _PS3, and the
|
||||
// NVIDIA Optimus / legacy and AMD ATPX vendor methods where firmware
|
||||
// relies on them).
|
||||
//
|
||||
// Call after the early (graphics) driver probe and before the normal
|
||||
// one, with the AML namespace initialized.
|
||||
void PowerDownUnused();
|
||||
|
||||
// Firmware repowers the card during S3 resume. Call once the platform is
|
||||
// back up to put every card powered down at boot back to sleep.
|
||||
void Resume();
|
||||
|
||||
// Number of cards powered down (for diagnostics).
|
||||
int PoweredDownCount();
|
||||
};
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Init.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/Graphics/DgpuPower.hpp>
|
||||
#include <Drivers/Net/E1000.hpp>
|
||||
#include <Drivers/Net/E1000E.hpp>
|
||||
#include <Drivers/Net/Wifi/Wifi.hpp>
|
||||
@@ -222,6 +223,10 @@ namespace Drivers {
|
||||
}
|
||||
}
|
||||
|
||||
void PowerDownUnusedGpus() {
|
||||
Graphics::DgpuPower::PowerDownUnused();
|
||||
}
|
||||
|
||||
void ProbeNormal() {
|
||||
Pci::ProbeAll(g_driverTable, g_driverTableCount, Pci::ProbePhase::Normal);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ namespace Drivers {
|
||||
// Post-probe: wire up GPU framebuffer to cursor subsystem.
|
||||
void InitializeGraphics();
|
||||
|
||||
// Power down discrete GPUs no driver claimed (hybrid-graphics laptops),
|
||||
// withholding their functions from the Normal-phase probe. Run between
|
||||
// ProbeEarly() and ProbeNormal(), once the timer is running.
|
||||
void PowerDownUnusedGpus();
|
||||
|
||||
// Probe PCI devices for Normal-phase drivers (xHCI, E1000, E1000E, AHCI, NVMe, IntelHDA).
|
||||
// USB class drivers (Bluetooth, etc.) initialize automatically during xHCI enumeration.
|
||||
void ProbeNormal();
|
||||
|
||||
@@ -213,6 +213,10 @@ namespace Memory::VMM {
|
||||
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]);
|
||||
|
||||
SetLeafPte(pageEntry, physicalAddress, false, true, true);
|
||||
|
||||
// The page may have been mapped before with other cache attributes
|
||||
// (an HHDM page remapped uncached): drop any stale translation.
|
||||
asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory");
|
||||
}
|
||||
|
||||
void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
|
||||
+93
-1
@@ -11,6 +11,7 @@
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -246,6 +247,54 @@ namespace Pci {
|
||||
return LegacyRead8(bus, device, function, (uint8_t)offset);
|
||||
}
|
||||
|
||||
static bool EcamCovers(uint8_t bus) {
|
||||
return g_useEcam && bus >= g_ecamStartBus && bus <= g_ecamEndBus;
|
||||
}
|
||||
|
||||
uint32_t ConfigRead(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width) {
|
||||
if (device > 31 || function > 7 || offset > 0xFFF || (offset & (width - 1)))
|
||||
return 0xFFFFFFFF;
|
||||
|
||||
if (EcamCovers(bus)) {
|
||||
switch (width) {
|
||||
case 1: return EcamRead8(g_ecamBase, bus, device, function, offset);
|
||||
case 2: return EcamRead16(g_ecamBase, bus, device, function, offset);
|
||||
case 4: return EcamRead32(g_ecamBase, bus, device, function, offset);
|
||||
default: return 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset > 0xFF) return 0xFFFFFFFF;
|
||||
switch (width) {
|
||||
case 1: return LegacyRead8(bus, device, function, (uint8_t)offset);
|
||||
case 2: return LegacyRead16(bus, device, function, (uint8_t)offset);
|
||||
case 4: return LegacyRead32(bus, device, function, (uint8_t)offset);
|
||||
default: return 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigWrite(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width, uint32_t value) {
|
||||
if (device > 31 || function > 7 || offset > 0xFFF || (offset & (width - 1)))
|
||||
return false;
|
||||
|
||||
if (EcamCovers(bus)) {
|
||||
switch (width) {
|
||||
case 1: EcamWrite8(g_ecamBase, bus, device, function, offset, (uint8_t)value); return true;
|
||||
case 2: EcamWrite16(g_ecamBase, bus, device, function, offset, (uint16_t)value); return true;
|
||||
case 4: EcamWrite32(g_ecamBase, bus, device, function, offset, value); return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset > 0xFF) return false;
|
||||
switch (width) {
|
||||
case 1: LegacyWrite8(bus, device, function, (uint8_t)offset, (uint8_t)value); return true;
|
||||
case 2: LegacyWrite16(bus, device, function, (uint8_t)offset, (uint16_t)value); return true;
|
||||
case 4: LegacyWrite32(bus, device, function, (uint8_t)offset, value); return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PCI capability list traversal
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -493,6 +542,46 @@ namespace Pci {
|
||||
LegacyWrite16(bus, device, function, (uint8_t)PCI_REG_COMMAND, cmd);
|
||||
}
|
||||
|
||||
bool IsBridge(const PciDevice& dev) {
|
||||
return (dev.HeaderType & 0x7F) == 0x01;
|
||||
}
|
||||
|
||||
// PMCSR lives at PM capability + 4; its low two bits are the D-state.
|
||||
static constexpr uint16_t PmcsrOffset = 4;
|
||||
|
||||
int GetPowerState(uint8_t bus, uint8_t device, uint8_t function) {
|
||||
if (ReadConfig16(bus, device, function, RegVendorId) == 0xFFFF) return -1;
|
||||
uint8_t pm = FindCapability(bus, device, function, PCI_CAP_PM);
|
||||
if (pm == 0) return -1;
|
||||
return (int)(ReadConfig16(bus, device, function, pm + PmcsrOffset) & 0x3);
|
||||
}
|
||||
|
||||
bool SetPowerState(uint8_t bus, uint8_t device, uint8_t function, int state) {
|
||||
if (state < 0 || state > 3) return false;
|
||||
uint8_t pm = FindCapability(bus, device, function, PCI_CAP_PM);
|
||||
if (pm == 0) return false;
|
||||
|
||||
// D1/D2 are optional; PMC bits 25/26 advertise them.
|
||||
uint32_t pmc = ConfigRead(bus, device, function, pm, 4);
|
||||
if (state == 1 && !(pmc & (1u << 25))) return false;
|
||||
if (state == 2 && !(pmc & (1u << 26))) return false;
|
||||
|
||||
uint16_t pmcsr = (uint16_t)ConfigRead(bus, device, function, pm + PmcsrOffset, 2);
|
||||
int current = pmcsr & 0x3;
|
||||
if (current == state) return true;
|
||||
|
||||
// Keep PME_Status (bit 15, write-1-to-clear) from being cleared by
|
||||
// accident; only the state field changes.
|
||||
pmcsr = (uint16_t)((pmcsr & ~0x8003u) | (uint16_t)state);
|
||||
ConfigWrite(bus, device, function, pm + PmcsrOffset, 2, pmcsr);
|
||||
|
||||
// PCI PM 1.2 table 5-6: 10 ms into or out of D3hot, 200 us for D2.
|
||||
if (state == 3 || current == 3) Timekeeping::BusyWaitMs(10);
|
||||
else if (state == 2 || current == 2) Timekeeping::BusyWaitUs(200);
|
||||
|
||||
return GetPowerState(bus, device, function) == state;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PCI driver matching
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -523,7 +612,9 @@ namespace Pci {
|
||||
|
||||
void ProbeAll(const PciDriverDesc* table, uint16_t count, ProbePhase phase) {
|
||||
for (uint64_t d = 0; d < g_devices.size(); d++) {
|
||||
const PciDevice& dev = g_devices[d];
|
||||
PciDevice& dev = g_devices[d];
|
||||
if (dev.Claimed || dev.Reserved)
|
||||
continue;
|
||||
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
if (table[i].Phase != phase)
|
||||
@@ -537,6 +628,7 @@ namespace Pci {
|
||||
<< "." << (uint64_t)dev.Function;
|
||||
|
||||
if (table[i].Probe(dev)) {
|
||||
dev.Claimed = true;
|
||||
KernelLogStream(OK, "PCI") << table[i].Name << " claimed device "
|
||||
<< base::hex << (uint64_t)dev.Bus << ":"
|
||||
<< (uint64_t)dev.Device << "." << (uint64_t)dev.Function;
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace Pci {
|
||||
uint8_t ProgIf;
|
||||
uint8_t RevisionId;
|
||||
uint8_t HeaderType;
|
||||
|
||||
bool Claimed; // a driver's Probe() accepted it
|
||||
bool Reserved; // withheld from driver probing (e.g. powered down)
|
||||
};
|
||||
|
||||
// Configuration space access (ECAM / memory-mapped)
|
||||
@@ -60,8 +63,18 @@ namespace Pci {
|
||||
void LegacyWrite16(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint16_t value);
|
||||
void LegacyWrite32(uint8_t bus, uint8_t device, uint8_t function, uint8_t offset, uint32_t value);
|
||||
|
||||
// Configuration access by whichever mechanism reaches the register:
|
||||
// ECAM when the bus is covered by MCFG (required for offsets >= 0x100),
|
||||
// the legacy CF8/CFC ports otherwise. width is 1, 2 or 4 and offset must
|
||||
// be aligned to it. Reads of an unreachable register return all ones;
|
||||
// writes to one return false.
|
||||
uint32_t ConfigRead(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width);
|
||||
bool ConfigWrite(uint8_t bus, uint8_t device, uint8_t function, uint16_t offset, uint8_t width, uint32_t value);
|
||||
|
||||
// PCI capability IDs
|
||||
constexpr uint8_t PCI_CAP_PM = 0x01;
|
||||
constexpr uint8_t PCI_CAP_MSI = 0x05;
|
||||
constexpr uint8_t PCI_CAP_PCIE = 0x10;
|
||||
constexpr uint8_t PCI_CAP_MSIX = 0x11;
|
||||
|
||||
// Walk the PCI capability linked list for a given device.
|
||||
@@ -97,6 +110,30 @@ namespace Pci {
|
||||
// Enable memory space access and bus mastering in PCI command register.
|
||||
void EnableBusMaster(uint8_t bus, uint8_t device, uint8_t function);
|
||||
|
||||
// Type 1 (PCI-to-PCI bridge) header registers
|
||||
constexpr uint16_t PCI_REG_PRIMARY_BUS = 0x18;
|
||||
constexpr uint16_t PCI_REG_SECONDARY_BUS = 0x19;
|
||||
constexpr uint16_t PCI_REG_SUBORDINATE_BUS = 0x1A;
|
||||
constexpr uint16_t PCI_REG_MEM_BASE = 0x20;
|
||||
constexpr uint16_t PCI_REG_PREF_BASE = 0x24;
|
||||
constexpr uint16_t PCI_REG_PREF_BASE_UPPER = 0x28;
|
||||
constexpr uint16_t PCI_REG_PREF_LIMIT_UPPER = 0x2C;
|
||||
constexpr uint16_t PCI_REG_BRIDGE_CONTROL = 0x3E;
|
||||
constexpr uint16_t PCI_BRIDGE_CTL_VGA = (1 << 3);
|
||||
|
||||
// True for a function with a type 1 (PCI-to-PCI bridge) header.
|
||||
bool IsBridge(const PciDevice& dev);
|
||||
|
||||
// The PCI power state of a function from its PM capability: 0..3 for
|
||||
// D0..D3hot, -1 when it has no PM capability or does not answer (which
|
||||
// is also what a function in D3cold looks like).
|
||||
int GetPowerState(uint8_t bus, uint8_t device, uint8_t function);
|
||||
|
||||
// Move a function to D0..D3hot through PMCSR and wait out the transition
|
||||
// recovery time the PCI PM spec requires. Returns false when the
|
||||
// function has no PM capability or did not take the state.
|
||||
bool SetPowerState(uint8_t bus, uint8_t device, uint8_t function, int state);
|
||||
|
||||
// Disable legacy INTx plus any enabled MSI/MSI-X capability. Returns true
|
||||
// when message-signalled delivery had been enabled and was turned off.
|
||||
bool DisableInterruptDelivery(uint8_t bus, uint8_t device, uint8_t function);
|
||||
|
||||
@@ -365,6 +365,35 @@ namespace Timekeeping {
|
||||
Hal::HaltWithInterruptsDisabled();
|
||||
}
|
||||
|
||||
void BusyWaitUs(uint64_t us) {
|
||||
// An I/O write to port 0x80 is an ISA-bus cycle of roughly a
|
||||
// microsecond on every chipset since the 486, and never shorter.
|
||||
for (uint64_t i = 0; i < us; i++)
|
||||
Io::IoPortWait();
|
||||
}
|
||||
|
||||
void BusyWaitMs(uint64_t ms) {
|
||||
if (ms == 0) return;
|
||||
|
||||
uint64_t flags;
|
||||
asm volatile("pushfq; pop %0" : "=r"(flags));
|
||||
bool ticking = g_ticksPerMs != 0 && (flags & (1 << 9));
|
||||
|
||||
// Either clock alone is long enough, so stop at whichever says so
|
||||
// first. The tick is the accurate one; the port writes bound the wait
|
||||
// when the tick is not running yet (or is masked), so this can never
|
||||
// hang on a timer that does not fire. One extra tick of slack because
|
||||
// the first may already be nearly over.
|
||||
uint64_t start = g_tickCount.load(std::memory_order_relaxed);
|
||||
uint64_t portBudget = (ms + 1) * 1000;
|
||||
for (uint64_t spins = 0; spins < portBudget; spins++) {
|
||||
if (ticking &&
|
||||
g_tickCount.load(std::memory_order_relaxed) - start >= ms + 1)
|
||||
return;
|
||||
Io::IoPortWait();
|
||||
}
|
||||
}
|
||||
|
||||
void Sleep(uint64_t ms) {
|
||||
if (ms == 0) return;
|
||||
// Use BlockForSleep to properly deschedule the process instead
|
||||
|
||||
@@ -43,4 +43,12 @@ namespace Timekeeping {
|
||||
|
||||
// Busy-wait sleep for the given number of milliseconds
|
||||
void Sleep(uint64_t ms);
|
||||
|
||||
// Spin for at least `ms` milliseconds without descheduling. Usable from
|
||||
// boot code, before the scheduler runs, and with interrupts disabled
|
||||
// (falls back to timed I/O port writes when the tick is not advancing).
|
||||
void BusyWaitMs(uint64_t ms);
|
||||
|
||||
// Spin for at least `us` microseconds. Port-0x80 timed, so it errs long.
|
||||
void BusyWaitUs(uint64_t us);
|
||||
};
|
||||
Reference in new issue
Block a user