feat: power down dGPUs on hybrid graphics laptops
This commit is contained in:
1 parent
7f12e1a150
commit
24f88ec4c6
23 files changed
+4325
-1130
No files matched your search
+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);
|
||||
|
||||
Reference in new issue
Block a user