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;
|
||||
|
||||
Reference in new issue
Block a user