<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammad Bilal</title>
    <description>The latest articles on DEV Community by Muhammad Bilal (@muhammad_bilal_linux).</description>
    <link>https://dev.to/muhammad_bilal_linux</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4059480%2F3617e77d-480d-4f88-b5ca-553e334dd506.jpg</url>
      <title>DEV Community: Muhammad Bilal</title>
      <link>https://dev.to/muhammad_bilal_linux</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammad_bilal_linux"/>
    <language>en</language>
    <item>
      <title>My Laptop's CPU stuck at Max Turbo 24/7 [ Here's Why ]</title>
      <dc:creator>Muhammad Bilal</dc:creator>
      <pubDate>Sun, 02 Aug 2026 20:11:09 +0000</pubDate>
      <link>https://dev.to/muhammad_bilal_linux/my-laptops-cpu-stuck-at-max-turbo-247-heres-why--427</link>
      <guid>https://dev.to/muhammad_bilal_linux/my-laptops-cpu-stuck-at-max-turbo-247-heres-why--427</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: My HP EliteBook 840 G2 was running its CPU at max turbo (2900 MHz) around the clock, even at idle, cooking itself at 83–88°C and throttling on the RAPL power limit over 115,000 times in two hours. I traced it into &lt;code&gt;intel_pstate.c&lt;/code&gt;, was fairly convinced I'd found a kernel bug, wrote up a whole Bugzilla report... and then the kernel documentation politely told me I hadn't. Here's the full investigation, the logs, the wrong turn, and the actual fix.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;I'd had this laptop running &lt;code&gt;tuned&lt;/code&gt;'s &lt;code&gt;throughput-performance&lt;/code&gt; profile for a while, and at some point I noticed something that should have bothered me a lot sooner: the fans never really spun down. Not under load --- that's expected --- but &lt;em&gt;at idle&lt;/em&gt;. I was running a background session with maybe 20–25% CPU busy, and the machine was behaving like it was under sustained full load.&lt;/p&gt;

&lt;p&gt;First stop, obviously: &lt;code&gt;cpupower&lt;/code&gt; / raw sysfs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
2900000
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
2900000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scaling_min_freq&lt;/code&gt; equal to &lt;code&gt;scaling_max_freq&lt;/code&gt; equal to &lt;code&gt;cpuinfo_max_freq&lt;/code&gt;. The governor had exactly one option: max turbo, always. On an i5-5300U (Broadwell-U, no HWP, 15 W RAPL PL1), that's not a "fast" configuration, it's a "the CPU is going to spend its whole life getting power-limited and thermal-throttled" configuration.&lt;/p&gt;

&lt;p&gt;Here's the full snapshot from that first pass:&lt;/p&gt;

&lt;p&gt;Full BEFORE snapshot (cpufreq + intel_pstate sysfs, RAPL, cpuidle, throttle counters)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Machine: HP EliteBook 840 G2, Intel Core i5-5300U (Broadwell-U, 4 threads, no HWP exposed)
Kernel: 7.2.0-rc5+ (vanilla drivers/cpufreq, verified against git only local commit was
        an unrelated hp-bioscfg fix, 105d2f15be0d)
Config: CONFIG_X86_INTEL_PSTATE=y, CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
Tuned:  profile "throughput-performance" applied at boot (tuned.log 2026-08-02 21:41:17)

--- cpufreq sysfs (cpu0) ---
scaling_driver      = intel_cpufreq   (intel_pstate PASSIVE mode)
scaling_governor    = performance
cpuinfo_min_freq    = 500000
cpuinfo_max_freq    = 2900000
scaling_min_freq    = 2900000        &amp;amp;lt;- floor pinned at MAX TURBO
scaling_max_freq    = 2900000

--- intel_pstate sysfs ---
max_perf_pct = 100
min_perf_pct = 100
no_turbo     = 0
num_pstates  = 25
status       = passive
turbo_pct    = 25

--- thermal_throttle counters (accumulated in ~2h uptime) ---
cpu0: core_throttle_count=115754  package_throttle_count=115803
cpu1: core_throttle_count=115754  package_throttle_count=115803
cpu2: core_throttle_count=45      package_throttle_count=115807
cpu3: core_throttle_count=45      package_throttle_count=115804
=&amp;amp;gt; ~115,800 package power-limit throttles in 2h. The package hits the RAPL
   PL1 limit constantly because it's never allowed to drop below max turbo.

--- RAPL / powercap ---
intel-rapl:0 (package-0): constraint_0 (long_term)  power_limit_uw = 15000000  (15 W)
                            constraint_1 (short_term) power_limit_uw = 25000000  (25 W)
                            enabled = 1

--- cpuidle states (cpu0) ---
POLL lat=0    use=8915     | C1  lat=2    use=64327   | C1E lat=10   use=284758
C3   lat=40   use=776488   | C6  lat=133  use=283868   | C7s lat=166  use=1028136
C8   lat=300  use=95112    | C9  lat=600  use=380983   | C10 lat=2600 use=52770

--- cmdline ---
BOOT_IMAGE=(hd0,gpt2)/vmlinuz-7.2.0-rc5+ root=UUID=... ro rootflags=subvol=root rhgb quiet kasan_multi_shot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;min_perf_pct = 100&lt;/code&gt; line is the one that matters, and I'll come back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  turbostat doesn't lie
&lt;/h2&gt;

&lt;p&gt;Sysfs tells you what the driver &lt;em&gt;thinks&lt;/em&gt; it's doing; &lt;code&gt;turbostat&lt;/code&gt; tells you what the silicon is actually doing. Ran it during a light-load phase (~22–25% busy):&lt;/p&gt;

&lt;p&gt;turbostat, "broken" phase (~22–25% busy)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Core CPU Avg_MHz Busy% Bzy_MHz TSC_MHz IPC IRQ NMI SMI LLCkRPS LLC%hit POLL% C1% C1E% C3% C6%
C7s% C8% C9% C10% CPU%c1 CPU%c3 CPU%c6 CPU%c7 CoreTmp CoreThr PkgTmp GFX%rc6 GFXMHz GFXAMHz
Pkg%pc2 Pkg%pc3 Pkg%pc6 Pkg%pc7 Pkg%pc8 Pkg%pc9 Pk%pc10 PkgWatt CorWatt GFXWatt RAMWatt

691   24.80  2795  2294  1.07  8739   0  0  105515  59.44  0.00  0.27  0.54  6.78  4.86 26.51
2.28  31.04  2.37  18.93  7.14  5.26  43.87  83  0  84  85.64  300  300  9.95  2.92  3.44
12.73  0.00  0.00  0.00  6.11  4.69  0.04  1.34

631   22.52  2807  2296  1.06  8041   1  0  109715  60.89  0.01  0.21  0.49  5.91  4.55 28.69
2.00  32.89  2.24  18.43  6.34  4.98  47.73  88  0  88  86.01  300  300  8.25  2.83  2.00
17.67  0.00  0.00  0.00  5.80  4.44  0.03  1.34

640   22.86  2806  2294  1.11  7122   1  0  115673  63.19  0.00  0.08  0.35  4.85  4.01 28.57
2.20  32.86  3.70  17.98  5.16  4.58  49.41  87  0  87  85.43  300  300  7.93  2.12  1.90
18.52  0.00  0.00  0.00  5.83  4.48  0.04  1.33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;CPU capabilities line, for reference:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPUID(1): family:model:stepping 0x6:3d:4 (6:61:4) microcode 0x2f
CPUID(6): APERF, TURBO, DTS, PTM, No-HWP, No-HWPnotify, No-HWPwindow, No-HWPepp, No-HWPpkg, EPB
cpu0: MSR_PLATFORM_INFO: 5100=500 MHz max efficiency, 23100=2300 MHz base
cpu0: MSR_TURBO_RATIO_LIMIT: 27100=2700 (2-6 cores), 29100=2900 (1 core)
cpu0: cpufreq driver: intel_cpufreq
cpu0: cpufreq governor: performance
cpufreq intel_pstate no_turbo: 0
cpu0: EPB: 0 (performance)
cpu0: MSR_IA32_POWER_CTL: 0x0004005f (C1E auto-promotion: ENabled)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Bzy_MHz sitting at 2795–2807 while the machine is 22–25% busy.&lt;/strong&gt; Core temps 83–88°C. That's not "using turbo when it helps," that's the driver never letting the CPU go below turbo &lt;em&gt;at all&lt;/em&gt;. Under an actual sustained load test the frequency was capped to about 2.2–2.5 GHz by RAPL anyway, so there was zero throughput upside, the floor was pure heat and battery cost, no benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  First theory: the driver is silently eating my write
&lt;/h2&gt;

&lt;p&gt;The obvious next move: try to fix it myself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;500000 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
0
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq
2900000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The write &lt;em&gt;succeeds&lt;/em&gt; return code 0, no error, nothing in &lt;code&gt;dmesg&lt;/code&gt;, and the value doesn't change. Checked it again at 50ms, 200ms, 500ms, 1s, 3s after the write. Still 2900000. The documented, user-facing knob for controlling minimum CPU frequency was accepting input and just... doing nothing with it.&lt;/p&gt;

&lt;p&gt;That's the kind of thing that gets a security/bug researcher's attention. A sysfs write that reports success but has no effect, with no diagnostic trail, is exactly the shape of bug I usually go looking for (just usually the outcome is "data corruption" instead of "annoyingly warm laptop").&lt;/p&gt;

&lt;p&gt;So I went into &lt;code&gt;drivers/cpufreq/intel_pstate.c&lt;/code&gt; to find out why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Down into intel_cpufreq_cpu_init()
&lt;/h2&gt;

&lt;p&gt;In passive mode, &lt;code&gt;intel_pstate&lt;/code&gt; calls itself &lt;code&gt;intel_cpufreq&lt;/code&gt; and hands frequency selection off to the generic cpufreq governors, but it doesn't fully let go. During per-policy init, it registers its &lt;em&gt;own&lt;/em&gt; min/max frequency constraints derived from the two global knobs (&lt;code&gt;min_perf_pct&lt;/code&gt; / &lt;code&gt;max_perf_pct&lt;/code&gt;), using the kernel's &lt;code&gt;FREQ_QOS&lt;/code&gt; request mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DIV_ROUND_UP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pstate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turbo_freq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_perf_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;freq_qos_add_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FREQ_QOS_MIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DIV_ROUND_UP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pstate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turbo_freq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_perf_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;freq_qos_add_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FREQ_QOS_MAX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;driver_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;min_perf_pct = 100&lt;/code&gt;, that first line computes a minimum frequency request of &lt;code&gt;turbo_freq * 100 / 100&lt;/code&gt; i.e., max turbo. The driver registers that as a &lt;code&gt;FREQ_QOS_MIN&lt;/code&gt; request against the policy. Every time you write to the global &lt;code&gt;min_perf_pct&lt;/code&gt; sysfs file, &lt;code&gt;store_min_perf_pct()&lt;/code&gt; refreshes this same request through &lt;code&gt;update_cpu_qos_request()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Meanwhile, your &lt;code&gt;scaling_min_freq&lt;/code&gt; write goes through generic cpufreq core (&lt;code&gt;store_one()&lt;/code&gt; in &lt;code&gt;drivers/cpufreq/cpufreq.c&lt;/code&gt;), which updates a &lt;em&gt;separate&lt;/em&gt; &lt;code&gt;FREQ_QOS_MIN&lt;/code&gt; request &lt;code&gt;policy-&amp;gt;min_freq_req&lt;/code&gt; representing the per-policy limit.&lt;/p&gt;

&lt;p&gt;So now you have two independent &lt;code&gt;FREQ_QOS_MIN&lt;/code&gt; requests sitting on the same policy: the driver's (from &lt;code&gt;min_perf_pct&lt;/code&gt;) and yours (from &lt;code&gt;scaling_min_freq&lt;/code&gt;). PM QoS has to resolve that down to one effective value, and for a &lt;em&gt;minimum&lt;/em&gt; constraint, it resolves by taking the &lt;strong&gt;maximum&lt;/strong&gt; of all registered minimums, which makes sense in isolation (if two parties both say "don't go below my number," the effective floor has to satisfy both, so you take the higher one). With &lt;code&gt;min_perf_pct=100&lt;/code&gt;, the driver's request is already at the ceiling, so it wins against literally anything you write to &lt;code&gt;scaling_min_freq&lt;/code&gt;. Your 500 MHz request never had a chance.&lt;/p&gt;

&lt;p&gt;At this point I was fairly confident I'd found a real bug: a driver silently overriding a documented user control, no error surfaced, no way to discover it without already reading the driver source. I started writing it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the bug report (before I caught myself)
&lt;/h2&gt;

&lt;p&gt;I put together what I thought was a clean, evidence-backed Bugzilla submission, reproduction steps, the QoS mechanism, the turbostat data, suggested fix directions. Short version of what I had drafted for &lt;code&gt;bugzilla.kernel.org&lt;/code&gt;, component &lt;strong&gt;Power Management&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;intel_pstate&lt;/code&gt; (passive mode): the driver's &lt;code&gt;FREQ_QOS_MIN&lt;/code&gt; request, derived from the global &lt;code&gt;min_perf_pct&lt;/code&gt; knob, silently overrides the user's &lt;code&gt;scaling_min_freq&lt;/code&gt; write and pins the CPU at max turbo frequency until &lt;code&gt;min_perf_pct&lt;/code&gt; is lowered.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reproduction was clean and repeatable. Impact was measurable and real (the throttle counters and temps above). Suggested fix directions ranged from "make the driver's request track &lt;code&gt;policy-&amp;gt;min&lt;/code&gt;" to "at minimum, return an error from &lt;code&gt;store_scaling_min_freq&lt;/code&gt; when the request can't be honored." I was about to go file it.&lt;/p&gt;

&lt;p&gt;Then, out of the same habit that makes me grep the actual source before writing up any finding, I went and read the driver documentation properly instead of just the code. specifically &lt;code&gt;Documentation/admin-guide/pm/intel_pstate.rst&lt;/code&gt;, section "Coordination of P-State Limits." And there it was, plainly stated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;none of them can be requested to run slower than the global minimum&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's rule #1 of the documented coordination model, and it applies "regardless of the current operation mode of the driver." Rule #3 explicitly adds that the global and per-policy limits are meant to be &lt;strong&gt;settable independently&lt;/strong&gt;. which is exactly why my write returns &lt;code&gt;rc=0&lt;/code&gt; even though it gets overridden: it's not being rejected, it's succeeding as a request that then loses to a stricter, higher-priority global constraint. That's not an accident of implementation. That's the API working exactly as specified.&lt;/p&gt;

&lt;p&gt;So the thing I'd found wasn't a bug. It was a correct, by-the-book implementation of a coordination rule I hadn't read yet. My QoS-request analysis was accurate I'd traced the actual mechanism correctly, I'd just drawn the wrong conclusion from it. I deleted the Bugzilla draft.&lt;/p&gt;

&lt;p&gt;I think this is worth admitting outright rather than quietly editing out of the story: I nearly filed a "kernel bug" that a maintainer would have closed in about thirty seconds with a link to documentation I should have read first. It happens. The fix is to build the documentation check into the workflow &lt;em&gt;before&lt;/em&gt; the writeup, not after lesson noted for next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's actually wrong here?
&lt;/h2&gt;

&lt;p&gt;If the kernel is behaving correctly, why was my laptop cooking itself?&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;min_perf_pct=100&lt;/code&gt; got set on a laptop, and that setting isn't meant for laptops. It came from the &lt;code&gt;tuned&lt;/code&gt; &lt;code&gt;throughput-performance&lt;/code&gt; profile, which is applied at boot on this machine. Its own upstream description is blunt about the target audience:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Broadly applicable tuning that provides excellent performance across a variety of common server workloads&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Server workloads. Sustained, predictable load, and critically server-class cooling and power budgets. &lt;code&gt;min_perf_pct=100&lt;/code&gt; on a machine with a 15 W package power limit is a fine choice on a rack server with real thermal headroom. On a Broadwell-U ultrabook with a 15 W RAPL PL1, it just means the CPU spends its entire life fighting the power limiter instead of scaling down when idle.&lt;/p&gt;

&lt;p&gt;The real bug: if you want to call it that is a profile-selection/config mismatch: a server-oriented tuned profile getting applied to thermally-constrained mobile hardware, with nothing in the middle catching that mismatch or warning about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Two parts: get off the pinned floor right now, and make sure it doesn't come back after the next &lt;code&gt;tuned-adm profile&lt;/code&gt; reapply or reboot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower the global floor and switch governors:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;27 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /sys/devices/system/cpu/intel_pstate/min_perf_pct
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;schedutil | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/devices/system/cpu/cpu&lt;span class="k"&gt;*&lt;/span&gt;/cpufreq/scaling_governor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full AFTER log: governor switch, min_perf_pct steps, verification&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;---&lt;/span&gt; governor switch alone is NOT enough &lt;span class="o"&gt;(&lt;/span&gt;QoS pin persists&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;schedutil &amp;amp;gt&lt;span class="p"&gt;;&lt;/span&gt; scaling_governor &lt;span class="o"&gt;(&lt;/span&gt;all 4 cpus&lt;span class="o"&gt;)&lt;/span&gt;
governor &lt;span class="o"&gt;=&lt;/span&gt; schedutil  BUT  scaling_min_freq &lt;span class="o"&gt;=&lt;/span&gt; 2900000, min_perf_pct &lt;span class="o"&gt;=&lt;/span&gt; 100  &lt;span class="o"&gt;(&lt;/span&gt;still pinned&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nt"&gt;---&lt;/span&gt; scaling_min_freq write still ineffective at &lt;span class="nv"&gt;min_perf_pct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="o"&gt;(&lt;/span&gt;confirmed again here&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt;

&lt;span class="nt"&gt;---&lt;/span&gt; lowering min_perf_pct releases the floor &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;27 &amp;amp;gt&lt;span class="p"&gt;;&lt;/span&gt; /sys/devices/system/cpu/intel_pstate/min_perf_pct
&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;gt&lt;span class="p"&gt;;&lt;/span&gt; min_perf_pct &lt;span class="o"&gt;=&lt;/span&gt; 27, scaling_min_freq &lt;span class="o"&gt;=&lt;/span&gt; 783000, scaling_cur_freq &lt;span class="o"&gt;=&lt;/span&gt; 2693 MHz &lt;span class="o"&gt;(&lt;/span&gt;scaling with load&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo &lt;/span&gt;50 &amp;amp;gt&lt;span class="p"&gt;;&lt;/span&gt; /sys/devices/system/cpu/intel_pstate/min_perf_pct   &lt;span class="o"&gt;(&lt;/span&gt;verification, stays stable&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;&amp;amp;gt&lt;span class="p"&gt;;&lt;/span&gt; min_perf_pct &lt;span class="o"&gt;=&lt;/span&gt; 50 &lt;span class="o"&gt;(&lt;/span&gt;unchanged after 3s&lt;span class="o"&gt;)&lt;/span&gt;, scaling_min_freq &lt;span class="o"&gt;=&lt;/span&gt; 1450000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;turbostat, "fixed" phase (~36–38% busy):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;878   36.47  2412  2295  1.12  10533  0  0  154694  66.09  0.00  0.17  0.71  9.12  6.13 26.03
2.04  17.61  1.14  21.91  8.36  5.82  27.44  81  0  81  77.15  300  300  3.71  2.29  1.71
5.69  0.00  0.00  0.00  6.84  5.29  0.06  1.52

769   37.42  2060  2295  1.19  8937   0  0  148147  67.49  0.00  0.23  0.47  6.50  4.19 28.82
1.72  18.46  2.00  20.90  6.29  3.98  31.40  80  0  81  79.04  300  300  4.06  1.85  1.02
7.10  0.00  0.00  0.00  5.62  4.05  0.05  1.48

967   37.84  2562  2294  1.12  10646  0  0  178202  68.13  0.01  0.16  0.86  10.04 6.96 26.78
1.77  13.40  1.49  21.81  9.12  6.41  24.82  90  0  90  78.24  300  300  3.66  2.65  1.87
5.37  0.00  0.00  0.00  7.52  5.97  0.05  1.56
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Bzy_MHz&lt;/code&gt; now varies 1988–2562 depending on load, instead of sitting locked at 2795–2807.&lt;/p&gt;

&lt;p&gt;Throughput check (2-core burn, 3s), to make sure the fix wasn't costing performance:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;under load : cpu MHz = 2494 / 2494 / 2530 / 2530   (RAPL-capped identical to before the fix)
after drop : cpu MHz = 2693 / 2693 / 2693 / 2693   (decaying, not pinned)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;No throughput loss. Makes sense sustained load was always RAPL-capped regardless of the floor; the floor only mattered when the machine was &lt;em&gt;not&lt;/em&gt; under load, and that's exactly the case it was hurting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make it stick.&lt;/strong&gt; Reapplying the &lt;code&gt;throughput-performance&lt;/code&gt; profile would just set &lt;code&gt;min_perf_pct=100&lt;/code&gt; again, so I created a local override instead of hand-patching global sysfs state every boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/tuned/profiles/throughput-performance
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/tuned/profiles/throughput-performance/tuned.conf
&lt;span class="c"&gt;# governor=schedutil; min_perf_pct=100 line removed; rest identical to distro file&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;tuned-adm profile throughput-performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/tuned/profiles/&lt;/code&gt; takes precedence over the shipped profile of the same name, so this survives &lt;code&gt;tuned&lt;/code&gt; updates without losing the override.&lt;/p&gt;

&lt;p&gt;Final verification snapshot (per-CPU, post-fix, post-reapply)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;intel_pstate: status=passive  no_turbo=0  max_perf_pct=100  min_perf_pct=17  num_pstates=25  turbo_pct=25
cpu0: driver=intel_cpufreq gov=schedutil min=500000 max=2900000
cpu1: driver=intel_cpufreq gov=schedutil min=500000 max=2900000
cpu2: driver=intel_cpufreq gov=schedutil min=500000 max=2900000
cpu3: driver=intel_cpufreq gov=schedutil min=500000 max=2900000

turbostat (busy ~40-54%, background session still running):

1397  51.13  2732  2300  1.06  14013  0  0  204633  67.36  0.01  0.18  1.18  12.79 8.29  22.03
0.79  2.78   0.09  19.91  9.67  6.57  12.72  102  10  102  69.28  300  300  7.80  0.36  0.43
0.00  0.00  0.00  0.00  9.82  8.05  0.08  1.72

1473  54.21  2719  2298  1.04  13141  2  0  217627  67.77  0.02  0.21  1.13  11.14 7.17  20.22
0.80  4.07   0.32  19.91  8.38  5.25  12.26  90   23  90   64.23  300  300  5.16  0.28  0.50
0.17  0.00  0.00  0.00  10.19 8.41  0.09  1.78

1049  40.76  2584  2291  1.14  10269  0  0  194151  70.24  0.01  0.27  0.73  8.67  6.17  28.57
1.68  11.74  0.73  19.87  7.68  5.54  26.15  94   0   94   77.78  300  300  4.15  2.32  1.57
6.82  0.00  0.00  0.00  7.64  6.12  0.05  1.56

thermal_throttle (cumulative; still incrementing under sustained load, which is expected
RAPL capping under real load is normal. The pin's actual harm was idle/light-load
throttling, which is what's gone now):
cpu0: core=137776 pkg=137848 | cpu1: core=137776 pkg=137848 | cpu2: core=65 pkg=137852 | cpu3: core=65 pkg=137850
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Before vs. after
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before (&lt;code&gt;min_perf_pct=100&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;After (&lt;code&gt;min_perf_pct=17&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scaling_min_freq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2900000 (max turbo)&lt;/td&gt;
&lt;td&gt;500000 (min P-state)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_perf_pct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;governor&lt;/td&gt;
&lt;td&gt;performance&lt;/td&gt;
&lt;td&gt;schedutil&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bzy_MHz, light load&lt;/td&gt;
&lt;td&gt;constant 2795–2807&lt;/td&gt;
&lt;td&gt;1988–2562, scales with load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;idle-capable floor&lt;/td&gt;
&lt;td&gt;none — 2.9 GHz min&lt;/td&gt;
&lt;td&gt;500 MHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;throughput under load&lt;/td&gt;
&lt;td&gt;~2.2–2.5 GHz (RAPL-capped)&lt;/td&gt;
&lt;td&gt;~2.2–2.5 GHz (identical)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;core temp, ~23% busy&lt;/td&gt;
&lt;td&gt;83–88°C&lt;/td&gt;
&lt;td&gt;n/a at matching load machine was busier during after-measurement, but idle/light throttling from the pin is gone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;power-limit throttles&lt;/td&gt;
&lt;td&gt;115,804 pkg / 2h&lt;/td&gt;
&lt;td&gt;no longer forced at idle/light load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same throughput ceiling under real load, meaningfully less heat and power draw everywhere else, and a laptop that actually behaves like a laptop again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone else hitting this
&lt;/h2&gt;

&lt;p&gt;If you're on Broadwell-U-class (or similarly TDP-constrained) Intel hardware, &lt;code&gt;intel_pstate&lt;/code&gt; in passive mode, and your &lt;code&gt;scaling_min_freq&lt;/code&gt; writes seem to silently do nothing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check &lt;code&gt;/sys/devices/system/cpu/intel_pstate/min_perf_pct&lt;/code&gt; first. If it's near 100, that's very likely your actual floor &lt;code&gt;scaling_min_freq&lt;/code&gt; can't go below whatever the global knob demands, by design.&lt;/li&gt;
&lt;li&gt;Check what set it. For me it was &lt;code&gt;tuned&lt;/code&gt;, but it could be a distro default, a power-profile daemon, or a manual tweak you forgot about.&lt;/li&gt;
&lt;li&gt;Lowering &lt;code&gt;min_perf_pct&lt;/code&gt; is what actually moves the floor. &lt;code&gt;scaling_min_freq&lt;/code&gt; will follow it down.&lt;/li&gt;
&lt;li&gt;If you're on &lt;code&gt;tuned&lt;/code&gt;'s &lt;code&gt;throughput-performance&lt;/code&gt; (or anything built on it, the MSSQL, virtual-host, or HFT-adjacent profiles inherit the same setting), remember it was designed for servers. On mobile/thermally-constrained hardware it's very likely the wrong default, and a local profile override under &lt;code&gt;/etc/tuned/profiles/&amp;lt;name&amp;gt;/&lt;/code&gt; is the clean way to fix it without patching the distro package.
## Where I landed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No kernel patch out of this one the code's doing what it's documented to do, and I don't think there's a functional bug here to fix. Two things I do think are worth following up on, separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A short documentation patch to &lt;code&gt;intel_pstate.rst&lt;/code&gt; making the "a near-100 global &lt;code&gt;min_perf_pct&lt;/code&gt; will silently swallow your &lt;code&gt;scaling_min_freq&lt;/code&gt; write" interaction more explicit and example-driven, since it took me a full read of the coordination-rules section (and an embarrassing near-miss on a bug report) to actually internalize it.&lt;/li&gt;
&lt;li&gt;A note to the &lt;code&gt;tuned&lt;/code&gt; project about whether &lt;code&gt;throughput-performance&lt;/code&gt; should carry a louder warning or a hardware-class check before it gets auto-applied on battery-powered, low-TDP systems.
Neither of those is glamorous. But "I found something that looked exactly like a bug, dug in, and it turned out the system was working as designed here's the real root cause instead" is, I'd argue, a more useful thing to publish than another closed WORKSFORME ticket would have been.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you've hit something similar on other Intel mobile chips or other &lt;code&gt;tuned&lt;/code&gt; profiles, I'd be curious to compare notes in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>performance</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
