cpufreq Scaling Drivers
A cpufreq scaling driver is the hardware-specific bottom layer of Linux CPU frequency scaling: the code that knows how this particular silicon changes its clock, sitting below the firmware-neutral cpufreq core and the policy-making governors. Every driver registers a
struct cpufreq_driver(include/linux/cpufreq.h, v6.12, lines 336–417) that fills in a small set of callbacks, and the shape of those callbacks declares the driver’s style. There are three. Atarget_indexdriver hands the core a frequency table and is told “set index N” — the classic, sleeping, table-based path used byacpi-cpufreqandcpufreq-dt. Afast_switchdriver additionally provides a lock-free, non-sleeping variant of the same operation so schedutil can change frequency directly from the scheduler’s wake-up path. Asetpolicydriver is self-governing: it does not expose a table at all, it just receives the policy’s min/max limits and decides frequencies internally — the model used by intel_pstate in active mode and by hardware-managed P-state schemes. The core enforces that a driver is exactly one of these: it must providesetpolicyortarget_index/target, never both (cpufreq.c, v6.12, lines 2908–2916).
Mental Model
Picture three horizontal layers. At the top, a governor decides what frequency the policy should run at from some signal (load, scheduler utilization, a fixed max/min). In the middle, the cpufreq core translates that desire into a concrete request, clamps it to policy limits, resolves it against a frequency table, and dispatches it. At the bottom, the scaling driver turns the request into an actual hardware write — a Model-Specific Register (MSR) poke on x86, a clock-rate change plus a voltage-regulator adjustment on ARM, or a message to firmware. The driver’s job is purely mechanical: make the silicon run at this speed. It contains no policy.
The three driver styles are three different contracts about who decides the frequency and how fast the decision must execute:
flowchart TB GOV["Governor (schedutil / ondemand / performance ...)"] CORE["cpufreq core<br/>clamp to limits, resolve to table index"] subgraph STYLES["Three driver styles (struct cpufreq_driver)"] TI["target_index(policy, idx)<br/>may sleep; table-based<br/>acpi-cpufreq, cpufreq-dt"] FS["fast_switch(policy, freq)<br/>atomic, no locks, no sleep<br/>schedutil fast path"] SP["setpolicy(policy)<br/>self-governing; gets min/max only<br/>intel_pstate active, HWP"] end HW["Hardware: MSR write / clk+regulator / firmware"] GOV --> CORE CORE -->|"slow path"| TI --> HW CORE -->|"scheduler context"| FS --> HW GOV -.->|"bypassed"| SP CORE -->|"limits only"| SP --> HW
The cpufreq layering and the three driver styles. What it shows: target_index and fast_switch drivers sit below a governor that picks the frequency; a setpolicy driver sits beside the governor — it receives only the min/max policy limits and makes its own per-instant decisions, so the generic governor layer is bypassed. The insight to take: which callbacks a driver fills in is not an implementation detail — it determines whether Linux’s software governors run at all on that platform.
The struct cpufreq_driver Interface
Reading the structure top to bottom (cpufreq.h, v6.12, lines 336–417) reveals the whole contract:
init(policy)andverify(policy_data)— mandatory for every driver.initis called once per policy to populate the frequency table, the related-CPU mask, and transition latency;verifyclamps a requested min/max to what the hardware can do.setpolicy(policy)— fill this xor the target callbacks. Present only on self-governing drivers.target(policy, freq, relation)— the original, now-deprecated callback (the comment literally says/* Deprecated */, line 350). Took a raw frequency and a relation (round up/down/closest).target_index(policy, index)— the modern table-based callback. The core resolves the desired frequency to an index into the driver’sfreq_tableand passes that index; the driver looks up the row and writes the hardware. May sleep.fast_switch(policy, target_freq)— a constrained variant oftarget_index: it must be lock-free, must not sleep, and must not call back into the cpufreq core. It exists so schedutil can switch frequency inside the scheduler’s update hook without queuing work to a kthread. A driver setspolicy->fast_switch_possibleininitto advertise it; the core enables it only when a fast-switch-capable governor is active (cpufreq.c, v6.12, lines 504–510).adjust_perf(cpu, min, target, capacity)— an even-faster variant for drivers with an internal performance-level abstraction (e.g. amd-pstate); it may only be set iffast_switchis also set (registration enforcesadjust_perf && !fast_switch→-EINVAL, line 2915).get(cpu)— return the CPU’s current frequency in kHz (best effort).get_intermediate/target_intermediate— for hardware that must pass through a safe intermediate frequency (e.g. switch a PLL through a stable reference) before reaching the final one. The core enforces they are set together (!get_intermediate != !target_intermediate→-EINVAL).online/offline/exit/suspend/resume/ready— lifecycle hooks for CPU hotplug and system sleep.register_em(policy)— register the policy’s Energy Model after init but before the governor starts, so EAS has a cost table.
The flags field selects core behaviors (lines 419–466): CPUFREQ_CONST_LOOPS (frequency changes do not affect loops_per_jiffy), CPUFREQ_IS_COOLING_DEV (auto-register as a thermal cooling device), CPUFREQ_HAVE_GOVERNOR_PER_POLICY (each clock domain gets its own governor instance and tunables), CPUFREQ_NEED_INITIAL_FREQ_CHECK (verify the boot frequency is in-table), and CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING (forbid dynamic-switching governors). The core automatically sets CPUFREQ_CONST_LOOPS on any setpolicy driver (line 2942), and enables the scheduler’s frequency-invariance static branch for any non-setpolicy driver (lines 2936–2939) — frequency invariance only makes sense when Linux itself chooses the frequency.
How the Core Dispatches to Each Style
The dispatch logic lives in cpufreq.c. For a setpolicy driver, cpufreq_set_policy() simply calls cpufreq_driver->setpolicy(policy) and returns (lines 2664–2667) — there is no governor, no frequency table, no per-frequency call. For a target_index driver, governor requests flow through __cpufreq_driver_target() → __target_index(policy, index) → cpufreq_driver->target_index(policy, index) (lines 2257–2292), which sends PRECHANGE/POSTCHANGE notifications around the driver call. For the fast path, cpufreq_driver_fast_switch(policy, target_freq) calls cpufreq_driver->fast_switch(...) directly with no notifications and no locks (lines 2165–2189), which is why the driver’s implementation must be so restricted.
acpi-cpufreq: x86 ACPI _PSS Tables
acpi-cpufreq is the generic x86 driver. It reads the per-processor _PSS (Performance Supported States) object that the firmware exposes through ACPI — a table of P-states, each with a core frequency, power, transition latency, and a hardware control value. ACPI’s processor layer parses _PSS into an acpi_processor_performance structure; the driver then builds a cpufreq freq_table from it, storing each P-state’s index in freq_table[i].driver_data so it can map a cpufreq index back to a hardware control word (acpi-cpufreq.c, v6.12, lines 797–831).
It is a target_index + fast_switch driver (lines 950–960). acpi_cpufreq_target() looks up the target P-state and writes its control value to the hardware via drv_write() (lines 413–461); acpi_cpufreq_fast_switch() does the same lock-free for schedutil (lines 463–496). The register it writes depends on what _PSS declares for the control register’s address space (lines 761–795):
ACPI_ADR_SPACE_FIXED_HARDWAREwith Enhanced SpeedStep → write theMSR_IA32_PERF_CTLModel-Specific Register (cpu_freq_write_intel, lines 258–265). On AMD it writesMSR_AMD_PERF_CTL.ACPI_ADR_SPACE_SYSTEM_IO→ write an I/O port viaacpi_os_write_port(cpu_freq_write_io, lines 288–291), the older mechanism.
Because the same MSR/port may have to be written on several CPUs sharing a clock, drv_write() uses smp_call_function_many() to run the write on every CPU in the mask (lines 332–349). The driver also caps a buggy-BIOS transition latency at 20 µs (lines 813–818) and notifies SMM/firmware that the OS now owns P-state control (acpi_processor_notify_smm, line 875). fast_switch_possible is set true only when acpi_pstate_strict is off and the policy is not shared in a way that would force a cross-CPU SMP call (lines 891–892) — a fast switch must stay cheap.
cpufreq-dt: Device Tree + Operating Performance Points (ARM)
On platforms without ACPI — most ARM and many embedded systems — frequencies come from the Device Tree (DT) via the Operating Performance Points (OPP) framework. cpufreq-dt is the generic glue. An OPP is a (frequency, voltage) pair the SoC supports; the firmware/DT lists them under an operating-points-v2 node, and the kernel’s pm_opp layer parses them. cpufreq-dt does not parse DT directly for frequencies — it asks the OPP layer to build the table:
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &priv->freq_table);(cpufreq-dt.c, v6.12, line 268). It is a pure target_index driver — it does not set fast_switch (lines 160–174). Its set_target() is one line:
static int set_target(struct cpufreq_policy *policy, unsigned int index)
{
struct private_data *priv = policy->driver_data;
unsigned long freq = policy->freq_table[index].frequency;
return dev_pm_opp_set_rate(priv->cpu_dev, freq * 1000);
}The real work is in dev_pm_opp_set_rate(): it changes the CPU clock and adjusts the voltage regulator in the correct order (raise voltage before raising frequency; lower frequency before lowering voltage) so the silicon never runs faster than its supply voltage permits. That clock-plus-regulator coordination — true DVFS — is exactly what acpi-cpufreq does not do, because on x86 the firmware/microcode handles voltage internally. cpufreq-dt finds the regulator name from DT (find_supply_name, lines 69–87, handling the legacy cpu0-supply name), discovers which CPUs share an OPP table via dev_pm_opp_of_get_sharing_cpus (line 217), and sets the cpufreq policy’s CPU mask accordingly so a whole cluster scales together. It also flags CPUFREQ_IS_COOLING_DEV so the thermal layer can throttle it, and provides register_em = cpufreq_register_em_with_opp so EAS gets an Energy Model built from the OPP power numbers (lines 160–174).
Why It Has No fast_switch
cpufreq-dt’s lack of a fast_switch callback is a direct consequence of its hardware: changing a regulator output voltage means talking to a Power Management IC over I²C/SPI, which sleeps. A fast_switch implementation must never sleep (it runs in the scheduler hook), so a regulator-coupled driver fundamentally cannot offer one. The practical effect: under schedutil on such a platform, every frequency change is dispatched to a kthread (the “slow path”) rather than done inline. Platforms that can fast-switch typically have a firmware mailbox or a single register that handles voltage internally — which is why SCMI-based and Qualcomm cpufreq-hw drivers implement fast_switch but the regulator-based cpufreq-dt does not.
The Self-Governing Special Cases
Two important x86 drivers are not target_index drivers and are covered in their own notes:
- intel_pstate — in its default active mode it is a
setpolicydriver: it bypasses the generic governor layer and either runs its own internal performance governor or, with Hardware-Managed P-States (HWP), hands the decision to the CPU’s own power-control unit, with Linux only supplying min/max and an energy-performance preference. In passive mode it instead behaves like atarget_index/fast_switchdriver under schedutil. This is the single biggest reasonondemand/conservativeare invisible on modern Intel laptops. - amd-pstate — the AMD analogue, built on ACPI Collaborative Processor Performance Control (CPPC). It can operate as a passive
fast_switch/adjust_perfdriver under schedutil, or in asetpolicy-style guided/active mode.
Uncertain
Verify: the precise default modes of intel_pstate (active vs passive) and amd-pstate (passive/active/guided) as of v6.12/v6.18, and which exact callbacks each fills in per mode. Reason: those defaults are mode- and
Kconfig-dependent and shift between releases; this note states the styles from the generic interface but defers the per-driver specifics to their own notes, which must verify againstdrivers/cpufreq/intel_pstate.candamd-pstate.cat the tags. To resolve: read those driver sources directly. uncertain
Failure Modes and Common Misunderstandings
- “My CPU is stuck at one frequency / scaling_governor shows nothing useful.” On a
setpolicydriver (intel_pstate active) the generic governor list is bypassed, so writingondemandtoscaling_governorhas no effect — the driver is the governor. This is not a bug; it is thesetpolicycontract. Switch the driver to passive mode (orintel_pstate=passive) if you want a software governor. - Confusing “driver” and “governor.”
scaling_driver(read-only) names the hardware layer;scaling_governornames the policy layer. A frequent diagnostic mistake is tuning the governor when the driver (e.g. acpi-cpufreq fell back because intel_pstate was disabled) is the thing that changed behavior. - Out-of-table boot frequency. A
CPUFREQ_NEED_INITIAL_FREQ_CHECKdriver that boots at a frequency not present in its table will have the core try to snap it to a table entry, andBUG_ON()if that fails (flag comment,cpufreq.h, v6.12, lines 453–460) — a hard, early crash that usually means a firmware/_PSSmismatch. - A regulator-coupled driver expected to fast-switch. Asking why schedutil’s fast path is not used on an ARM board: because
cpufreq-dtcannot implementfast_switch(regulator writes sleep). The frequency change goes through the kthread slow path by necessity. - Registration
-EINVALat probe. Setting bothsetpolicyandtarget_index, oradjust_perfwithoutfast_switch, fails registration immediately (lines 2908–2916). A driver author who tries to be “both styles” is rejected by design.
Alternatives and When to Choose Them
acpi-cpufreq— the x86 fallback when a vendor-specific self-governing driver is absent or disabled (intel_pstate=disable). Generic, works anywhere_PSSexists, table-based.cpufreq-dt— the generic ARM/embedded choice when the SoC has no dedicated driver; relies on correct DToperating-points-v2and a regulator.intel_pstate/amd-pstate— the right choice on modern AMD/Intel; they expose hardware features (HWP/CPPC, energy-performance preference, turbo) that the generic drivers cannot. Prefer these on capable hardware.- SoC-specific drivers (
cpufreq-hw/ SCMI /qcom-cpufreq-hw) — where the SoC has a hardware frequency-control block, these implementfast_switchand outperformcpufreq-dt. Use whatever the platform vendor ships.
Production Notes
The driver layer is where “my laptop won’t turbo” and “my ARM board overheats” problems actually live. On x86, the practical reality is that acpi-cpufreq is increasingly a fallback: Intel and AMD machines load intel_pstate/amd-pstate first, and acpi-cpufreq only appears when those are explicitly disabled or on older silicon. Checking cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver is the first diagnostic step, because it tells you whether a software governor is even in the loop. On ARM, the dominant failure is a Device Tree with wrong OPP voltages — dev_pm_opp_set_rate will program a voltage too low for the requested frequency and the CPU hangs, or too high and it wastes power; this is a DT bug, not a kernel bug, and it is why OPP tables are vendor-validated. The clean separation the cpufreq_driver interface enforces — driver knows hardware, governor knows policy, core knows bookkeeping — is what lets the same governors run unchanged across an Intel server, a Raspberry Pi, and a Qualcomm phone; the driver absorbs all the silicon-specific ugliness behind three small callback contracts.
See Also
- The cpufreq Subsystem — the policy model and the core that dispatches to these drivers
- intel_pstate and Hardware-Managed P-States — the
setpolicyself-governing x86 special case - amd_pstate Driver — the AMD CPPC-based counterpart
- The schedutil Governor — the consumer of the
fast_switch/adjust_perfcallbacks - The ondemand and conservative Governors — the table-based governors that drive
target_indexdrivers - Dynamic Voltage and Frequency Scaling DVFS — the clock+voltage coordination
cpufreq-dtperforms via OPP - The Energy Model EM Framework — built from driver-supplied power data via
register_em - Cooling Devices and Thermal Governors — what
CPUFREQ_IS_COOLING_DEVwires the driver into - Linux Power Management MOC — the parent map (§2, CPU Frequency Scaling)