I fixed it. Here is my diagnosis in case it helps anyone else who finds themselves in a similar predicament. Disclosure: I generated this report with the AI I was using to help with the diagnosis, with only light post-editing, which is why it reads weird. But I have spent too long on this already, so this is what you get 
TL;DR
I traced my recurring 400 MHz lock to a failing heatsink/fan assembly that caused the system to peg at 100°C while drawing only ~17 W. This eventually triggers a platform-level (i.e. external to the CPU) PROCHOT condition that latches and does not clear after the system cools. Replacing the heatsink/fan assembly with a spare I had lying around fixed the issue because the system can no longer be forced into the pathological thermal state that triggers the latch.
Diagnosis of recurring 400 MHz lock on 12th-gen Framework: failed cooling assembly triggering latched external PROCHOT
I think I have finally diagnosed the recurring 400 MHz throttling problem on my 12th-gen Framework.
Symptoms
The failure mode was:
- All CPU cores became locked at approximately 400 MHz regardless of load.
- CPU frequency policy still allowed the normal maximum frequency.
- Temperatures would fall after the workload stopped, but the 400 MHz limit remained.
- Suspend, including deep suspend, did not clear it.
- Normal reboot is insufficient.
- Shutting down and removing external power for several minutes cleared the condition.
- It would eventually recur, particularly after combined CPU + GPU workloads that kept the fans running hard for an extended period.
For reference, while locked:
grep . /sys/devices/system/cpu/cpu0/cpufreq/{scaling_driver,scaling_governor,scaling_cur_freq,scaling_max_freq,cpuinfo_max_freq}
gave:
scaling_driver:intel_pstate
scaling_governor:powersave
scaling_cur_freq:399841
scaling_max_freq:4400000
cpuinfo_max_freq:4400000
So Linux was not imposing a 400 MHz frequency limit. The requested maximum was still 4.4 GHz.
Reproducing and monitoring the problem
I used turbostat to monitor CPU, GPU, temperatures and package power:
sudo turbostat --quiet --Summary --interval 1 \
--show Busy%,Bzy_MHz,CoreTmp,CoreThr,PkgTmp,GFX%C0,PkgWatt,CorWatt,GFXWatt,PKG_%
For GPU load I used:
glmark2-wayland \
--off-screen \
--size 7680x4320 \
--frame-end finish \
--run-forever \
-b 'refract:duration=60'
and added CPU load with stress-ng, for example:
stress-ng --cpu 8
After replacing the cooling assembly I went as far as:
stress-ng --cpu 16
while simultaneously saturating the iGPU.
The important clue: 100°C at only ~17 W
With the original heatsink/fan assembly, a combined CPU+iGPU load produced this:
Busy% Bzy_MHz CoreTmp PkgTmp GFX%C0 PkgWatt CorWatt GFXWatt
50.66 1452 99 100 100.51 17.30 7.89 5.38
51.66 1467 99 100 100.44 17.48 7.97 5.46
52.11 1444 99 100 100.89 17.69 7.99 5.62
52.03 1438 100 100 100.68 17.41 7.87 5.38
51.69 1398 100 100 100.34 17.17 7.61 5.44
CoreThr was also increasing rapidly during this state.
This was the clue I initially missed: the processor was sitting at its 100°C thermal limit while dissipating only about 17 W. That is extremely poor cooling performance for this system.
Identifying what was causing the 400 MHz lock
I installed msr-tools and loaded the MSR driver:
sudo apt install msr-tools
sudo modprobe msr
The most useful register was Intel’s package thermal-status MSR:
sudo rdmsr -p 0 0x1b1
This is IA32_PACKAGE_THERM_STATUS.
To watch the low four status bits continuously:
sudo watch -n 0.5 '
v=$(rdmsr -p 0 0x1b1)
printf "THERM_STATUS=0x%s low=0x%x\n" "$v" "$((0x$v & 15))"
'
Intel documents/interprets the low bits as:
bit 0 internal thermal status
bit 1 internal thermal status log
bit 2 external PROCHOT status
bit 3 external PROCHOT log
So useful low-nibble values are:
0x0 no internal or external thermal condition indicated
0x1 internal thermal control currently active
0x2 internal thermal event logged
0x3 internal thermal control active + logged
0x4 external PROCHOT currently asserted
0x8 external PROCHOT event logged
0xc external PROCHOT currently asserted + logged
Intel specifically recommends using IA32_PACKAGE_THERM_STATUS to distinguish internal thermal throttling from an externally asserted PROCHOT condition; bits 0-1 describe the internal thermal sensors and bits 2-3 the external PROCHOT signal.
While the processor was genuinely at approximately 100°C, I saw the low nibble oscillating through values such as 0x0, 0x2 and 0x3, consistent with ordinary internal thermal control engaging and disengaging around the thermal limit.
But once the pathological 400 MHz state occurred, I read:
IA32_PACKAGE_THERM_STATUS = 0x881c080c
The key part is the final 0xc.
That means the internal thermal-status bits 0 and 1 were clear, while the external PROCHOT status and log bits 2 and 3 were set.
This was particularly significant because by then the processor had already cooled:
Busy% Bzy_MHz CoreTmp PkgTmp GFX%C0 PkgWatt
50.99 400 79 79 100.64 4.93
56.47 400 79 78 100.60 5.11
55.55 400 79 79 100.54 5.07
53.85 400 79 79 100.11 5.00
52.96 400 78 78 101.10 5.11
So the CPU was no longer thermally hot and package power was only about 5 W, yet the platform was still asserting external PROCHOT and forcing the processor to its minimum frequency.
That explains why changing Linux governors, frequency limits, etc. did nothing.
Checking package power limits
I also checked Intel RAPL:
rapl=/sys/class/powercap/intel-rapl:0
for i in 0 1; do
printf 'constraint %s: ' "$i"
cat "$rapl/constraint_${i}_name"
printf 'power: '
cat "$rapl/constraint_${i}_power_limit_uw"
printf 'window: '
cat "$rapl/constraint_${i}_time_window_us"
done
Before I started experimenting with the limits, this reported:
constraint 0: long_term
power: 200000000
window: 27983872
constraint 1: short_term
power: 64000000
window: 2440
I also checked the processor’s advertised long-term thermal-spec power:
cat /sys/class/powercap/intel-rapl:0/constraint_0_max_power_uw
which returned:
28000000
or 28 W.
For lower-level inspection, the relevant RAPL MSRs can be read with:
sudo rdmsr -p 0 0x606 # MSR_RAPL_POWER_UNIT
sudo rdmsr -p 0 0x610 # MSR_PKG_POWER_LIMIT
sudo rdmsr -p 0 0x614 # MSR_PKG_POWER_INFO
At one point, after I had manually configured PL1=20 W and PL2=64 W, I obtained:
0x606: a0e03
0x610: 43820000dd80a0
0x614: e0
0x606 gives a power unit of 1/8 W, so the 0xe0 thermal-spec power field in 0x614 corresponds to:
224 × 0.125 W = 28 W
MSR_PKG_POWER_LIMIT contains the PL1 and PL2 power limits, enable/clamp fields and their respective time-window encodings. Intel documents PL1 in bits 14:0 and PL2 in bits 46:32, with their enable, clamp and time-window fields adjacent.
I experimented with lower RAPL limits and found that reducing package power could prevent the problem during ordinary workloads. However, that turned out to be treating the symptom rather than the cause: it was while messing around with the RAPL limits that I noticed the package temperature at 100°C while consuming only ~17 W, and realized the cooling was borked.
Replacing the heatsink/fan assembly
I had a vague recollection of buying a spare heatsink/fan during one of framework’s clearance sales a while back. After finding it in the spare PC parts bin, I replaced the complete heatsink/fan assembly.
That completely changed the behavior.
Under combined CPU+iGPU stress, the system can now dissipate roughly 30-36 W while remaining substantially below 100°C for most of the test:
Busy% Bzy_MHz CoreTmp PkgTmp GFX%C0 PkgWatt CorWatt GFXWatt
99.77 2519 85 85 100.58 36.18 25.75 6.69
99.77 2523 89 89 100.57 36.17 26.00 6.41
99.77 2528 92 92 100.57 36.18 25.91 6.49
99.77 2530 94 94 100.58 36.50 26.09 6.53
99.77 2097 92 92 100.57 30.05 20.53 5.71
Even with essentially 100% CPU load and 100% iGPU load, the machine remains functional.
There were occasional normal thermal-throttle events during the most extreme part of the test. For example, CoreThr increased when package temperature briefly reached the high 90s. That is qualitatively different from the old cooling system, where the machine continuously sat at 100°C at only ~17 W and accumulated thermal-throttle events rapidly. Intel’s normal hardware thermal monitor reduces processor power when the trip temperature is reached.
Most importantly, I have been unable to reproduce the permanent 400 MHz lock since replacing the cooling assembly, even using:
stress-ng --cpu 16
while simultaneously saturating the iGPU.
Working diagnosis
My current interpretation is:
failed/degraded heatsink, fan, TIM or heatpipe
|
v
very high thermal resistance
|
v
package reaches 100°C at only ~17 W
|
v
normal Intel internal thermal throttling engages repeatedly
|
v
continued severe thermal condition eventually causes some
platform-level protection mechanism to assert external PROCHOT
|
v
CPU and GPU are forced to their minimum performance state
|
v
external PROCHOT remains asserted even after the CPU cools
|
v
CPU remains locked at ~400 MHz until full power removal
The first part of this chain is now strongly supported experimentally: replacing the cooling assembly increased sustainable package dissipation from roughly 17 W at 100°C to roughly 30-36 W at lower temperatures under a substantially heavier workload.
The register evidence also establishes that the eventual 400 MHz state was not ordinary CPU thermal throttling. While locked and already cooled, IA32_PACKAGE_THERM_STATUS showed an active external PROCHOT assertion (...0c).
What I cannot determine from software is exactly which Framework component asserts that external signal, or why it remains asserted after the initiating thermal condition has disappeared. It could be EC/firmware behavior, VR protection, another board-level thermal sensor, or an intentionally latched hardware protection circuit.
So I would separate the two issues:
- Primary fault on my machine: the cooling assembly had degraded badly enough that the CPU package reached 100°C at only ~17 W.
- Secondary Framework/platform behavior: after sufficiently severe/prolonged thermal stress, external PROCHOT can apparently become asserted and remain latched, leaving the machine permanently at ~400 MHz until power is completely removed.
With a healthy cooling assembly, I can no longer reproduce the second condition because even an extreme CPU+iGPU workload does not put the machine into the same pathological thermal state.