Runtime PM Reference Leaks: Find the Operation That Keeps a Device Awake
Find a runtime-PM reference leak by accounting for each operation’s acquired reference and its release on every terminal path. A device remaining active is a symptom, not proof of a leak. Separate an unbalanced usage count from autosuspend timing, active children, policy constraints, and a callback that cannot suspend.
Applicability: the helper semantics below are pinned to Linux v6.12 with runtime PM enabled and correctly initialized for the device. They do not describe Zephyr references or system-suspend wakeup sources. The operation identifiers and counts are hypothetical, and no driver or power measurement was executed.

1. Establish a baseline before counting requests
Identify the exact device object passed to the runtime-PM helpers. A peripheral, its bus controller, and a parent device can have separate counters. Logging only the driver name can merge several legitimate ownership histories into an apparent leak.
Record the expected idle baseline after probe and after any persistent operating session opens. A driver may intentionally retain a reference while an application stream is active. The useful invariant is that a completed operation returns to the correct baseline, not that every observed count must be zero.
The v6.12 runtime-PM framework requires a zero usage count and, normally, no active children before runtime idle or suspend can proceed. Runtime status, policy, callbacks, and scheduling still matter. Reaching zero establishes eligibility for part of the decision, not a measured rail-off state.
2. Audit the exact acquisition helper
The v6.12 helper definitions make an important distinction: pm_runtime_get_sync leaves the usage counter incremented even on an error. pm_runtime_resume_and_get undoes its increment when resuming fails and returns zero when it successfully retains the reference.
Therefore, a failed get_sync path still needs the corresponding accounting cleanup, commonly pm_runtime_put_noidle for the failed acquisition. A failed resume_and_get path must not add an unconditional put for a reference that the helper already removed. Review migrations between these helpers for leftover cleanup code.
Use the helper’s documented success test. Legacy get_sync can return a positive success result; treating every nonzero value as failure can send a valid acquisition down the wrong exit. Do not access the device after a genuine resume failure merely because the count was incremented.
Build a ledger with columns for operation, helper, return result, acquired delta, current owner, terminal path, and released delta. Put the helper name in the record rather than summarizing every operation as get.
3. Transfer ownership when work becomes asynchronous
For synchronous I/O, the initiating path can usually release the reference after its last device access. For asynchronous I/O, returning from submission does not finish the device’s work. Transfer responsibility to a completion or cancellation path that can prove the hardware operation has ended.
In a hypothetical driver, request A acquires one reference and submits successfully. Request B then acquires another. A completes first, dropping the total from two to one. B still owns the remaining reference; the count is not leaking merely because A’s completion did not make it zero.
If B’s submission fails before any asynchronous owner is established, the submitter releases B’s reference. If B was accepted, its eventual completion or fully synchronized cancellation releases it instead. Make those branches mutually exclusive so a failure callback and submitter cannot both release the same acquisition.
Track the transfer of ownership at the acceptance boundary. A Boolean indicating reference ownership can help explain the design, but its synchronization must match the actual concurrent paths. A variable without a race-safe protocol does not prevent double release.
4. Include errors after a successful resume
Many leaks occur after acquisition succeeds but before normal I/O begins: buffer allocation fails, validation rejects a command, a lock acquisition is interrupted, or hardware submission returns an error. Enumerate these exits explicitly instead of inspecting only the normal completion handler.
| Hypothetical path | Required reference result |
|---|---|
| resume_and_get fails | No operation-owned reference remains. |
| Resume succeeds; setup fails | The initiating path releases its one acquisition. |
| Submission accepted | Completion/cancellation owns the reference until final device use ends. |
| Cancellation races completion | Exactly one terminal owner performs the release. |
| Device removal begins | Stop producers and resolve outstanding ownership before final teardown. |
Use one review ledger for normal requests and another for long-lived references held by probe, open, streaming, or recovery. Mixing them hides the owner responsible for the baseline. Avoid repairing an unexplained count by issuing extra puts during removal.
5. Match release policy to the deployed version
Release helpers differ in whether they request idle, synchronous suspend, or delayed autosuspend. In v6.12, pm_runtime_put_autosuspend decrements the count and requests autosuspend; it does not itself update last_busy. Where the driver uses last-activity-based delay, mark the last busy point deliberately before the appropriate release.
Keep count balancing separate from deadline tuning. A longer autosuspend delay can explain why a balanced device remains active briefly. Repeated last-busy updates can defer suspension without any leaked reference. Conversely, changing the delay cannot repair a count that never returns to baseline.
A release helper returning an error does not necessarily mean its counter change was undone. Inspect the exact helper contract before retrying the accounting operation. Retrying a put merely because a later suspend attempt failed can underflow ownership.
6. Capture the first unmatched operation
Instrument acquisition result, submission acceptance, completion, cancellation, and release with a stable operation identifier and device identity. Add runtime-suspend callback entry and outcome. Keep tracing bounded and record its overhead; heavy logging can change the idle pattern under investigation.
Start with one request, then overlapping requests, then inject each setup and submission failure. Finally exercise cancellation and removal races. Compare the sum of operation-owned deltas with the expected baseline after all accepted work has reached a terminal state.
If accounting balances but suspension does not occur, move to policy, child activity, delayed deadlines, and callback failures. If it does not balance, report the first operation whose ownership never ended. This is more actionable than a final screenshot saying the device stayed active.
The acceptance artifact is a get/put/error ledger tied to a trace of accepted work and terminal ownership. It establishes software accounting for the tested paths. Actual current reduction, power-domain shutdown, and resume latency require separate target measurements under documented conditions.