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

+560
View File
@@ -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]);
}
}
};
+33
View File
@@ -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();
};
+5
View File
@@ -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);
}
+5
View File
@@ -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();