Runtime PM, suspend, clocks, regulators, and thermal policy

Explain device runtime power, system suspend and resource dependencies.

15 min readChapter 64 of 168Intermediate → advanced
In this chapter

Before you start#

Read I2C, SPI, GPIO, and pinctrl and platform firmware. Linux v6.12 is the historical source baseline. Power domains, clocks, thermal sensors, firmware and hardware behavior vary. The lab reads existing attributes only; it does not suspend the host or change power policy.

Learning goals#

Follow runtime-PM usage references and error handling, distinguish runtime from system suspend, balance shared clock and regulator operations, and interpret thermal/cpufreq observations without assuming they prove physical power state.

Source walkthrough#

Runtime PM, suspend, clocks, regulators, and thermal policy
Runtime PM, suspend, clocks, regulators, and thermal policy1. Driver to Runtime PM core: resume_and_get: 0 → 1. 2. Runtime PM core to Device resources: Resume callbacks, clocks and supplies as configured. 3. Runtime PM core to Driver (return): Success or negative error. 4. Driver to Runtime PM core: mark last busy; put autosuspend: 1 → 0. 5. Runtime PM core to Device resources: After delay, suspend if still eligible. 6. Driver to Runtime PM core: Racing get cancels or coordinates transition.DriverRuntime PM coreDevice resourcesresume_and_get: 0 → 1Resume callbacks, clocksand supplies asconfiguredSuccess or negativeerrormark last busy; putautosuspend: 1 → 0After delay, suspend ifstill eligibleRacing get cancels orcoordinates transition
  1. DriverRuntime PM core

    resume_and_get: 0 → 1

  2. Runtime PM coreDevice resources

    Resume callbacks, clocks and supplies as configured

  3. Runtime PM coreDriver

    Return. Success or negative error

  4. DriverRuntime PM core

    mark last busy; put autosuspend: 1 → 0

  5. Runtime PM coreDevice resources

    After delay, suspend if still eligible

  6. DriverRuntime PM core

    Racing get cancels or coordinates transition

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Driver → Runtime PM core: resume_and_get: 0 → 1
  2. Runtime PM core → Device resources: Resume callbacks, clocks and supplies as configured
  3. Runtime PM core → Driver (return): Success or negative error
  4. Driver → Runtime PM core: mark last busy; put autosuspend: 1 → 0
  5. Runtime PM core → Device resources: After delay, suspend if still eligible
  6. Driver → Runtime PM core: Racing get cancels or coordinates transition
Conceptual model. Conditional Linux v6.12 source model; illustrative state and ownership, not a captured hardware execution.

Runtime PM lets a driver and the PM core coordinate a device's active and suspended runtime states while the rest of the system continues to run. dev->power contains state including a usage count and runtime status protected by the core's synchronization. A usage reference says that a consumer needs the device under the API's rules; it is not a wattmeter or proof that every internal circuit is physically powered. Parent devices, device links, suppliers, PM domains and driver callbacks can constrain transitions. Runtime PM can be disabled or unsupported, and a device can remain active at count zero due to policy or other conditions. See drivers/base/power/runtime.c and include/linux/pm_runtime.h.

A driver beginning sleepable I/O often needs a successful runtime resume and a balanced reference before touching registers. pm_runtime_get_sync increments the usage count even if resume returns an error; a caller that only puts on success can leak a reference. The helper pm_runtime_resume_and_get makes this failure handling clearer. Its v6.12 implementation in include/linux/pm_runtime.h, GPL-2.0-only, includes:

c
	ret = __pm_runtime_resume(dev, RPM_GET_PUT);
	if (ret < 0) {
		pm_runtime_put_noidle(dev);
		return ret;
	}

The first call requests the synchronous resume while taking a usage reference. On a negative result, the helper drops that increment without scheduling an idle transition and returns the error. The following success path returns zero. The caller must not proceed with hardware I/O as if a failed resume succeeded. __pm_runtime_resume increments the count for RPM_GET_PUT, takes the power lock around rpm_resume, and may sleep under its documented conditions. It is not safe to call a potentially sleeping synchronous helper from arbitrary atomic context.

After successful I/O, a driver can mark the last busy time and use an autosuspend put. The core can schedule a delayed suspend when the usage count and policy permit. A runtime-suspend callback must ensure outstanding device I/O is completed or safely stopped before disabling resources; a reference counter by itself cannot stop DMA. An interrupt or new request can race the pending autosuspend, so the core's state machine and driver locking must be respected. A runtime-resume callback reverses the device-specific power sequence and can fail; error handling must preserve a coherent software state rather than assume that a failed clock or regulator enable partially powered everything correctly.

A usage-count timeline#

Consider a hypothetical device at usage count 0 and runtime status suspended, with runtime PM enabled and no other users. A sleepable request calls pm_runtime_resume_and_get: the count is incremented, resume succeeds, and the helper returns zero with count 1 and status active. The driver performs I/O and waits for device completion. It marks last busy and puts with autosuspend, taking the count to 0 and permitting a delayed suspend after the configured interval. This does not mean that the device suspended at the instant the count reached zero.

Before the timer expires, another request obtains a reference. Depending on the exact transition state and API path, the core cancels or coordinates pending work, or resumes/waits for a transition already in progress. The driver can start hardware access only after its chosen get/resume operation succeeds. If resume returns -EIO, pm_runtime_resume_and_get rolls back its increment; the caller reports or handles the error and does not perform the intended I/O. The real count can include other consumers and dependencies. The numbers here illustrate ownership, not a captured device trace or a promise that physical power is off at count zero.

System suspend is a different lifecycle#

System suspend coordinates the whole system, including process freezing where applicable, device dependency ordering, suspend callbacks and platform entry. Device PM has phases such as prepare, suspend, late and noirq with corresponding resume phases; which callbacks run depends on the device and PM domain. Async device suspend can run independent devices in parallel while respecting core ordering constraints, so drivers must not assume an arbitrary sibling completed first without a dependency. Wakeup sources and device wake configuration affect whether events can wake the system. A wake-capable device is not necessarily allowed to wake the system under current policy.

s2idle is a suspend-to-idle mechanism; deeper platform suspend can involve firmware such as ACPI on applicable systems. Hibernation writes a system image and has a different restore and storage lifecycle from ordinary suspend-to-RAM or runtime autosuspend. A runtime-suspended device can enter system suspend through optimized or alternative callback paths depending on the PM core and driver flags. A successful callback does not by itself prove a particular board removed power from a rail. See kernel/power/suspend.c and kernel/power/hibernate.c.

A device must quiesce I/O before a system suspend transition that makes registers, clocks or DMA unavailable. An interrupt may arrive while suspend is in progress; the driver and PM core need an explicit ordering and wakeup protocol. If a suspend callback fails after disabling one resource but before another step, its error path must restore or unwind the resources it changed under that callback's contract. A blanket instruction to "turn off everything" can break a shared supplier or a wakeup path. This chapter does not suspend the host.

Clocks and regulators are shared resources#

The common clock framework distinguishes preparation from enabling. clk_prepare can perform operations that sleep and uses the prepare-side locking; clk_enable is intended for the non-sleeping enable side under its API constraints. clk_prepare_enable combines the operations in a sleepable context and requires the matching disable/unprepare sequence. Parent clocks and other consumers can hold references, so one consumer's disable does not prove a physical clock stopped. See drivers/clk/clk.c.

The v6.12 clk_prepare implementation, GPL-2.0, contains:

c
	if (!clk)
		return 0;

	return clk_core_prepare_lock(clk->core);

A null clock is treated as a no-op by this helper; otherwise it delegates to the core's prepare operation under the relevant lock. A successful prepare is not identical to enabling the clock, configuring a rate, or proving a peripheral is ready. If a later enable or device-registration step fails, the driver must balance successful earlier operations according to the helper's contract. A clock provider can itself depend on power management or firmware.

A regulator consumer obtains a regulator handle separately from enabling it. regulator_enable participates in shared enable accounting and provider/dependency locking; a supply can remain on because another consumer still needs it or because constraints require it. Voltage constraints and supported ranges matter: a consumer request is not permission to force an arbitrary voltage onto a shared rail. Probe can fail or defer if a provider is unavailable. If a driver enables a regulator and then fails to enable a clock, it must unwind the successful regulator enable under its ownership rules. See drivers/regulator/core.c.

In regulator_enable, Linux v6.12, GPL-2.0-or-later, the consumer entry point takes the dependent-regulator lock around the internal enable:

c
int regulator_enable(struct regulator *regulator)
{
	struct regulator_dev *rdev = regulator->rdev;
	struct ww_acquire_ctx ww_ctx;
	int ret;

	regulator_lock_dependent(rdev, &ww_ctx);
	ret = _regulator_enable(regulator);
	regulator_unlock_dependent(rdev, &ww_ctx);

	return ret;
}

The enable accounting and constraint checks are in _regulator_enable, which the excerpt does not show. The result is returned to the caller, so a consumer must check it before treating the enable as a reference it holds.

Thermal feedback and CPU frequency#

A thermal zone combines a temperature source, trip points and policy linking it to cooling devices where configured. Values exposed by common thermal sysfs interfaces are typically in millidegrees Celsius, but a sensor's calibration, location and hardware accuracy matter. Hysteresis prevents rapid repeated transitions around a threshold; it is not a guarantee that temperature will never overshoot. A cooling device can throttle a CPU, fan or other resource under its own range and policy. Missing thermal zones in a VM or container are a valid environment, not evidence that physical cooling is unnecessary. See drivers/thermal/thermal_core.c.

CPU frequency policy groups CPUs that share a control domain. A governor such as schedutil uses scheduler signals and driver/platform constraints to request performance states; hardware, firmware, thermal limits and shared policies affect the actual frequency. A sysfs current-frequency value can have driver-specific measurement or reporting limitations. A thermal cap can reduce available performance even if a governor requests more, but a single reading does not prove why a particular workload is slow. See kernel/sched/cpufreq_schedutil.c. Do not change governor, trip points or clocks on the host for this exercise.

Failure and ownership across suppliers#

Imagine a device needs a regulator and a clock. The driver successfully enables the regulator, then clk_prepare_enable fails. It must not access the peripheral registers as though the clock were working; its error path balances the regulator enable it owns and releases handles according to the APIs. If another consumer holds the same regulator, that consumer's reference can keep the rail enabled. A later successful runtime resume can still fail at a different device-specific step. A useful probe diagram records return values and owned references at each transition, including the failure label, instead of assuming that a shared resource always turns off when one driver exits.

Failed clock after regulator enable
  1. Get handles

    Obtain the regulator and clock handles; each acquisition is owned.

  2. Enable regulator

    Success records one reference this driver owns.

  3. Prepare and enable clock

    Failure returns an error before any register access.

  4. Unwind owned refs

    Disable only the regulator reference this driver holds.

  5. Return error

    The rail stays on if another consumer still holds it.

Conceptual model. Teaching model of the failed-clock unwind described in the chapter; shared supplies and clocks keep their own counts. Linux v6.12 consumer APIs. Not a captured trace.

For runtime suspend, the device's private lock, PM core state and supplier references have different scopes. The callback can wait for in-flight I/O if its context and design permit, or refuse/defer a transition if the device is busy. It must not disable a clock used by an outstanding DMA engine and then free the buffer while the device can still access it. Wake IRQs and device links can require additional ordering. On resume, restore resources and registers in the hardware-required order and handle partial failure. Runtime PM status in sysfs is an observation of core state, not a direct measurement of voltage on a pin.

Autosuspend race and error accounting#

The 0 -> 1 -> 0 example assumes one consumer. With two consumers, A can raise the usage count from 0 to 1 and B can raise it from 1 to 2; when A finishes and puts its reference, the count remains 1 and autosuspend must not be inferred merely from A's completion. Only after B balances its own reference can the count reach 0, and even then the configured delay, policy, dependencies and core state determine whether suspend occurs. A consumer must balance only references it successfully owns. Failure of pm_runtime_resume_and_get already undoes that helper's increment; doing an additional unconditional put can underflow the logical ownership protocol.

Two consumers, one usage count

Synthetic teaching data, not a device capture.

Two consumers, one usage count1. Consumer A to PM usage count: pm_runtime_resume_and_get: 0 to 1. 2. Consumer B to PM usage count: pm_runtime_resume_and_get: 1 to 2. 3. Consumer A to PM usage count: pm_runtime_put_autosuspend: 2 to 1, no suspend implied. 4. Consumer B to PM usage count: pm_runtime_put_autosuspend: 1 to 0, delay may start.Consumer AConsumer BPM usage countpm_runtime_resume_and_get: 0 to 1pm_runtime_resume_and_get: 1 to 2pm_runtime_put_autosuspend: 2 to 1, no suspendimpliedpm_runtime_put_autosuspend: 1 to 0, delay maystart
  1. Consumer APM usage count

    pm_runtime_resume_and_get: 0 to 1

  2. Consumer BPM usage count

    pm_runtime_resume_and_get: 1 to 2

  3. Consumer APM usage count

    pm_runtime_put_autosuspend: 2 to 1, no suspend implied

  4. Consumer BPM usage count

    pm_runtime_put_autosuspend: 1 to 0, delay may start

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Consumer A → PM usage count: pm_runtime_resume_and_get: 0 to 1
  2. Consumer B → PM usage count: pm_runtime_resume_and_get: 1 to 2
  3. Consumer A → PM usage count: pm_runtime_put_autosuspend: 2 to 1, no suspend implied
  4. Consumer B → PM usage count: pm_runtime_put_autosuspend: 1 to 0, delay may start
Synthetic teaching data. Teaching model of two consumers sharing one runtime-PM usage count with synthetic numbers; count zero permits but does not perform a suspend. Linux v6.12 helper names. Not a captured trace.

If autosuspend is pending when B arrives, the PM core coordinates the timer and resume state. The driver must also serialize its own registers, DMA and private queues: the PM usage counter is not a replacement for a device-specific lock. A runtime-suspend callback that starts shutting down a clock while a separately managed DMA channel is still active can corrupt data. A system suspend callback has additional ordering and wakeup requirements, so a runtime-PM experiment cannot establish correctness of the system sleep path.

Shared supply unwind and thermal feedback#

Suppose consumer A obtains and enables a regulator, then prepares a clock. If preparation fails, A disables the regulator reference it successfully enabled and releases handles as required. If consumer B also enabled that rail, the regulator framework can retain the physical supply for B. A's cleanup should not assume that the physical voltage necessarily falls. Likewise, disabling A's clock reference does not mean a shared parent clock stops if another user or a critical-clock constraint keeps it enabled. The driver must check return codes and balance the operations it owns, not force hardware state behind the framework's accounting.

A thermal zone's trip point can initiate a policy response only through the configured sensor, governor and cooling devices. Hysteresis can keep a cooling state active until temperature falls sufficiently, avoiding immediate oscillation at the same threshold. The number read from temp is a sensor report with location and calibration limits; it does not establish the junction temperature of every component or prove a particular clock was throttled. A cpufreq policy can cover multiple CPUs and be constrained by firmware or hardware, so one frequency sample is not a complete history of an application's performance. These relationships motivate recording policy and time, not writing host controls.

What a status sample can establish#

runtime_status is a snapshot of the PM core's state for one device. It can change immediately after the read, and control=auto permits the core's runtime policy rather than commanding that the device is currently suspended. An unsupported result can reflect the device's runtime-PM setup and is not evidence that all system suspend paths are unsupported. A temperature or frequency sample is similarly time-local; record its unit and source before comparing it with another value.

The thermal and cpufreq frameworks coordinate policy with drivers and hardware, but a cooling response may involve multiple constraints and shared resources. If a workload slows down at the same time as a high thermal reading, that correlation is a hypothesis, not proof that the thermal governor caused the slowdown. A proper investigation would collect time-aligned policy, limit and workload observations without changing production controls. The read-only exercise intentionally does not trigger autosuspend, system sleep or a thermal event, so its results cannot validate those callback paths.

Practice#

Source-only route:

sh
rg -n -A20 'static inline int pm_runtime_resume_and_get\(' "$KERNEL_SRC/include/linux/pm_runtime.h"
rg -n -A28 '^int __pm_runtime_resume\(' "$KERNEL_SRC/drivers/base/power/runtime.c"
rg -n -A12 '^int clk_prepare\(' "$KERNEL_SRC/drivers/clk/clk.c"
rg -n -A20 '^int regulator_enable\(' "$KERNEL_SRC/drivers/regulator/core.c"

Verify v6.12. Mark the usage-count increment, negative-return rollback, clock prepare and regulator locking. Work through the 0 -> 1 -> 0 autosuspend timeline and the failed-clock unwind without claiming those transitions occurred on your machine.

Read-only local observation:

sh
python3 - <<'PY'
from pathlib import Path
roots = [Path('/sys/bus/platform/devices'), Path('/sys/bus/pci/devices')]
found = False
for root in roots:
    try: devices = sorted(root.iterdir()) if root.is_dir() else []
    except OSError: continue
    for d in devices:
        p = d / 'power' / 'runtime_status'
        try:
            status = p.read_text().strip()
            print('example runtime status:', status)
            try: print('control:', (d / 'power' / 'control').read_text().strip())
            except OSError: print('control unavailable')
            found = True; break
        except OSError: continue
    if found: break
if not found: print('no readable runtime PM example')
for label, root, pattern in [('thermal zones', '/sys/class/thermal', 'thermal_zone*'),
                              ('cpufreq policies', '/sys/devices/system/cpu/cpufreq', 'policy*')]:
    p = Path(root)
    try: print(label + ':', len(list(p.glob(pattern))) if p.is_dir() else 0)
    except OSError as e: print(label + ' unavailable:', e.strerror)
zones = sorted(Path('/sys/class/thermal').glob('thermal_zone*'))
if zones:
    try: print('example temperature (millidegrees C):', (zones[0] / 'temp').read_text().strip())
    except OSError as e: print('temperature unavailable:', e.strerror)
policies = sorted(Path('/sys/devices/system/cpu/cpufreq').glob('policy*'))
if policies:
    for field in ('scaling_governor', 'scaling_cur_freq'):
        try: print(field + ':', (policies[0] / field).read_text().strip())
        except OSError as e: print(field + ' unavailable:', e.strerror)
clock_dir = Path('/sys/kernel/debug/clk')
try: print('clock debug metadata:', 'visible' if clock_dir.is_dir() else 'unavailable')
except OSError as e: print('clock metadata unavailable:', e.strerror)
PY

A status such as active, suspended or unsupported is a PM-core report at one instant; control is read, not written. No readable device, no thermal zone or no cpufreq policy is a valid result in a guest/container. A temp sample, when available, is reported in millidegrees Celsius; scaling_cur_freq, when exposed, is conventionally in kHz and can be driver-reported rather than an instantaneous hardware measurement. Clock debug metadata may be unavailable without privileged debugfs access. The counts and samples do not establish a rail voltage or calibrated temperature. Deliver the count timeline, failed-resume and clock-unwind explanations, and observed status or limitation. Do not write power/control, suspend, tune clocks or trigger thermal policy.

Knowledge check#

  1. Why can a caller leak a reference when pm_runtime_get_sync fails?
  2. What does pm_runtime_resume_and_get do on a negative result?
  3. Why does count zero not prove the hardware is physically off?
  4. How do clock prepare and enable differ, and why can another consumer keep a clock running?
  5. Why is a thermal-zone value not a complete performance diagnosis?

Answers#

  1. The helper increments the usage count even on failure; a caller must balance that increment under its chosen error path.
  2. It calls pm_runtime_put_noidle to undo the increment and returns the error.
  3. Autosuspend timing, policy, dependencies and hardware implementation determine what happens next; count zero only permits eligible transitions.
  4. Prepare may sleep and enable is the non-sleeping side of the API; shared consumer references and provider constraints affect physical state.
  5. Sensor location/calibration, policy, firmware, cpufreq and workload all influence the result; one core-reported number is not a causal trace.

Checkpoint#

Save the count timeline, PM error path, clock/regulator unwind and read-only observation. Pass when you can explain a racing get, a failed resume, shared-resource references and the difference between runtime and system suspend. If attributes are absent, complete the source-only route. This is practice, not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.