feat: power down dGPUs on hybrid graphics laptops

This commit is contained in:
daniel committed 2026-10-01 13:09:26 +02:00
1 parent 7f12e1a150
commit 24f88ec4c6
23 files changed
+4325 -1130

No files matched your search

+132 -1
View File
@@ -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);
}
};
};