Generic Power Domains genpd
A generic power domain (genpd) is the kernel’s model of a shared on-SoC power rail — a regulator, clock-gate, or hardware power switch that feeds several devices at once and can only be turned off when every device fed by it is idle. On a System-on-Chip (SoC) the hardware is not wired so that each IP block has its own independent power switch; instead a single switch gates a whole “island” of blocks. Runtime PM alone cannot turn such a rail off, because runtime PM reasons about one device at a time and has no idea its neighbour shares the same supply. The genpd framework (
struct generic_pm_domain, defined ininclude/linux/pm_domain.h, v6.12; core indrivers/pmdomain/core.c, v6.12) closes that gap: it sits on top of runtime PM, counts how many of a domain’s devices are still runtime-active, and invokes the SoC-specificpower_offcallback only when that count reaches zero. This note traces the data structure, how devices and sub-domains attach, the reference-counting that gates the rail, and the governors that decide whether powering down is worthwhile. Verified against the v6.12 source on 2026-06-25.
Uncertain
Verify: the genpd core file path. Reason: the source brief named
drivers/base/power/domain.c, but in Linux 6.12 the genpd core lives atdrivers/pmdomain/core.cand the governors atdrivers/pmdomain/governor.c— the code was relocated out ofdrivers/base/power/during the 6.6/6.7 cycle. The mechanism is identical; only the path moved. To resolve: this is already confirmed against the v6.12 tag (the old path returns HTTP 404;drivers/pmdomain/core.cis 3564 lines). uncertain
The single most important idea: genpd is a consumer-aggregation layer. Each device’s runtime-suspend still happens individually, but the power rail is a collective resource, so genpd interposes its own runtime_suspend/runtime_resume callbacks, lets the driver’s real callbacks run, and then does the bookkeeping that decides whether the shared hardware can now be gated.
Mental Model: A Reference-Counted Switch Above Runtime PM
Think of a genpd as a light switch wired to a whole room full of appliances. Each appliance (device) has its own on/off button (its runtime-PM usage counter). The room’s master switch (the power rail) may only be flipped off when every appliance is off. genpd is the doorman that watches all the appliance buttons and flips the master switch — never sooner. Domains nest: a building’s main breaker (a parent domain) can only trip when every room (sub-domain) below it is dark, and that nesting is itself reference-counted.
flowchart TB subgraph GENPD["genpd: VDD_PERIPH rail (struct generic_pm_domain)"] direction TB DL["dev_list: I2C0, SPI1, UART2"] SDC["sd_count (subdomains powered on)"] PON["power_on() / power_off()<br/>(SoC regulator/clock-gate callbacks)"] end RPM0["I2C0 runtime PM<br/>usage_count -> 0"] -->|"genpd_runtime_suspend"| GENPD RPM1["SPI1 runtime PM<br/>usage_count -> 0"] -->|"genpd_runtime_suspend"| GENPD RPM2["UART2 runtime PM<br/>usage_count -> 0"] -->|"genpd_runtime_suspend"| GENPD GENPD -->|"all suspended AND sd_count==0 AND<br/>governor power_down_ok()? -> power_off()"| RAIL["VDD_PERIPH regulator OFF"] GENPD -.->|"is a subdomain of"| PARENT["Parent domain: VDD_SOC<br/>(only off when ALL children off)"]
How genpd gates a shared rail. What it shows: three peripherals share the VDD_PERIPH rail. Each one runtime-suspends independently through genpd’s interposed genpd_runtime_suspend callback; only when the last device is runtime-suspended, no sub-domain is still on (sd_count == 0), and the governor agrees, does genpd call the SoC’s power_off() to actually drop the regulator. The dashed edge shows that VDD_PERIPH is itself a sub-domain of VDD_SOC, which can only power off once all its children are off. The insight to take: runtime PM decides per-device idleness; genpd turns a set of those per-device decisions into one collective decision about the shared hardware, and stacks those decisions hierarchically.
The Structure: struct generic_pm_domain
The domain is described by struct generic_pm_domain (pm_domain.h, v6.12, line 155). The fields that matter for the core mechanism:
struct device dev— yes, a domain is itself astruct device. This lets genpd appear in sysfs/debugfs and own an OPP (Operating Performance Point) table for performance-state scaling.struct dev_pm_domain domain— thedev_pm_opsthat genpd installs into each attached device’sdev->pm_domain. This is the hook by which genpd intercepts runtime PM and system-sleep transitions (see The Device PM Core and dev_pm_ops, where PM-domain ops take precedence over type/class/bus/driver ops).struct list_head dev_list— the list of devices currently attached to this domain. Reference counting walks this list.struct list_head parent_links/child_links— the sub-domain graph. Agpd_linkties a parent domain to a child (sub-)domain;parent_linksenumerates this domain’s children,child_linksenumerates its parents (a domain can have several parents).atomic_t sd_count— “subdomain count”: the number of this domain’s sub-domains that are currently powered on. A domain cannot power off whilesd_count > 0.enum gpd_status status—GENPD_STATE_ONorGENPD_STATE_OFF.unsigned int device_count— how many devices are attached (not how many are active).int (*power_off)(struct generic_pm_domain *)/int (*power_on)(...)— the SoC-specific callbacks that actually toggle the regulator or hardware power switch. genpd never touches hardware directly; the backend driver (underdrivers/pmdomain/<vendor>/) supplies these.struct dev_power_governor *gov— the policy object that answers “is it worth powering down right now?” (see governors below).unsigned int flags— a bitfield ofGENPD_FLAG_*values that tune behaviour (GENPD_FLAG_ALWAYS_ON,GENPD_FLAG_IRQ_SAFE,GENPD_FLAG_RPM_ALWAYS_ON,GENPD_FLAG_CPU_DOMAIN,GENPD_FLAG_PM_CLK, …).struct genpd_power_state *states/state_count/state_idx— multiple idle states a domain may enter (analogous to CPU C-states), with per-state power-off/power-on latency and residency.state_idxis the state the domain will go to when it powers off.struct work_struct power_off_work— used to defer a power-off to thepm_wqworkqueue when it cannot run in the current context.- The trailing
union { mutex mlock; spinlock_t slock; raw_spinlock_t raw_slock; }— the domain lock can be a sleeping mutex (default) or a spinlock, chosen by whether the domain must be powered on/off from atomic context (GENPD_FLAG_IRQ_SAFE).
Each attached device gets a struct generic_pm_domain_data (pm_domain.h, line 244) hung off dev->power.subsys_data->domain_data. It embeds a pm_domain_data base (whose dev pointer and list_node thread it onto the domain’s dev_list) plus timing data, the CPU index (for CPU-PM domains), and the cached performance state. The helper dev_gpd_data(dev) recovers it.
Mechanical Walk-through: How genpd Sits on Runtime PM
genpd’s central trick is in pm_genpd_init() (core.c, line 2214): it populates the domain’s domain.ops with genpd’s own callbacks, including:
genpd->domain.ops.runtime_suspend = genpd_runtime_suspend;
genpd->domain.ops.runtime_resume = genpd_runtime_resume;
genpd->domain.ops.prepare = genpd_prepare;
genpd->domain.ops.suspend_noirq = genpd_suspend_noirq;
/* ... resume_noirq, freeze/thaw/poweroff/restore_noirq, complete ... */When a device is attached, genpd sets dev->pm_domain = &genpd->domain. Because the device PM core consults dev->pm_domain->ops before the driver’s own dev_pm_ops (the precedence rule in The Device PM Core and dev_pm_ops), every runtime-PM transition for that device now routes through genpd first.
The runtime-suspend path (genpd_runtime_suspend, core.c, line 1085) does, in order:
- Ask the governor’s
suspend_ok. If runtime PM is enabled and the governor’s->suspend_ok(dev)returns false (a PM-QoS latency constraint forbids suspending this device), return-EBUSYimmediately. - Call the driver’s real
runtime_suspendvia__genpd_runtime_suspend(dev), which itself re-walks type/class/bus/driver to find the actual device callback (genpd does not swallow the driver’s work — it brackets it). - Call
genpd_stop_dev— the optionaldev_ops.stop(e.g.pm_clk_suspendwhenGENPD_FLAG_PM_CLKis set, which gates the device’s functional clocks). - Try to power off the domain: take the domain lock and call
genpd_power_off(genpd, true, 0). This is where the collective decision happens.
The resume path (genpd_runtime_resume, line 1161) mirrors it: power the domain on first (genpd_power_on), then genpd_start_dev, then the driver’s __genpd_runtime_resume.
The reference counting that gates the rail
genpd_power_off(genpd, one_dev_on, depth) (core.c, line 825) is the heart of the framework. It refuses to power down unless all of the following hold:
- The domain is currently on and the system is not mid-suspend (
genpd->prepared_count == 0). - The domain is not
GENPD_FLAG_ALWAYS_ON/GENPD_FLAG_RPM_ALWAYS_ON, andatomic_read(&genpd->sd_count) == 0— no powered-on sub-domain depends on it. - Every child sub-domain is in its deepest state (
child->state_idx == child->state_count - 1). - Walking
dev_list, the count of not-suspended devices is acceptable. The loop incrementsnot_suspendedfor each device where!pm_runtime_suspended(pdd->dev). The checkif (not_suspended > 1 || (not_suspended == 1 && !one_dev_on)) return -EBUSY;is subtle: when this runs from a device’s ownruntime_suspend, that one device is in an intermediate state (not yet markedRPM_SUSPENDED), soone_dev_onis passedtrueto tolerate exactly one not-yet-suspended device — itself. - The governor’s
->power_down_ok(&genpd->domain)returns true.
Only then does it call _genpd_power_off(genpd, true) (which fires the GENPD_NOTIFY_PRE_OFF notifier, invokes the SoC’s genpd->power_off(), and on success fires GENPD_NOTIFY_OFF), set status = GENPD_STATE_OFF, and update accounting. Crucially, after powering this domain off it walks child_links and, for each parent, calls genpd_sd_counter_dec(parent) and then recursively genpd_power_off(parent, …) — so dropping the last device on a leaf domain can cascade all the way up, gating the parent rails too.
genpd_power_on(genpd, depth) (core.c, line 917) is the dual: it walks child_links upward first, incrementing each parent’s sd_count and recursively powering parents on, then calls _genpd_power_on() for this domain. The ordering guarantees a parent rail is live before its child draws from it.
genpd_sd_counter_dec/genpd_sd_counter_inc (core.c, lines 256/266) are thin wrappers over atomic_dec_and_test/atomic_inc; the decrementer WARN_ONs if the count is already zero (a balance bug) and returns whether the count hit zero.
Attaching Devices and Sub-domains
There are three ways a device joins a domain.
1. Imperative — pm_genpd_add_device
A backend driver that knows the topology can call pm_genpd_add_device(genpd, dev) (core.c, line 1832) directly. It takes gpd_list_lock, then genpd_add_device allocates the per-device generic_pm_domain_data, runs the optional attach_dev callback, increments device_count, links the device onto dev_list, and finally dev_pm_domain_set(dev, &genpd->domain) to point dev->pm_domain at the domain.
2. Declarative via Device Tree — the power-domains binding
The common case on ARM/ARM64. A consumer node references a provider with a power-domains phandle, and the provider declares how many cells the specifier uses with #power-domain-cells:
/* Provider: an SoC power-domain controller */
pd_controller: power-controller@12340000 {
compatible = "vendor,soc-pm-domains";
#power-domain-cells = <1>; /* one cell selects which domain */
};
/* Consumer: a UART that lives on domain index 3 */
serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
power-domains = <&pd_controller 3>; /* phandle + selector cell */
};When the UART driver probes, the driver core calls dev_pm_domain_attach(dev, true) (common.c, line 102), which first tries acpi_dev_pm_attach() (the x86 path) and, failing that, genpd_dev_pm_attach(dev) (core.c, line 2991). genpd_dev_pm_attach requires the node to reference exactly one power domain (devices with multiple domains must be attached by index separately); it then calls __genpd_dev_pm_attach (core.c, line 2906), which:
of_parse_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells", …)to read the specifier.genpd_get_from_provider()to resolve the specifier to ageneric_pm_domainregistered earlier by the provider viaof_genpd_add_provider_simple/_onecell.- If the provider is not yet registered, returns
driver_deferred_probe_check_state()— i.e. defer the consumer’s probe until the domain provider appears. This is why a missing power-domain provider shows up as endless-EPROBE_DEFER. genpd_add_device(pd, dev, base_dev)to attach.- Optionally sets a default performance state from
required-opps. - If
power_onwas requested,genpd_power_on(pd, 0)to bring the rail up so the device can be probed.
It returns 1 on a successful attach, 0 when there is no power domain to attach (so probe continues normally), and a negative error (often -EPROBE_DEFER) otherwise.
3. Sub-domains — pm_genpd_add_subdomain
pm_genpd_add_subdomain(parent, child) (core.c, line 2064) wires one domain as a sub-domain of another. genpd_add_subdomain (core.c, line 2004) allocates a gpd_link, links it onto parent->parent_links and child->child_links, and — if the child is currently on — bumps the parent’s sd_count so the parent cannot power off out from under it. There is a safety rule: a non-IRQ_SAFE parent may not have an IRQ_SAFE sub-domain, because an atomic-context power-off of the child could try to take the parent’s sleeping lock.
Governors: pm_domain_always_on_gov and simple_qos_governor
A governor is a tiny struct dev_power_governor with two predicates (pm_domain.h, line 122):
struct dev_power_governor {
bool (*power_down_ok)(struct dev_pm_domain *domain);
bool (*suspend_ok)(struct device *dev);
};power_down_ok is consulted by genpd_power_off to decide whether powering the rail down is worthwhile (the latency to power off and back on must be less than the time the domain will actually stay off); suspend_ok is consulted per device to decide whether a single device’s PM-QoS resume-latency constraint permits its runtime-suspend.
The framework ships three governors (governor.c, v6.12):
pm_domain_always_on_gov(line 416) — supplies onlysuspend_ok = default_suspend_ok; it deliberately leavespower_down_okNULL. With nopower_down_ok,genpd_power_offnever gets approval to drop the rail, so the domain stays powered on even when all devices are idle.pm_genpd_initadditionally OR-sGENPD_FLAG_RPM_ALWAYS_ONwhen this governor is used. Use this for a rail whose hardware cannot tolerate being gated (or where gating costs more than it saves) but whose individual devices should still runtime-suspend.simple_qos_governor(line 408) —suspend_ok = default_suspend_ok,power_down_ok = default_power_down_ok. This is the workhorse.default_power_down_ok(governor.c, line 340 →_default_power_down_ok) computes, for every device and every sub-domain, the minimum time the rail may be off given each consumer’s effective PM-QoS resume-latency constraint, subtracts the state’spower_on_latency_ns, and only approves the power-down if the rail will be off long enough to be worth the round-trip cost. It also picks the deepeststate_idxwhose residency that off-time can satisfy.pm_domain_cpu_gov(line 402, underCONFIG_CPU_IDLE) — for CPU power domains (GENPD_FLAG_CPU_DOMAIN); it additionally consults each online CPU’s next hrtimer (dev->next_hrtimer) to find the soonest wakeup and only enters a domain idle state whose residency that interval covers. This is how clusters of CPUs gate a shared cluster rail via the “last man standing” algorithm.
Passing gov = NULL to pm_genpd_init is allowed only when the domain has a single state; genpd_power_off then defaults state_idx to 0 and powers off whenever the reference counts permit, with no latency arbitration.
Failure Modes
- Device never powers down. Almost always the domain’s reference count never reaches the gating condition. Check that every device on the rail is actually runtime-suspended (
cat /sys/devices/.../power/runtime_statusshould readsuspendedfor all of them) — one chatty device that never reachesusage_count == 0keeps the whole rail up. See Runtime PM Usage Counters and Autosuspend for why a counter may be stuck. Also check/sys/kernel/debug/pm_genpd/for the live domain state andsd_count. -EPROBE_DEFERstorm at boot. The consumer probes before the domain provider driver has registered the domain.__genpd_dev_pm_attachreturnsdriver_deferred_probe_check_state(), which becomes-EPROBE_DEFER. The fix is provider ordering, not consumer code; if the provider never registers, the consumer is stuck deferring forever.- Parent rail won’t gate even though all leaves are off. A sub-domain still counted as “on” keeps
sd_count > 0. This usually means apm_genpd_add_subdomainwas done while the child was on but a later balance is wrong, or the child hasGENPD_FLAG_ALWAYS_ON. WARN_ONingenpd_sd_counter_dec—sd_countunderflowed, a genpd balance bug in a backend driver (a power-off without a matching prior power-on, or vice versa).- Lockdep splat with an
IRQ_SAFEmismatch.genpd_add_subdomainWARNs “Parent … of subdomain … must be IRQ safe” when an IRQ-safe child is added under a non-IRQ-safe parent — the atomic child cannot acquire the parent’s sleeping mutex.
Alternatives and When to Choose Them
Plain runtime PM suffices when each device owns its own independent power/clock with no sharing — the driver’s runtime_suspend simply gates its own resources and there is nothing collective to arbitrate. genpd is specifically the answer when a single hardware switch feeds several devices, which is the norm on SoCs but rare on PCs. On x86, the analogous mechanism is usually ACPI power resources driven by acpi_dev_pm_attach (tried first in dev_pm_domain_attach), so genpd is overwhelmingly an ARM/ARM64/RISC-V SoC story. For pure clock gating without a power island, the PM-clock framework (GENPD_FLAG_PM_CLK) can be layered into a genpd rather than used standalone.
Production Notes
genpd is the backbone of power management on essentially every modern ARM SoC: Qualcomm, MediaTek, Rockchip, Renesas, NXP i.MX, Tegra, Amlogic, and Apple Silicon all ship genpd backends under drivers/pmdomain/<vendor>/ in v6.12. The relocation of the core from drivers/base/power/domain.c to drivers/pmdomain/core.c (with vendor backends moved out of drivers/soc/) was a deliberate 6.6/6.7-era consolidation so the framework and its providers live together. Two operational details bite in the field: (1) late_initcall_sync(genpd_power_off_unused) (core.c, line 1262) powers off every domain with no in-use devices at the end of boot — the pd_ignore_unused kernel command-line flag disables this, which is the standard workaround when an un-described consumer (firmware, a co-processor) is silently using a rail the kernel thinks is idle; (2) genpd performance states (OPP tables attached to the domain) let a domain scale voltage/frequency the way cpufreq scales the CPU, aggregating the maximum requested state across all consumers.
See Also
- The Device PM Core and dev_pm_ops — genpd installs its ops into
dev->pm_domain, which the core consults before the driver’s ops - Runtime Power Management — the per-device layer genpd sits on top of and aggregates
- Runtime PM Usage Counters and Autosuspend — why a device’s usage counter may never reach zero, blocking the whole rail
- Wakeup Sources and Wakeirq —
GENPD_FLAG_ACTIVE_WAKEUPkeeps a domain on for a device used as a wakeup source - System Suspend and Resume Phases — genpd’s
*_noirqcallbacks participate in the system-sleep phase machine - Linux Power Management MOC — parent map (§4 Device Power Management Core)