After resuming my TUXEDO laptop on battery, the CPU remained at 200 MHz under full load for 30 to 40 seconds. An external PROCHOT signal caused the throttling. A systemd sleep hook now starts a worker that disables the CPU's response to the signal temporarily and re-enables it after 45 seconds.
The machine is a TUXEDO InfinityBook Pro MK1 Gen7 with an Intel Core
i7-11370H. It runs Debian 13 (Trixie), the intel_pstate CPU frequency
driver and the TUXEDO/Uniwill drivers.
On battery, the TUXEDO power profile limits the CPU to a range of 900 MHz to 2.5 GHz. The configured 2.5 GHz maximum was therefore expected and unrelated to the problem.
model name : 11th Gen Intel(R) Core(TM) i7-11370H @ 3.30GHz scaling_min_freq:900000 scaling_max_freq:2500000 scaling_governor:powersave scaling_driver:intel_pstate intel_pstate/status:active intel_pstate/min_perf_pct:8 intel_pstate/max_perf_pct:100
The problem occurred only after suspending and resuming on battery. Resuming with the power supply connected worked normally.
Immediately after a battery resume the laptop was extremely slow. Reading
scaling_cur_freq showed about 200 MHz even though the configured minimum
frequency was 900 MHz:
scaling_cur_freq:200007 scaling_min_freq:900000 scaling_max_freq:2500000
CPU temperatures remained between 35 °C and 50 °C, which ruled out throttling caused by the CPU reaching its thermal limit.
turbostat confirmed that the cpufreq value reflected the effective
frequency. With the logical CPUs almost fully busy, Bzy_MHz remained at
200 MHz:
Avg_MHz Busy% Bzy_MHz CoreTmp CoreThr PkgWatt 199 99.60 200 36 0 5.41 199 99.58 200 35 0 5.41 199 99.53 200 199 99.40 200 35 0 200 99.77 200
After roughly 30 to 40 seconds the limit disappeared suddenly and the CPU jumped back to about 2.3 to 2.5 GHz:
Avg_MHz Busy% Bzy_MHz PkgWatt 199 99.72 200 5.48 199 99.38 200 198 98.95 200 1011 43.05 2345 10.91 918 39.75 2309 10.86
Connecting the power supply during the slow period released the limit immediately. The failure was therefore specific to the post-resume battery power state.
I then inspected these Intel model-specific registers (MSRs):
0x19c IA32_THERM_STATUS 0x1b1 IA32_PACKAGE_THERM_STATUS 0x1fc MSR_POWER_CTL 0x64f IA32_CORE_PERF_LIMIT_REASONS
The values changed reproducibly before suspend, during the 200 MHz period, and
after recovery. The relevant change was in IA32_THERM_STATUS:
before: ...08 during: ...0c after: ...08
Bit 2 was set only during the slow period. According to the
Intel
Software Developer's Manual, this bit reports a PROCHOT# or FORCEPR# signal
asserted by another platform agent. IA32_CORE_PERF_LIMIT_REASONS changed
at the same time:
before: 0x1010000 during: 0x10001 after: 0x1010000
The CPU remained cool and did not report an internal thermal event. Instead, the external signal forced it to its lowest P-state.
These measurements do not identify which component asserted the signal. Plausible sources include the embedded controller, voltage regulator, and battery power-path logic.
Reloading uniwill_wmi and restarting tccd did not clear
the condition:
modprobe -r uniwill_wmi modprobe uniwill_wmi systemctl restart tccd
Disabling bidirectional PROCHOT in MSR_POWER_CTL, however, released
the CPU immediately:
wrmsr -a 0x1fc 0x24005a
Re-enabling it while the external signal was still asserted immediately forced the CPU back to 200 MHz:
wrmsr -a 0x1fc 0x24005b
The response to bit 0 of MSR_POWER_CTL isolated the active external
PROCHOT signal as the cause. The intel_pstate limits and governor did not
cause the 200 MHz cap.
Bidirectional PROCHOT allows other platform components to throttle the CPU for
protection, so I did not want to disable it permanently. The workaround clears only
bit 0 of MSR_POWER_CTL for 45 seconds after a battery resume, then sets
the bit again.
Warning: This workaround temporarily disables a hardware protection input. Use it only after confirming the same MSR state and behavior on the affected machine. The register and bit definitions are processor-specific. If the worker is killed before its cleanup handler runs, verify the bit manually or reboot.
The script rewrites the register throughout those 45 seconds because firmware or System Management Mode (SMM) may set the bit again after resume. Before each write, it reads the current value and changes only the bidirectional PROCHOT enable bit.
The worker script is /usr/local/sbin/tuxedo-resume-prochot:
#!/bin/bash
set -eu
MSR=0x1fc
DURATION=45
INTERVAL=1
modprobe msr
restore_prochot()
{
for cpu in /dev/cpu/[0-9]*; do
[ -e "$cpu/msr" ] || continue
n=${cpu##*/cpu/}
cur=$(rdmsr -p "$n" "$MSR" 2>/dev/null) || continue
new=$(printf '%x' $((0x$cur | 1)))
wrmsr -p "$n" "$MSR" "$new" 2>/dev/null || true
done
}
trap restore_prochot EXIT INT TERM
# Only run on battery.
for f in /sys/class/power_supply/*/online; do
[ -e "$f" ] || continue
if [ "$(cat "$f" 2>/dev/null || echo 0)" = "1" ]; then
exit 0
fi
done
logger -t tuxedo-prochot \
"battery resume: forcing BD PROCHOT off for ${DURATION}s"
END=$(( $(date +%s) + DURATION ))
while [ "$(date +%s)" -lt "$END" ]; do
for cpu in /dev/cpu/[0-9]*; do
[ -e "$cpu/msr" ] || continue
n=${cpu##*/cpu/}
cur=$(rdmsr -p "$n" "$MSR")
new=$(printf '%x' $((0x$cur & ~1)))
wrmsr -p "$n" "$MSR" "$new"
done
sleep "$INTERVAL"
done
restore_prochot
v=$(rdmsr -p0 "$MSR")
logger -t tuxedo-prochot \
"45s elapsed; BD PROCHOT re-enabled: MSR=0x$v BD_PROCHOT=$((0x$v & 1))"
exit 0
A systemd sleep hook starts the script asynchronously, so the 45-second interval
does not block resume. The hook is
/usr/lib/systemd/system-sleep/tuxedo-prochot:
#!/bin/sh
case "$1" in
post)
systemd-run \
--quiet \
--collect \
--unit=tuxedo-resume-prochot \
/usr/local/sbin/tuxedo-resume-prochot
;;
esac
exit 0
Both files need to be executable:
chmod 755 /usr/local/sbin/tuxedo-resume-prochot chmod 755 /usr/lib/systemd/system-sleep/tuxedo-prochot
The final log now looks like this:
tuxedo-prochot: battery resume: forcing BD PROCHOT off for 45s tuxedo-prochot: 45s elapsed; BD PROCHOT re-enabled: MSR=0x5 BD_PROCHOT=1
A final check confirms that bidirectional PROCHOT is enabled again on all eight logical CPUs:
for n in {0..7}; do
v=$(rdmsr -p "$n" 0x1fc)
printf 'CPU%d: 0x%s BD_PROCHOT=%d\n' \
"$n" "$v" $((0x$v & 1))
done
CPU0: 0x5 BD_PROCHOT=1
CPU1: 0x5 BD_PROCHOT=1
CPU2: 0x5 BD_PROCHOT=1
CPU3: 0x5 BD_PROCHOT=1
CPU4: 0x5 BD_PROCHOT=1
CPU5: 0x5 BD_PROCHOT=1
CPU6: 0x5 BD_PROCHOT=1
CPU7: 0x5 BD_PROCHOT=1
After a battery resume, an external platform component asserted PROCHOT for 30 to
40 seconds and forced the i7-11370H to 200 MHz. The normal temperatures, MSR state,
and immediate response to both AC power and the bidirectional PROCHOT enable bit
distinguish this condition from an intel_pstate or governor problem.
The measurements point to a firmware, embedded-controller, voltage-regulator, or battery power-path defect, but do not identify the responsible component. Until that behavior is fixed, masking bidirectional PROCHOT for 45 seconds avoids the slowdown during the affected interval. The script then re-enables the protection.