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. A target_index driver hands the core a frequency table and is told “set index N” — the classic, sleeping, table-based path used by acpi-cpufreq and cpufreq-dt. A fast_switch driver 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. A setpolicy driver 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 provide setpolicy or target_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) and verify(policy_data) — mandatory for every driver. init is called once per policy to populate the frequency table, the related-CPU mask, and transition latency; verify clamps 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’s freq_table and passes that index; the driver looks up the row and writes the hardware. May sleep.
  • fast_switch(policy, target_freq) — a constrained variant of target_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 sets policy->fast_switch_possible in init to 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 if fast_switch is also set (registration enforces adjust_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_HARDWARE with Enhanced SpeedStep → write the MSR_IA32_PERF_CTL Model-Specific Register (cpu_freq_write_intel, lines 258–265). On AMD it writes MSR_AMD_PERF_CTL.
  • ACPI_ADR_SPACE_SYSTEM_IO → write an I/O port via acpi_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 setpolicy driver: 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 a target_index/fast_switch driver under schedutil. This is the single biggest reason ondemand/conservative are 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_perf driver under schedutil, or in a setpolicy-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 against drivers/cpufreq/intel_pstate.c and amd-pstate.c at 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 setpolicy driver (intel_pstate active) the generic governor list is bypassed, so writing ondemand to scaling_governor has no effect — the driver is the governor. This is not a bug; it is the setpolicy contract. Switch the driver to passive mode (or intel_pstate=passive) if you want a software governor.
  • Confusing “driver” and “governor.” scaling_driver (read-only) names the hardware layer; scaling_governor names 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_CHECK driver that boots at a frequency not present in its table will have the core try to snap it to a table entry, and BUG_ON() if that fails (flag comment, cpufreq.h, v6.12, lines 453–460) — a hard, early crash that usually means a firmware/_PSS mismatch.
  • A regulator-coupled driver expected to fast-switch. Asking why schedutil’s fast path is not used on an ARM board: because cpufreq-dt cannot implement fast_switch (regulator writes sleep). The frequency change goes through the kthread slow path by necessity.
  • Registration -EINVAL at probe. Setting both setpolicy and target_index, or adjust_perf without fast_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 _PSS exists, table-based.
  • cpufreq-dt — the generic ARM/embedded choice when the SoC has no dedicated driver; relies on correct DT operating-points-v2 and 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 implement fast_switch and outperform cpufreq-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