# CPU Power and Thermal Management MontaukOS manages CPU frequency, idle states, and package temperature on Intel hardware. Without this, a modern many-core laptop part (the reference target is a TUXEDO Gemini Gen2 with an i9-13900HX, 24 cores / 32 threads) runs at whatever P-state the firmware left it, never drops below C1 when idle, and heats until the hardware throttles at TjMax - or until the embedded controller intervenes (on Clevo boards: forced full-volume beeping, then a hard power-off). Everything below is feature-gated by CPUID, so virtual machines (KVM masks the power bits in CPUID.06H) and non-Intel CPUs skip it safely. ## Components ### HWP frequency scaling (`Hal/CpuPower.cpp`) `Hal::CpuPower::InitializeBsp()` enables Hardware P-states (IA32_PM_ENABLE), reads the performance range from IA32_HWP_CAPABILITIES, and programs IA32_HWP_REQUEST with: - min = lowest performance level, max = highest - desired = 0 (fully autonomous hardware frequency selection) - EPP = 0x80 (balanced; same as Linux "balance_performance") This is the same mechanism intel_pstate uses on Linux and is the single biggest thermal win: the CPU idles near its minimum frequency and ramps only while there is work. APs apply the same request in `ApEntry` via `InitializeAp()` (HWP request MSRs are per-logical-CPU). CPUs with EPB but no HWP get IA32_ENERGY_PERF_BIAS = 6 (balanced) instead. ### C1E promotion MSR_POWER_CTL bit 1 lets HLT-idle drop core voltage/frequency to minimum (C1E) instead of parking at the current ratio. Set per-core on the BSP and every AP. Gated on the DTS CPUID bit as a real-hardware heuristic, because MSR_POWER_CTL is not architectural and hypervisors may not emulate it. ### Thermal governor (`Hal/CpuPower.cpp`) A closed-loop passive-cooling controller on the BSP, stepped from `Sched::RunBspMaintenance()` (reached from both the idle loop and the timer tick, so it keeps running under full load). Every 500 ms it reads the package digital thermal sensor (IA32_PACKAGE_THERM_STATUS readout relative to TjMax from MSR_TEMPERATURE_TARGET) and adjusts the HWP performance ceiling: - **>= 85 C (passive):** step the ceiling down 2 ratio units (~200 MHz) - **>= 94 C (hot):** step down 8 units per interval - **<= 78 C (resume):** step back up 1 unit per interval - between resume and passive: hold (hysteresis) The ceiling propagates through a policy epoch counter: each CPU compares the epoch in `Sched::Tick()` (and the AP idle loop) against the last one it applied and rewrites its own IA32_HWP_REQUEST when it changed, since MSR writes only affect the executing core. APs tick every 10 ms, so a throttle decision reaches all cores within one tick. Throttle engage/release transitions are logged (WARNING/OK, "CpuPower"). ### Deep AP idle (`ACPI/CpuIdle.cpp`) APs use deep MWAIT C-states while idle; C6 power-gates the core entirely, which is where most of the idle-heat win lives on many-core parts. The hint comes from ACPI `_CST` FFH entries (the GAS address low byte is the MWAIT hint, same convention as Linux), validated against the CPUID.05H sub-state enumeration and capped at the C6 family (0x2F). With no usable `_CST`, the deepest CPUID-enumerated hint (up to C6) is used. **The BSP stays in C1 (HLT).** 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 the comment in `Timekeeping::IdleOnce`). APs have no timekeeping duty: an idle AP is woken by the reschedule IPI, and its coarse tick resumes on wake. ### S3 wake IA32_PM_ENABLE, HWP requests, and MSR_POWER_CTL do not survive suspend. `Hal::CpuPower::ReapplyAfterWake()` runs in the S3 resume path, redoes the BSP setup, and bumps the policy epoch so every AP re-applies its HWP request (and re-checks C1E) on its next tick. ## Diagnostics `SYS_POWERINFO` (149) fills a `PowerInfo` struct: package temperature, TjMax, HWP state and performance range, the governor's current ceiling, CPUID base/turbo frequency, the measured average active frequency (APERF/MPERF over the 500 ms governor window), and the AP idle MWAIT hint. Returns -1 when the CPU exposes no power features (e.g. under QEMU). The `power` program (`programs/src/power`) prints it: ``` power one-shot snapshot power watch 1 Hz samples for 60 s (temp, avg freq, ceiling) power watch 300 same, for 300 s ``` Boot log lines to look for (Kernel Log app): ``` [CpuPower] HWP enabled: perf range 4-54, EPP=0x80 [CpuPower] Thermal governor armed: TjMax=100C, passive=85C, hot=94C, ... [CpuIdle] AP deep idle: MWAIT hint 0x20 ``` ## Tuning The governor constants live at the top of `Hal/CpuPower.cpp` (`PassiveTempC`, `HotTempC`, `ResumeTempC`, `GovernorIntervalMs`, step sizes) and were chosen to keep the package well below both TjMax (100 C on the i9-13900HX) and the Clevo EC's emergency thresholds. If the fans still spin up aggressively under sustained load, lower `PassiveTempC` first; if builds feel throttled while cool, raise the recovery rate instead of the trip points.