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

+93 -1
View File
@@ -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;
+37
View File
@@ -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);