5.0 KiB
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.