feat: HWP scaling, C1E, closed-loop thermal governor for Intel CPUs
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include <Hal/Apic/IoApic.hpp>
|
||||
#include <Hal/Apic/Pic.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Graphics/Framebuffer.hpp>
|
||||
@@ -435,6 +436,9 @@ namespace Hal {
|
||||
}
|
||||
reinitProgress(0x04); // PAT
|
||||
Hal::InitializePAT();
|
||||
// HWP enable and POWER_CTL reset across S3; re-apply them and
|
||||
// bump the policy epoch so APs re-apply on their next tick.
|
||||
Hal::CpuPower::ReapplyAfterWake();
|
||||
reinitProgress(0x05); // PIC
|
||||
Hal::DisableLegacyPic();
|
||||
reinitProgress(0x06); // Local APIC
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <ACPI/FADT.hpp>
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -30,6 +31,7 @@ namespace Hal {
|
||||
IdleEntryKind EntryKind = IdleEntryKind::FixedHardware;
|
||||
uint16_t IoPort = 0;
|
||||
uint8_t WidthBits = 0;
|
||||
uint8_t MwaitHint = 0; // FFH entries: MWAIT hint from GAS address
|
||||
};
|
||||
|
||||
static constexpr int MaxIdleStates = 8;
|
||||
@@ -180,6 +182,9 @@ namespace Hal {
|
||||
|
||||
if (addressSpace == GAS_FIXED_HARDWARE) {
|
||||
state.EntryKind = IdleEntryKind::FixedHardware;
|
||||
// Intel FFH convention: the GAS address low byte carries the
|
||||
// MWAIT hint for this C-state (same as Linux acpi_idle).
|
||||
state.MwaitHint = (uint8_t)(address & 0xFF);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -382,10 +387,81 @@ namespace Hal {
|
||||
return best;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Deep MWAIT idle for APs
|
||||
//
|
||||
// The BSP must stay in C1 (HLT / MWAIT hint 0): on some laptops
|
||||
// deep MWAIT states freeze the LAPIC timer even though ARAT is
|
||||
// advertised, and the BSP's periodic 1 ms tick is the system
|
||||
// timekeeper (see ApicTimer::IdleOnce). APs have no such duty:
|
||||
// an idle AP is woken by the reschedule IPI, and its coarse tick
|
||||
// simply resumes on wake. Deep core C-states (C6) power-gate the
|
||||
// core entirely, which is where most of the idle-heat win lives
|
||||
// on many-core parts.
|
||||
// ============================================================
|
||||
|
||||
static constexpr uint8_t MaxApMwaitHint = 0x2F; // cap at the C6 family
|
||||
static uint8_t g_deepApHint = 0;
|
||||
|
||||
// CPUID.05H:EDX enumerates the number of MWAIT sub-states per
|
||||
// C-state group; the group index for hint h is (h >> 4) + 1.
|
||||
static bool MwaitHintSupported(uint8_t hint) {
|
||||
uint32_t a, b, c, d;
|
||||
asm volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d)
|
||||
: "a"(0), "c"(0));
|
||||
if (a < 5) return false;
|
||||
asm volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d)
|
||||
: "a"(5), "c"(0));
|
||||
if ((c & 1) == 0) return hint == 0; // extensions not enumerated
|
||||
uint32_t group = ((uint32_t)(hint >> 4) & 0xF) + 1;
|
||||
uint32_t sub = hint & 0xF;
|
||||
uint32_t count = (d >> (group * 4)) & 0xF;
|
||||
return sub < count;
|
||||
}
|
||||
|
||||
static void ComputeDeepApHint() {
|
||||
g_deepApHint = 0;
|
||||
if (!Hal::HasMwait()) return;
|
||||
|
||||
// Prefer firmware-blessed hints from _CST FFH entries.
|
||||
uint8_t best = 0;
|
||||
for (int i = 0; i < g_idleStateCount; i++) {
|
||||
const auto& state = g_idleStates[i];
|
||||
if (!state.Valid || state.EntryKind != IdleEntryKind::FixedHardware) continue;
|
||||
if (state.MwaitHint == 0 || state.MwaitHint > MaxApMwaitHint) continue;
|
||||
if (!MwaitHintSupported(state.MwaitHint)) continue;
|
||||
if (state.MwaitHint > best) best = state.MwaitHint;
|
||||
}
|
||||
|
||||
// No usable _CST data: derive from CPUID alone (deepest core
|
||||
// state, capped at C6).
|
||||
if (best == 0) {
|
||||
static constexpr uint8_t candidates[] = { 0x20, 0x10, 0x01 };
|
||||
for (uint8_t hint : candidates) {
|
||||
if (MwaitHintSupported(hint)) { best = hint; break; }
|
||||
}
|
||||
}
|
||||
|
||||
g_deepApHint = best;
|
||||
if (g_deepApHint != 0) {
|
||||
KernelLogStream(OK, "CpuIdle") << "AP deep idle: MWAIT hint 0x"
|
||||
<< base::hex << (uint64_t)g_deepApHint;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t CurrentIdleHint() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
return (cpu != nullptr && cpu->cpuIndex != 0) ? g_deepApHint : 0;
|
||||
}
|
||||
|
||||
uint8_t GetApIdleHint() {
|
||||
return g_deepApHint;
|
||||
}
|
||||
|
||||
static void FallbackWait(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||
volatile uint64_t* addr = (monitorAddr != nullptr) ? monitorAddr : &g_fallbackMonitor;
|
||||
if (hasMwait) {
|
||||
Hal::IdleWait(addr);
|
||||
Hal::IdleWait(addr, CurrentIdleHint());
|
||||
} else {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
@@ -395,7 +471,7 @@ namespace Hal {
|
||||
volatile uint64_t* monitorAddr) {
|
||||
volatile uint64_t* addr = (monitorAddr != nullptr) ? monitorAddr : &g_fallbackMonitor;
|
||||
if (hasMwait) {
|
||||
Hal::IdleWaitWithInterruptsDisabled(addr);
|
||||
Hal::IdleWaitWithInterruptsDisabled(addr, CurrentIdleHint());
|
||||
} else {
|
||||
Hal::HaltWithInterruptsDisabled();
|
||||
}
|
||||
@@ -424,7 +500,7 @@ namespace Hal {
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize(ACPI::CommonSDTHeader* xsdt) {
|
||||
static void LoadIdleStates(ACPI::CommonSDTHeader* xsdt) {
|
||||
g_idleStateCount = 0;
|
||||
for (int i = 0; i < MaxIdleStates; i++) {
|
||||
g_idleStates[i] = {};
|
||||
@@ -471,6 +547,11 @@ namespace Hal {
|
||||
<< "ACPI _CST unavailable or unsupported - using safe MWAIT/HLT idle fallback";
|
||||
}
|
||||
|
||||
void Initialize(ACPI::CommonSDTHeader* xsdt) {
|
||||
LoadIdleStates(xsdt);
|
||||
ComputeDeepApHint();
|
||||
}
|
||||
|
||||
void Wait(uint32_t predictedIdleMs, bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||
const IdleState* state = SelectIdleState(predictedIdleMs, hasMwait);
|
||||
if (state == nullptr) {
|
||||
|
||||
@@ -29,5 +29,9 @@ namespace Hal {
|
||||
void WaitShallowWithInterruptsDisabled(bool hasMwait,
|
||||
volatile uint64_t* monitorAddr = nullptr);
|
||||
|
||||
// The MWAIT hint APs use for deep idle (0 = plain C1). Exposed for
|
||||
// SYS_POWERINFO diagnostics.
|
||||
uint8_t GetApIdleHint();
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user