- 框架概述
- 源码文件分布
- 整体架构:电源域树
- 核心数据结构
- 锁机制
- 引用计数与级联上电:genpd_power_on 递归流程
- 级联下电:genpd_power_off 流程
- 电源通知机制
- Governor:simple_qos 与 cpu governor
- 9.1 dev_power_governor 接口
- 9.2 simple_qos_governor
- 9.3 CPU domain governor
- 9.4 延迟预算计算流程
- Runtime PM 与 genpd 集成
- 10.1 genpd_runtime_suspend
- 10.2 genpd_runtime_resume
- 设备树 Binding
- 11.1 power-domains 属性解析
- 11.2 of_genpd_parse_idle_states
- 初始化流程:pm_genpd_init
- 性能状态管理
- 系统睡眠集成
- SCMI 电源域后端
- 调试接口:/sys/kernel/debug/pm_genpd/
- genpd 标志位汇总
- 关键设计决策与注意事项
- 电源域状态机详解
- 设备绑定与 device_link 机制
- QoS 约束传播机制
- genpd_governor_data 与缓存机制
- 系统挂起/恢复路径完整分析
- Rockchip 电源域驱动实现分析
- SCMI genpd 驱动完整分析
- PSCI 与 ARM CPU 电源管理协作
- genpd 设备生命周期管理
- Provider 注册与 sync_state 机制
- HW mode 硬件自动电源管理
- genpd 与 PM clock 框架集成
- 多电源域设备(dev_pm_domain_list)
- GDB 调试辅助脚本
- 典型使用模式与驱动开发指南
- 性能分析与常见问题排查
- 附录:关键函数索引
Generic Power Domain(genpd)是 Linux 内核为 SoC 芯片级电源域管理提供的通用框架,由 Rafael J. Wysocki 于 2011 年引入(版权归属 Renesas Electronics Corp.)。其核心思想是:将若干设备归属于同一个"电源域",由该域统一管理上下电时序,从而在所有设备均空闲时将整个域断电,在有设备唤醒时再级联重新上电。
genpd 框架解决了以下问题:
- 多设备共享同一电源轨(power rail)时的引用计数与上/下电时序协调
- 电源域之间的父子层级关系(parent / subdomain),上电需先上父域再上子域
- 与 Runtime PM(运行时电源管理)的无缝集成:设备
rpm_suspend触发域下电,rpm_resume触发域上电 - 多级空闲状态(retention、off 等),由 governor 决策最优档位
- 设备树中通过
power-domains属性声明设备归属 - 通过 SCMI 等固件接口将电源控制委托给片外固件
| 文件 | 说明 |
|---|---|
drivers/pmdomain/core.c |
genpd 核心实现(3946 行),历史路径注释为 drivers/base/power/domain.c |
drivers/pmdomain/governor.c |
PM domain governor 实现(simple_qos、cpu governor) |
include/linux/pm_domain.h |
所有公开数据结构与 API 声明 |
drivers/base/power/runtime.c |
Runtime PM 核心,与 genpd 回调对接 |
drivers/firmware/arm_scmi/power.c |
ARM SCMI 电源协议后端实现 |
drivers/pmdomain/arm/scmi_pm_domain.c |
SCMI genpd 驱动(将 SCMI power ops 适配为 genpd) |
drivers/pmdomain/rockchip/pm-domains.c |
Rockchip SoC 电源域驱动 |
drivers/pmdomain/*/ |
各 SoC 厂商具体实现(Rockchip、Renesas、Qualcomm 等) |
scripts/gdb/linux/genpd.py |
GDB 调试辅助脚本 |
注:
drivers/pmdomain/core.c文件开头第 3 行注释仍写的是旧路径:// * drivers/base/power/domain.c - Common code related to device power domains.这是历史遗留,实际文件已迁移到drivers/pmdomain/。
genpd 以树形结构组织所有电源域。父域(parent domain)必须在子域(subdomain/child domain)上电之前先上电,下电顺序相反。
全局链表 gpd_list
(gpd_list_lock 保护)
|
+------------------+------------------+
| | |
[always-on pd] [parent_pd_A] [parent_pd_B]
(GENPD_FLAG_ status=ON/OFF status=ON/OFF
ALWAYS_ON) sd_count=2 sd_count=0
parent_links dev_list
| | |
[child_pd_1] [child_pd_2] [dev_x]
child_links child_links
| |
[dev_a][dev_b] [dev_c][dev_d]
关键数据:
gpd_list(drivers/pmdomain/core.c:54):static LIST_HEAD(gpd_list),所有已注册的 genpd 挂载于此gpd_list_lock(drivers/pmdomain/core.c:55):static DEFINE_MUTEX(gpd_list_lock),保护全局链表genpd->parent_links:本域作为父域时,连接到子域的gpd_link链表genpd->child_links:本域作为子域时,连接到父域的gpd_link链表genpd->dev_list:归属本域的设备pm_domain_data链表genpd->sd_count:atomic_t,记录当前处于上电状态的子域数量
子域上电时,通过 genpd_sd_counter_inc() 增加父域的 sd_count;子域下电时,通过 genpd_sd_counter_dec() 减少。只有 sd_count == 0 且所有设备均已 RPM suspend,父域才可下电。
定义在 include/linux/pm_domain.h:194,是整个 genpd 框架的核心结构:
struct generic_pm_domain {
struct device dev; // 作为内核设备注册,bus = genpd_provider_bus_type
struct dev_pm_domain domain; // PM domain 操作表(ops)
struct list_head gpd_list_node; // 挂入全局 gpd_list
struct list_head parent_links; // 本域作为父域的子域链接
struct list_head child_links; // 本域作为子域的父域链接
struct list_head dev_list; // 归属设备链表
struct dev_power_governor *gov; // 关联的 governor
struct genpd_governor_data *gd; // governor 运行时数据
struct work_struct power_off_work; // 异步下电 workqueue 任务
struct fwnode_handle *provider; // OF provider fwnode
bool has_provider;
const char *name;
atomic_t sd_count; // 子域上电引用计数
enum gpd_status status; // GENPD_STATE_ON / GENPD_STATE_OFF
unsigned int device_count; // 归属设备数
unsigned int device_id; // 由 IDA 分配的唯一 ID
unsigned int suspended_count; // 系统睡眠期间已挂起设备数
unsigned int prepared_count; // 系统睡眠 prepare 阶段计数
unsigned int performance_state; // 聚合的最大性能状态
cpumask_var_t cpus; // CPU domain 专用:关联的 CPU mask
bool synced_poweroff; // 需要同步下电
bool stay_on; // 启动阶段保持上电(等待 sync_state)
enum genpd_sync_state sync_state; // sync_state 管理方式
// 核心回调
int (*power_off)(struct generic_pm_domain *domain);
int (*power_on)(struct generic_pm_domain *domain);
struct raw_notifier_head power_notifiers; // 上下电通知链
struct opp_table *opp_table;
int (*set_performance_state)(struct generic_pm_domain *genpd, unsigned int state);
struct gpd_dev_ops dev_ops; // start/stop 回调(用于 PM clock)
// HW mode 支持
int (*set_hwmode_dev)(struct generic_pm_domain *domain, struct device *dev, bool enable);
bool (*get_hwmode_dev)(struct generic_pm_domain *domain, struct device *dev);
// 设备附加/分离回调
int (*attach_dev)(struct generic_pm_domain *domain, struct device *dev);
void (*detach_dev)(struct generic_pm_domain *domain, struct device *dev);
unsigned int flags; // GENPD_FLAG_* 位域
struct genpd_power_state *states; // 空闲状态数组
void (*free_states)(...);
unsigned int state_count; // 空闲状态数量
unsigned int state_idx; // 当前/目标空闲状态索引
u64 on_time; // 累计上电时间(ns),用于 debugfs
u64 accounting_time; // 上次时间统计更新时刻
const struct genpd_lock_ops *lock_ops; // 锁操作函数指针
union {
struct mutex mlock; // 普通睡眠上下文
struct { spinlock_t slock; ... }; // IRQ safe 域
struct { raw_spinlock_t raw_slock; ... }; // CPU domain
};
};两个关键回调:
power_on(genpd):由后端驱动实现,执行物理上电操作(例如写寄存器、调用 SCMI 接口)power_off(genpd):执行物理下电操作
这两个回调在 _genpd_power_on()(drivers/pmdomain/core.c:831)和 _genpd_power_off()(drivers/pmdomain/core.c:882)中被调用,调用前后会触发 power_notifiers 通知链。
定义在 include/linux/pm_domain.h:177:
struct genpd_power_state {
const char *name; // 状态名称,如 "retention"、"off"
s64 power_off_latency_ns; // 进入此状态的延迟(ns),运行时动态更新
s64 power_on_latency_ns; // 从此状态恢复的延迟(ns),运行时动态更新
s64 residency_ns; // 最小驻留时间(ns):值得进入此状态的最短空闲时长
u64 usage; // 成功进入此状态的次数(统计)
u64 rejected; // 下电被拒绝的次数
u64 above; // 实际驻留时间 < target 的次数(进入过深)
u64 below; // 实际驻留时间 >= 更深状态 target 的次数(不够深)
struct fwnode_handle *fwnode; // 对应的 DT idle-state 节点
u64 idle_time; // 在此状态累计空闲时间(ns)
void *data; // 驱动私有数据
};典型的多档位配置(以 ARM big.LITTLE 系统为例):
states[0]: name="retention"
power_off_latency_ns=1000 (1 µs)
power_on_latency_ns=1000 (1 µs)
residency_ns=10000 (10 µs)
states[1]: name="off"
power_off_latency_ns=5000 (5 µs)
power_on_latency_ns=10000 (10 µs)
residency_ns=100000 (100 µs)
当没有在设备树或驱动中声明任何状态时,genpd_set_default_power_state()(drivers/pmdomain/core.c:2268 附近)会分配一个默认的单一 off 状态:
static int genpd_set_default_power_state(struct generic_pm_domain *genpd)
{
struct genpd_power_state *state;
state = kzalloc_obj(*state);
genpd->states = state;
genpd->state_count = 1;
genpd->free_states = genpd_free_default_power_state;
return 0;
}pm_domain_data(include/linux/pm_domain.h:280)是基础结构,嵌入到设备的 subsys_data->domain_data:
struct pm_domain_data {
struct list_head list_node; // 挂入 genpd->dev_list
struct device *dev; // 指向设备
};generic_pm_domain_data(include/linux/pm_domain.h:285)是扩展版本,包含 genpd 特有的设备运行时数据:
struct generic_pm_domain_data {
struct pm_domain_data base; // 必须是第一个字段,container_of 使用
struct gpd_timing_data *td; // suspend/resume 延迟统计与 QoS 数据
struct notifier_block nb; // QoS 约束变化通知回调
struct notifier_block *power_nb; // 域上下电通知回调
int cpu; // 若设备是 CPU,记录 CPU 编号,否则 -1
unsigned int performance_state; // 设备请求的性能状态
unsigned int default_pstate; // DT 声明的默认性能状态
unsigned int rpm_pstate; // suspend 时暂存的性能状态
unsigned int opp_token; // OPP 配置 token
bool hw_mode; // 是否启用 HW 自动电源控制模式
bool rpm_always_on; // 设备要求域始终上电
void *data; // 驱动私有数据
};通过以下内联函数访问(include/linux/pm_domain.h:301-308):
static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data *pdd)
{
return container_of(pdd, struct generic_pm_domain_data, base);
}
static inline struct generic_pm_domain_data *dev_gpd_data(struct device *dev)
{
return to_gpd_data(dev->power.subsys_data->domain_data);
}genpd_alloc_dev_data() 在设备加入域时分配该结构(drivers/pmdomain/core.c:1803),同时注册 QoS 通知回调:
gpd_data->nb.notifier_call = genpd_dev_pm_qos_notifier;
dev_pm_qos_add_notifier(dev, &gpd_data->nb, DEV_PM_QOS_RESUME_LATENCY);
// 见 drivers/pmdomain/core.c:1960-1961include/linux/pm_domain.h:260:
struct gpd_link {
struct generic_pm_domain *parent;
struct list_head parent_node; // 挂入 parent->parent_links
struct generic_pm_domain *child;
struct list_head child_node; // 挂入 child->child_links
/* 子域对父域的性能状态要求 */
unsigned int performance_state;
unsigned int prev_performance_state; // 回滚用
};当 pm_genpd_add_subdomain(parent, child) 被调用时,genpd_add_subdomain()(drivers/pmdomain/core.c:2143)分配一个 gpd_link 并双向插入两个域的链表:
link->parent = genpd;
list_add_tail(&link->parent_node, &genpd->parent_links); // 父域侧
link->child = subdomain;
list_add_tail(&link->child_node, &subdomain->child_links); // 子域侧
if (genpd_status_on(subdomain))
genpd_sd_counter_inc(genpd); // 子域已上电,父域 sd_count++注意加锁顺序(drivers/pmdomain/core.c:2168-2169):
genpd_lock(subdomain);
genpd_lock_nested(genpd, SINGLE_DEPTH_NESTING);先锁子域再嵌套锁父域,与 genpd_power_on/off 中的遍历顺序一致,防止死锁。
include/linux/pm_domain.h:271:
struct gpd_timing_data {
s64 suspend_latency_ns; // 设备 runtime suspend 延迟(动态测量更新)
s64 resume_latency_ns; // 设备 runtime resume 延迟
s64 effective_constraint_ns; // 经过计算的有效 QoS 约束(考虑子设备)
ktime_t next_wakeup; // 设备预报的下次唤醒时刻
bool constraint_changed; // QoS 约束发生变化,需重新计算
bool cached_suspend_ok; // governor suspend_ok 缓存结果
};suspend_latency_ns 和 resume_latency_ns 在 genpd_runtime_suspend/resume 中实际测量,如果测量值超过已记录值则更新(drivers/pmdomain/core.c:1256-1264,drivers/pmdomain/core.c:1334-1343)。
genpd 支持三种不同的锁原语,在 pm_genpd_init 时通过 genpd_lock_init()(drivers/pmdomain/core.c:2360)根据 flags 决定:
flag 判断 选用的锁 场景
---- ---- ----
GENPD_FLAG_CPU_DOMAIN raw_spinlock_t CPU idle 路径,不能用普通 spinlock
GENPD_FLAG_IRQ_SAFE spinlock_t 中断上下文安全
(默认) mutex 普通睡眠上下文
实现代码(drivers/pmdomain/core.c:2360-2371):
static void genpd_lock_init(struct generic_pm_domain *genpd)
{
if (genpd_is_cpu_domain(genpd)) {
raw_spin_lock_init(&genpd->raw_slock);
genpd->lock_ops = &genpd_raw_spin_ops;
} else if (genpd_is_irq_safe(genpd)) {
spin_lock_init(&genpd->slock);
genpd->lock_ops = &genpd_spin_ops;
} else {
mutex_init(&genpd->mlock);
genpd->lock_ops = &genpd_mtx_ops;
}
}通过统一宏进行访问(drivers/pmdomain/core.c:176-179):
#define genpd_lock(p) p->lock_ops->lock(p)
#define genpd_lock_nested(p, d) p->lock_ops->lock_nested(p, d)
#define genpd_lock_interruptible(p) p->lock_ops->lock_interruptible(p)
#define genpd_unlock(p) p->lock_ops->unlock(p)锁的嵌套获取(lock_nested)用于父子域层级遍历时避免 lockdep 误报。约定:先锁子域,再以 depth+1 嵌套锁父域。
重要约束(drivers/pmdomain/core.c:2158-2162):
if (!genpd_is_irq_safe(genpd) && genpd_is_irq_safe(subdomain)) {
WARN(1, "Parent %s of subdomain %s must be IRQ safe\n", ...);
return -EINVAL;
}IRQ safe 的子域要求其所有父域也必须是 IRQ safe,否则在中断上下文中可能持有 mutex 导致问题。
三种锁实现的核心差异:
mutex: 允许睡眠,线程阻塞时让出 CPU,适合有 I2C/SPI 操作的电源控制器
spinlock: 忙等,适合纯寄存器操作且需要从中断上下文访问
raw_spinlock: 关抢占+关中断,必须用于 CPU idle 路径(realtime 内核下也不可调度)
genpd_power_on()(drivers/pmdomain/core.c:1043)是级联上电的核心:
static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth)
{
struct gpd_link *link;
int ret = 0;
if (genpd_status_on(genpd)) // 已上电,直接返回
return 0;
genpd_reflect_residency(genpd); // 更新空闲状态驻留时间统计
// 遍历所有父域(child_links 是本域作为子域连接到父域的链表)
list_for_each_entry(link, &genpd->child_links, child_node) {
struct generic_pm_domain *parent = link->parent;
genpd_sd_counter_inc(parent); // 父域 sd_count++
genpd_lock_nested(parent, depth + 1);
ret = genpd_power_on(parent, depth + 1); // 递归上电父域
genpd_unlock(parent);
if (ret) {
genpd_sd_counter_dec(parent);
goto err;
}
}
ret = _genpd_power_on(genpd, true); // 调用本域的 power_on 回调
if (ret)
goto err;
genpd->status = GENPD_STATE_ON;
genpd_update_accounting(genpd); // 更新时间统计
return 0;
err:
// 回滚:对已上电的父域调用 genpd_power_off
list_for_each_entry_continue_reverse(link, &genpd->child_links, child_node) {
genpd_sd_counter_dec(link->parent);
genpd_lock_nested(link->parent, depth + 1);
genpd_power_off(link->parent, false, depth + 1);
genpd_unlock(link->parent);
}
return ret;
}递归调用流程示意(三层域:root_pd -> mid_pd -> leaf_pd):
genpd_power_on(leaf_pd, 0)
|-- genpd_sd_counter_inc(mid_pd) [mid_pd.sd_count: 0->1]
|-- genpd_power_on(mid_pd, 1) [递归]
| |-- genpd_sd_counter_inc(root_pd) [root_pd.sd_count: 0->1]
| |-- genpd_power_on(root_pd, 2) [递归]
| | |-- _genpd_power_on(root_pd) [调用 root_pd->power_on()]
| | |-- root_pd.status = ON
| |-- _genpd_power_on(mid_pd) [调用 mid_pd->power_on()]
| |-- mid_pd.status = ON
|-- _genpd_power_on(leaf_pd) [调用 leaf_pd->power_on()]
|-- leaf_pd.status = ON
_genpd_power_on() 的内部流程(drivers/pmdomain/core.c:831):
static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed)
{
// 1. 发出 GENPD_NOTIFY_PRE_ON 通知,失败则自动回滚发 GENPD_NOTIFY_OFF
ret = raw_notifier_call_chain_robust(&genpd->power_notifiers,
GENPD_NOTIFY_PRE_ON,
GENPD_NOTIFY_OFF, NULL);
// 2. 若没有 power_on 回调(软件虚拟域),跳过
if (!genpd->power_on) goto out;
// 3. 测量延迟(timed=true 且有 governor data 且非 fwnode 管理状态)
timed = timed && genpd->gd && !genpd->states[state_idx].fwnode;
if (timed) time_start = ktime_get();
ret = genpd->power_on(genpd); // 调用硬件上电
if (ret) goto err;
// 4. 若实际延迟超过记录值,更新并标记 max_off_time_changed
elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
if (elapsed_ns > genpd->states[state_idx].power_on_latency_ns) {
genpd->states[state_idx].power_on_latency_ns = elapsed_ns;
genpd->gd->max_off_time_changed = true;
}
out:
// 5. 发出 GENPD_NOTIFY_ON 通知,清除 synced_poweroff 标记
raw_notifier_call_chain(&genpd->power_notifiers, GENPD_NOTIFY_ON, NULL);
genpd->synced_poweroff = false;
return 0;
}sd_count 的语义:父域的 sd_count 大于 0 时,genpd_power_off() 会立即返回,拒绝下电。这保证了父域不会在任一子域仍然上电时被关断。
genpd_power_off()(drivers/pmdomain/core.c:956)在设备 RPM suspend 后被调用,条件检查非常严格:
static void genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
unsigned int depth)
{
// 以下任一条件成立则拒绝下电:
if (!genpd_status_on(genpd) // 已经下电
|| genpd->prepared_count > 0 // 系统睡眠 prepare 进行中
|| genpd_is_always_on(genpd) // GENPD_FLAG_ALWAYS_ON
|| genpd_is_rpm_always_on(genpd) // GENPD_FLAG_RPM_ALWAYS_ON
|| genpd->stay_on // 启动阶段保护
|| atomic_read(&genpd->sd_count) > 0) // 仍有子域处于上电状态
return;
// 子域必须全部处于最深空闲状态
list_for_each_entry(link, &genpd->parent_links, parent_node) {
struct generic_pm_domain *child = link->child;
if (child->state_idx < child->state_count - 1)
return;
}
// 统计未 suspend 的设备数量
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
if (!pm_runtime_suspended(pdd->dev) ||
irq_safe_dev_in_sleep_domain(pdd->dev, genpd))
not_suspended++;
if (to_gpd_data(pdd)->rpm_always_on) // 设备要求域保持上电
return;
}
// one_dev_on=true 时允许一个设备正处于 RPM suspend 中间态
if (not_suspended > 1 || (not_suspended == 1 && !one_dev_on))
return;
// 询问 governor 是否可以下电,并选择空闲状态
if (genpd->gov && genpd->gov->power_down_ok) {
if (!genpd->gov->power_down_ok(&genpd->domain))
return;
}
if (!genpd->gov)
genpd->state_idx = 0; // 无 governor 时使用最浅状态
// 执行实际下电
if (_genpd_power_off(genpd, true)) {
genpd->states[genpd->state_idx].rejected++;
return;
}
genpd->status = GENPD_STATE_OFF;
genpd_update_accounting(genpd);
genpd->states[genpd->state_idx].usage++;
// 级联:递减父域 sd_count,尝试父域下电
list_for_each_entry(link, &genpd->child_links, child_node) {
genpd_sd_counter_dec(link->parent);
genpd_lock_nested(link->parent, depth + 1);
genpd_power_off(link->parent, false, depth + 1);
genpd_unlock(link->parent);
}
}下电流程图:
设备 rpm_suspend 完成
|
genpd_runtime_suspend(dev)
|
|-- __genpd_runtime_suspend(dev) [调用设备驱动的 runtime_suspend]
|-- genpd_stop_dev(genpd, dev) [调用 dev_ops.stop,如 pm_clk_suspend]
|
|-- genpd_lock(genpd)
|-- genpd_power_off(genpd, true, 0)
| |
| +-- 条件检查(all pass)
| +-- governor->power_down_ok() [选择 state_idx]
| +-- _genpd_power_off(genpd) [调用 power_off 回调]
| +-- genpd->status = OFF
| +-- 级联检查父域...
|-- gpd_data->rpm_pstate = genpd_drop_performance_state(dev)
|-- genpd_unlock(genpd)
_genpd_power_off() 通知机制(drivers/pmdomain/core.c:882):
下电成功时发出 GENPD_NOTIFY_OFF;下电失败(power_off 回调返回非零)时发出 GENPD_NOTIFY_ON 通知(表示域仍然处于上电状态)。这是通过 raw_notifier_call_chain_robust 的自动回滚能力实现的。
异步下电:genpd_power_off_work_fn()(drivers/pmdomain/core.c:1149)通过 genpd_queue_power_off_work() 将下电操作排入 pm_wq 工作队列,用于系统启动后 genpd_power_off_unused() 关闭所有无设备域(late_initcall_sync,drivers/pmdomain/core.c:1392)。
genpd 提供了 raw_notifier_head power_notifiers 通知链,在上下电前后发出 4 种通知事件(include/linux/pm_domain.h:142-147):
enum genpd_notication {
GENPD_NOTIFY_PRE_OFF = 0, // 即将下电,回调可以返回错误阻止下电
GENPD_NOTIFY_OFF, // 已下电(或下电失败时恢复通知)
GENPD_NOTIFY_PRE_ON, // 即将上电,回调可以返回错误阻止上电
GENPD_NOTIFY_ON, // 已上电
};_genpd_power_on() 中的通知调用(drivers/pmdomain/core.c:839-843):
ret = raw_notifier_call_chain_robust(&genpd->power_notifiers,
GENPD_NOTIFY_PRE_ON,
GENPD_NOTIFY_OFF, NULL);使用 raw_notifier_call_chain_robust 的原因:若 PRE_ON 通知中某个回调返回 NOTIFY_BAD,则自动向已调用过 PRE_ON 的回调发送回滚通知 GENPD_NOTIFY_OFF。
消费者驱动通过 dev_pm_genpd_add_notifier(dev, nb) 注册(drivers/pmdomain/core.c:2063),dev_pm_genpd_remove_notifier() 注销(drivers/pmdomain/core.c:2109)。
每设备只允许一个通知回调(drivers/pmdomain/core.c:2078):
if (gpd_data->power_nb)
return -EEXIST;通知回调注册后保存在 gpd_data->power_nb,注销时通过 raw_notifier_chain_unregister 从链表中移除。
定义在 include/linux/pm_domain.h:155-159:
struct dev_power_governor {
bool (*system_power_down_ok)(struct dev_pm_domain *domain); // 系统睡眠时是否可下电
bool (*power_down_ok)(struct dev_pm_domain *domain); // 运行时是否可下电
bool (*suspend_ok)(struct device *dev); // 设备是否可 suspend
};内核提供三个预置 governor(均在 drivers/pmdomain/governor.c 底部声明):
| Governor | suspend_ok | power_down_ok | system_power_down_ok | 用途 |
|---|---|---|---|---|
simple_qos_governor |
default_suspend_ok |
default_power_down_ok |
无 | 通用 I/O 设备 |
pm_domain_always_on_gov |
default_suspend_ok |
无(域不下电) | 无 | 总线供电等 always-on 域 |
pm_domain_cpu_gov |
default_suspend_ok |
cpu_power_down_ok |
cpu_system_power_down_ok |
CPU domain |
drivers/pmdomain/governor.c:461-464:
struct dev_power_governor simple_qos_governor = {
.suspend_ok = default_suspend_ok,
.power_down_ok = default_power_down_ok,
};default_suspend_ok(dev)(drivers/pmdomain/governor.c:56)检查设备的 PM QoS resume latency 约束:
- 若约束未变化,返回缓存结果(
td->cached_suspend_ok) - 读取
__dev_pm_qos_resume_latency(dev)(单位 µs,转换为 ns) - 若设备有子设备(
!dev->power.ignore_children),递归取dev_update_qos_constraint()中各子设备的约束最小值 - 最终计算:
effective_constraint = constraint - suspend_latency - resume_latency - 若
effective_constraint <= 0,设备不允许 suspend
注意约束为 0 时的特殊语义(drivers/pmdomain/governor.c:79-80):
if (constraint_ns == 0)
return false; // 约束=0 表示"禁止 suspend"而 PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS 表示"无限制",设备可以 suspend。
default_power_down_ok(pd) 通过调用 _default_power_down_ok(pd, ktime_get())(drivers/pmdomain/governor.c:270)实现:
- 调用
update_domain_next_wakeup()收集所有设备和子域的下次唤醒时刻 - 如果设置了
GENPD_FLAG_MIN_RESIDENCY,从最深状态向上找第一个满足next_wakeup - now >= power_off_latency + residency的状态 - 遍历所有子域和设备,验证它们的
max_off_time_ns是否满足off_on_time_ns(下电+上电延迟总和) - 计算并缓存
gd->max_off_time_ns,通知父域刷新缓存
__default_power_down_ok() 中的核心逻辑(drivers/pmdomain/governor.c:256-258):
genpd->gd->max_off_time_ns = min_off_time_ns -
genpd->states[state].power_on_latency_ns;
return true;cpu_power_down_ok()(drivers/pmdomain/governor.c:347)增加了以下 CPU 特有检查:
- 先调用
_default_power_down_ok()验证 QoS 约束 - 收集 CPU QoS latency 约束(
wakeup_constraint和global_constraint) - 遍历所有在线 CPU 的
cpuidle_devices->next_hrtimer,取最早唤醒时刻 - 计算空闲时长
idle_duration_ns,从 governor 已选state_idx开始向上找第一个满足驻留时间和唤醒延迟约束的状态 - 调用
cpus_peek_for_pending_ipi(genpd->cpus)检查是否有待处理 IPI(drivers/pmdomain/governor.c:419)
最后在进入时记录时间戳(drivers/pmdomain/governor.c:423-424):
genpd->gd->last_enter = now;
genpd->gd->reflect_residency = true;从 OFF 状态恢复时,genpd_reflect_residency()(drivers/pmdomain/core.c:320)计算实际驻留时间,并更新 above/below 统计。
cpu_system_power_down_ok()(drivers/pmdomain/governor.c:428)用于系统睡眠阶段,找满足 cpu_wakeup_latency_qos_limit() 约束的最深状态。
设备的 PM QoS resume_latency 约束
|
v
default_suspend_ok(dev)
effective_constraint_ns = qos_ns - suspend_ns - resume_ns
|
v (所有设备均已 suspend)
_default_power_down_ok(pd, now)
|
|-- update_domain_next_wakeup() 聚合所有唤醒时刻
|
|-- 对每个子域:sd_max_off_ns >= off_on_time_ns ?
|-- 对每个设备:effective_constraint_ns > off_on_time_ns ?
|
+-- min_off_time_ns = min(all constraints)
|
+-- gd->max_off_time_ns = min_off_time_ns - power_on_latency_ns
|
v
选定 state_idx,调用 _genpd_power_off()
完整实现在 drivers/pmdomain/core.c:1214-1280,在 pm_genpd_init 时被注册为 genpd->domain.ops.runtime_suspend(drivers/pmdomain/core.c:2419):
static int genpd_runtime_suspend(struct device *dev)
{
// 1. 检查 governor->suspend_ok,不满足 QoS 约束则返回 -EBUSY
suspend_ok = genpd->gov ? genpd->gov->suspend_ok : NULL;
if (runtime_pm && suspend_ok && !suspend_ok(dev))
return -EBUSY;
// 2. 测量并调用设备驱动的 runtime_suspend 回调
if (td && runtime_pm) time_start = ktime_get();
ret = __genpd_runtime_suspend(dev); // 调用 bus/class/driver->pm->runtime_suspend
if (ret) return ret;
// 3. 调用 dev_ops.stop(通常是 pm_clk_suspend,关闭设备时钟)
ret = genpd_stop_dev(genpd, dev);
if (ret) { __genpd_runtime_resume(dev); return ret; }
// 4. 更新 suspend 延迟测量值
if (td && runtime_pm) {
elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start));
if (elapsed_ns > td->suspend_latency_ns) {
td->suspend_latency_ns = elapsed_ns;
genpd->gd->max_off_time_changed = true;
td->constraint_changed = true;
}
}
// 5. IRQ safe 设备 + 非 IRQ safe 域:不下电域,直接返回
if (irq_safe_dev_in_sleep_domain(dev, genpd))
return 0;
// 6. 尝试域下电(genpd_power_off 内部会检查是否所有设备都已 suspend)
genpd_lock(genpd);
genpd_power_off(genpd, true, 0); // one_dev_on=true:允许本设备仍在中间态
gpd_data->rpm_pstate = genpd_drop_performance_state(dev); // 释放性能状态
genpd_unlock(genpd);
return 0;
}__genpd_runtime_suspend() 的回调查找优先级(drivers/pmdomain/core.c:1164-1181):
dev->type->pm->runtime_suspend
dev->class->pm->runtime_suspend
dev->bus->pm->runtime_suspend
dev->driver->pm->runtime_suspend
drivers/pmdomain/core.c:1290-1359:
static int genpd_runtime_resume(struct device *dev)
{
// 1. 非 IRQ safe 路径:恢复性能状态(在 suspend 时暂存在 rpm_pstate)
if (!irq_safe_dev_in_sleep_domain(dev, genpd)) {
genpd_lock(genpd);
genpd_restore_performance_state(dev, gpd_data->rpm_pstate);
// 2. 级联上电域
ret = genpd_power_on(genpd, 0);
genpd_unlock(genpd);
if (ret) return ret;
}
// 3. 调用 dev_ops.start(通常是 pm_clk_resume,重启设备时钟)
ret = genpd_start_dev(genpd, dev);
// 4. 测量并调用设备驱动的 runtime_resume 回调
if (timed) time_start = ktime_get();
ret = __genpd_runtime_resume(dev);
if (timed && elapsed_ns > td->resume_latency_ns) {
td->resume_latency_ns = elapsed_ns;
genpd->gd->max_off_time_changed = true;
td->constraint_changed = true;
}
return 0;
}Runtime PM 与 genpd 调用链总览:
pm_runtime_put(dev)
|
v
rpm_suspend(dev) [drivers/base/power/runtime.c]
|
v
dev->pm_domain->ops.runtime_suspend(dev)
|
v (已被 pm_genpd_init 注册)
genpd_runtime_suspend(dev) [drivers/pmdomain/core.c:1214]
|-- governor->suspend_ok()
|-- driver->pm->runtime_suspend()
|-- genpd_stop_dev() / pm_clk_suspend()
|-- genpd_power_off()
|-- governor->power_down_ok()
|-- genpd->power_off() [硬件/固件操作]
|-- 级联父域 genpd_power_off()
设备树中的典型声明:
/* 电源域控制器节点 */
power: power-controller@12340000 {
compatible = "example,power-ctrl";
reg = <0x12340000 0x1000>;
#power-domain-cells = <1>; // 1 个 cell 作为域 ID
};
/* 使用电源域的设备 */
uart0: serial@10000000 {
compatible = "example,uart";
reg = <0x10000000 0x1000>;
power-domains = <&power 3>; // 连接到 power 控制器的域 ID=3
};
/* 多电源域设备 */
gpu: gpu@20000000 {
power-domains = <&power 5>, <&power 6>; // 两个域
power-domain-names = "core", "mem";
};
解析流程(drivers/pmdomain/core.c:3188 附近):
ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
"#power-domain-cells", index, &pd_args);__genpd_dev_pm_attach()(drivers/pmdomain/core.c:3180 附近)完整流程:
- 解析第
index个power-domainsphandle 参数 - 调用
genpd_get_from_provider(&pd_args)(drivers/pmdomain/core.c:2896)查找 genpd provider 并获得对应generic_pm_domain - 调用
genpd_add_device(pd, dev, base_dev)将设备加入域 - 设置
dev->pm_domain->detach = genpd_dev_pm_detach - 可选:调用
genpd_power_on(pd, 0)立即上电
Provider 注册有两种方式(include/linux/pm_domain.h:455-458):
// 方式一:单一域,节点 np 对应唯一一个 genpd
int of_genpd_add_provider_simple(struct device_node *np,
struct generic_pm_domain *genpd);
// 方式二:多域,通过 index 从数组中选取
int of_genpd_add_provider_onecell(struct device_node *np,
struct genpd_onecell_data *data);genpd_onecell_data 结构(include/linux/pm_domain.h:448 附近):
struct genpd_onecell_data {
struct generic_pm_domain **domains; // 域指针数组
unsigned int num_domains;
genpd_xlate_t xlate; // 可选自定义映射函数
};默认的 genpd_xlate_onecell() 直接以 pd_args->args[0] 为数组下标(drivers/pmdomain/core.c:2594-2613)。
genpd_get_from_provider() 查找流程(drivers/pmdomain/core.c:2896-2918):
static struct generic_pm_domain *genpd_get_from_provider(
const struct of_phandle_args *genpdspec)
{
struct generic_pm_domain *genpd = ERR_PTR(-ENOENT);
struct of_genpd_provider *provider;
mutex_lock(&of_genpd_mutex);
list_for_each_entry(provider, &of_genpd_providers, link) {
if (provider->node == genpdspec->np)
genpd = provider->xlate(genpdspec, provider->data);
if (!IS_ERR(genpd))
break;
}
mutex_unlock(&of_genpd_mutex);
return genpd;
}Provider 通过 genpd_add_provider() 注册(drivers/pmdomain/core.c:2621),注销通过 of_genpd_del_provider()(drivers/pmdomain/core.c:2846)。
drivers/pmdomain/core.c:3461,用于从设备树解析空闲状态参数:
int of_genpd_parse_idle_states(struct device_node *dn,
struct genpd_power_state **states, int *n)内部调用 genpd_iterate_idle_states()(drivers/pmdomain/core.c:3413),通过 of_for_each_phandle 遍历 domain-idle-states 属性引用的所有节点,筛选兼容性为 domain-idle-state 且 available 的节点,调用 genpd_parse_state()(drivers/pmdomain/core.c:3380 附近)解析各属性:
// entry-latency-us 转换为 ns 存入 power_off_latency_ns
genpd_state->power_off_latency_ns = 1000LL * entry_latency;
// exit-latency-us 转换为 ns 存入 power_on_latency_ns
genpd_state->power_on_latency_ns = 1000LL * exit_latency;
// min-residency-us 转换为 ns 存入 residency_ns
genpd_state->residency_ns = 1000LL * residency;对应的 DTS 节点格式(需引用 domain-idle-states 定义):
power: power-ctrl@12340000 {
#power-domain-cells = <1>;
domain-idle-states = <&DOMAIN_RETENTION &DOMAIN_OFF>;
};
DOMAIN_RETENTION: domain-retention {
compatible = "domain-idle-state";
entry-latency-us = <1>; // power_off_latency_ns = 1000
exit-latency-us = <2>; // power_on_latency_ns = 2000
min-residency-us = <10>; // residency_ns = 10000
};
DOMAIN_OFF: domain-off {
compatible = "domain-idle-state";
entry-latency-us = <5>;
exit-latency-us = <15>;
min-residency-us = <100>;
};
pm_genpd_init()(drivers/pmdomain/core.c:2394)是 genpd 驱动必须调用的初始化入口:
int pm_genpd_init(struct generic_pm_domain *genpd,
struct dev_power_governor *gov, bool is_off)初始化步骤(drivers/pmdomain/core.c:2402-2463):
1. 初始化链表:parent_links, child_links, dev_list, power_notifiers
2. genpd_lock_init():根据 flags 选择锁类型
3. 设置 gov、初始化 power_off_work
4. atomic_set(&genpd->sd_count, 0)
5. genpd->status = is_off ? GENPD_STATE_OFF : GENPD_STATE_ON
6. genpd_set_stay_on():若 DT provider 且初始上电,设 stay_on=true
7. genpd->sync_state = GENPD_SYNC_STATE_OFF
8. 注册 PM domain 操作表(domain.ops):
- runtime_suspend = genpd_runtime_suspend
- runtime_resume = genpd_runtime_resume
- prepare = genpd_prepare
- suspend_noirq = genpd_suspend_noirq
- resume_noirq = genpd_resume_noirq
- freeze_noirq = genpd_freeze_noirq
- thaw_noirq = genpd_thaw_noirq
- poweroff_noirq = genpd_poweroff_noirq
- restore_noirq = genpd_restore_noirq
- complete = genpd_complete
- start = genpd_dev_pm_start
9. 若 GENPD_FLAG_PM_CLK:设置 dev_ops.stop/start = pm_clk_suspend/resume
10. 若 gov == pm_domain_always_on_gov:自动添加 GENPD_FLAG_RPM_ALWAYS_ON
11. 检查 always-on 域必须初始上电(否则返回 -EINVAL)
12. genpd_alloc_data():分配 governor_data、设置默认状态、初始化设备对象
13. 加入 gpd_list 全局链表
14. genpd_debug_add():创建 debugfs 条目
genpd 支持性能状态(OPP)聚合,多个设备/子域对同一父域投票,父域取最大值。
核心函数 _genpd_set_performance_state()(drivers/pmdomain/core.c:463):
设备调用 dev_pm_genpd_set_performance_state(dev, state)
|
v
genpd_set_performance_state(dev, state)
|-- gpd_data->performance_state = state
|-- _genpd_reeval_performance_state(genpd, state)
| 遍历 dev_list 和 parent_links,取所有投票最大值
|
v
_genpd_set_performance_state(genpd, state, depth)
|
|-- 上调时(state > current):先更新父域,再更新本域
| list_for_each_entry(child_links) -> _genpd_set_parent_state()
| genpd_xlate_performance_state() [OPP 跨域转换]
| 递归 _genpd_set_performance_state(parent, ...)
|
|-- genpd->set_performance_state(genpd, state) [硬件操作]
|
|-- 下调时(state < current):先更新本域,再更新父域(逆序)
上调与下调的顺序不同,是为了保证电源轨的电压/频率切换时序安全(先提升父域才能提升子域,降低顺序相反)。
_genpd_reeval_performance_state()(drivers/pmdomain/core.c:355)遍历策略:
- 遍历
dev_list中所有设备的pd_data->performance_state,取最大值 - 遍历
parent_links中所有子域的link->performance_state(子域对本域的投票),取最大值
注意 link->performance_state 不同于 link->child->performance_state:前者是子域换算到父域坐标系的值,后者是子域内部设备的投票值。
系统 suspend/resume 时,genpd 通过 genpd_switch_state()(drivers/pmdomain/core.c:1735)进行同步上下电:
Suspend 阶段(noirq 级别):
genpd_suspend_noirq(dev) [core.c:1582]
|-- genpd_finish_suspend(dev, pm_generic_suspend_noirq, ...)
| |-- suspend_noirq(dev) [实际 suspend 设备]
| |-- 若 active_wakeup && device_awake_path: 跳过下电
| |-- genpd_stop_dev() [关闭设备时钟]
| |-- genpd->suspended_count++
| |-- genpd_sync_power_off(genpd, true, 0)
| 检查 suspended_count == device_count
| 使用 gov->system_power_down_ok() 选择最深状态
| 默认选 state_count-1(最深状态)
| 调用 _genpd_power_off(genpd, false) [不测量延迟]
| 级联父域
注意 system_power_down_ok 与运行时 power_down_ok 的区别:系统睡眠时若没有 governor 提供该回调,默认选择最深状态(drivers/pmdomain/core.c:1432-1433):
} else {
/* Default to the deepest state. */
genpd->state_idx = genpd->state_count - 1;
}而运行时没有 governor 时选择最浅状态(state_idx = 0)。这是因为系统睡眠期间不关心唤醒延迟。
Resume 阶段(noirq 级别):
genpd_resume_noirq(dev) [core.c:1635]
|-- genpd_finish_resume(dev, pm_generic_resume_noirq)
| |-- 若 active_wakeup && device_awake_path: 直接调用 resume_noirq
| |-- genpd_sync_power_on(genpd, true, 0)
| 级联父域 genpd_sync_power_on
| 调用 _genpd_power_on(genpd, false)
| genpd->status = GENPD_STATE_ON
| |-- genpd->suspended_count--
| |-- genpd_start_dev() [恢复设备时钟]
| |-- pm_generic_resume_noirq(dev)
complete 阶段(drivers/pmdomain/core.c:1714):
static void genpd_complete(struct device *dev)
{
pm_generic_complete(dev);
genpd_lock(genpd);
genpd->prepared_count--;
if (!genpd->prepared_count)
genpd_queue_power_off_work(genpd); // resume 完成后尝试下电
genpd_unlock(genpd);
}dev_pm_genpd_suspend/resume()(drivers/pmdomain/core.c:1770-1788)是供 syscore 阶段和 s2idle(suspend-to-idle)使用的同步接口,不需要经过完整的 noirq 流程。
Hibernate 路径:genpd_freeze_noirq/thaw_noirq/poweroff_noirq/restore_noirq 本质上都调用了 genpd_finish_suspend/resume,只是传入不同的 generic 回调(freeze/thaw、poweroff/restore)。
ARM SCMI(System Control and Management Interface)是 ARM 定义的固件接口标准,允许内核通过消息传递向 SCP(System Control Processor)等固件请求电源域控制。
源码:drivers/firmware/arm_scmi/power.c
协议命令(第 19-25 行):
enum scmi_power_protocol_cmd {
POWER_DOMAIN_ATTRIBUTES = 0x3, // 查询域属性
POWER_STATE_SET = 0x4, // 设置电源状态
POWER_STATE_GET = 0x5, // 获取当前电源状态
POWER_STATE_NOTIFY = 0x6, // 订阅状态变化通知
POWER_DOMAIN_NAME_GET = 0x8, // 获取扩展域名称(v3.0+)
};域属性标志位(第 37-41 行):
#define SUPPORTS_STATE_SET_NOTIFY(x) ((x) & BIT(31)) // 支持状态变化通知
#define SUPPORTS_STATE_SET_ASYNC(x) ((x) & BIT(30)) // 支持异步设置
#define SUPPORTS_STATE_SET_SYNC(x) ((x) & BIT(29)) // 支持同步设置
#define SUPPORTS_EXTENDED_NAMES(x) ((x) & BIT(27)) // 支持扩展名称(v3.0+)初始化流程(scmi_power_protocol_init,第 322 行):
1. scmi_power_attributes_get() -> 获取域数量、统计地址
2. 对每个 domain 调用 scmi_power_domain_attributes_get()
-> 填充 power_dom_info:支持标志、域名称
3. 若协议版本 >= 3.0 且支持扩展名称,调用 extended_name_get()
SCMI genpd 驱动(位于 drivers/firmware/arm_scmi/)将 SCMI power 协议暴露的操作接口注册为 genpd 的 power_on/power_off 回调:
static const struct scmi_power_proto_ops power_proto_ops = {
.num_domains_get = scmi_power_num_domains_get,
.name_get = scmi_power_name_get,
.state_set = scmi_power_state_set, // 对应 power_off/power_on
.state_get = scmi_power_state_get,
};SCMI 消息传输(以 scmi_power_state_set 为例,第 153 行):
static int scmi_power_state_set(const struct scmi_protocol_handle *ph,
u32 domain, u32 state)
{
struct scmi_power_set_state *st;
ret = ph->xops->xfer_get_init(ph, POWER_STATE_SET, sizeof(*st), 0, &t);
st = t->tx.buf;
st->flags = cpu_to_le32(0); // 同步模式
st->domain = cpu_to_le32(domain);
st->state = cpu_to_le32(state); // 0=OFF, 1=ON(或固件定义的其他状态码)
ret = ph->xops->do_xfer(ph, t); // 发送消息并等待响应
ph->xops->xfer_put(ph, t);
return ret;
}通知机制:SCMI 支持域状态变化的异步通知(POWER_STATE_NOTIFY 命令)。当 SCP 主动改变某域电源状态时,内核侧可通过 scmi_power_state_notify_payld 获知:
struct scmi_power_state_notify_payld {
__le32 agent_id;
__le32 domain_id;
__le32 power_state;
};SCMI 版本演进:SCMI_PROTOCOL_SUPPORTED_VERSION = 0x30001(第 17 行),即支持到 v3.1。v3.0 新增了 POWER_DOMAIN_NAME_GET 命令,允许使用超过 16 字节短名称限制的扩展名称。
调试 FS 在 genpd_debug_init()(drivers/pmdomain/core.c:3925)中初始化,使用 late_initcall 确保所有 genpd 都已注册后再创建:
static int __init genpd_debug_init(void)
{
genpd_debugfs_dir = debugfs_create_dir("pm_genpd", NULL);
debugfs_create_file("pm_genpd_summary", S_IRUGO, genpd_debugfs_dir,
NULL, &summary_fops);
list_for_each_entry(genpd, &gpd_list, gpd_list_node)
genpd_debug_add(genpd);
return 0;
}
late_initcall(genpd_debug_init);每个 genpd 在 /sys/kernel/debug/pm_genpd/<domain_name>/ 下创建以下文件(genpd_debug_add,drivers/pmdomain/core.c:3899):
| 文件 | 说明 | 实现函数 |
|---|---|---|
current_state |
当前状态:on 或 off-N(N 为 state_idx) |
status_show (drivers/pmdomain/core.c:3720) |
sub_domains |
子域名称列表 | sub_domains_show (drivers/pmdomain/core.c:3747) |
idle_states |
各空闲状态统计表 | idle_states_show (drivers/pmdomain/core.c:3764) |
active_time |
上电累计时间(ms) | active_time_show (drivers/pmdomain/core.c:3805) |
total_idle_time |
各状态空闲时间之和(ms) | total_idle_time_show (drivers/pmdomain/core.c:3829) |
devices |
归属设备列表 | devices_show (drivers/pmdomain/core.c:3860) |
perf_state |
当前聚合性能状态(仅有 set_performance_state 时) | perf_state_show (drivers/pmdomain/core.c:3877) |
pm_genpd_summary 输出格式(drivers/pmdomain/core.c:3702-3703):
domain status children performance
/device runtime status managed by
------------------------------------------------------------------------------
pd_usb on pd_usb_phy 0
/sys/bus/platform/drivers/... active managed
pd_gpu off-1 - 100
/sys/bus/platform/drivers/... suspended managed
idle_states 文件输出格式(drivers/pmdomain/core.c:3775):
State Time Spent(ms) Usage Rejected Above Below
S0 12345 100 3 5 2
S1 4567 50 1 2 8
各字段含义:
Usage:成功进入该状态的次数Rejected:governor 或 power_off 失败导致被拒绝的次数Above:实际驻留时间不足(进入过深)的次数Below:本可进入更深状态但选了此状态(不够深)的次数
定义在 include/linux/pm_domain.h:125-135:
| 标志 | 值 | 含义 |
|---|---|---|
GENPD_FLAG_PM_CLK |
BIT(0) | 使用 PM clock 框架管理设备时钟 |
GENPD_FLAG_IRQ_SAFE |
BIT(1) | power_on/off 不睡眠,可在中断上下文调用 |
GENPD_FLAG_ALWAYS_ON |
BIT(2) | 域永远上电,即使系统睡眠 |
GENPD_FLAG_ACTIVE_WAKEUP |
BIT(3) | 有唤醒源的设备时保持上电 |
GENPD_FLAG_CPU_DOMAIN |
BIT(4) | CPU 电源域,使用 raw_spinlock,特殊的 last-man-standing 算法 |
GENPD_FLAG_RPM_ALWAYS_ON |
BIT(5) | 运行时保持上电,系统睡眠时可下电 |
GENPD_FLAG_MIN_RESIDENCY |
BIT(6) | governor 考虑最小驻留时间选择最优状态 |
GENPD_FLAG_OPP_TABLE_FW |
BIT(7) | OPP 表由固件提供,非 DT |
GENPD_FLAG_DEV_NAME_FW |
BIT(8) | 域名由固件提供,使用 IDA 生成唯一名称 |
GENPD_FLAG_NO_SYNC_STATE |
BIT(9) | 跳过 genpd 内部的 sync_state 支持 |
GENPD_FLAG_NO_STAY_ON |
BIT(10) | 不等待 sync_state 就允许初始下电 |
设备绑定时的 PD_FLAG_* 标志(include/linux/pm_domain.h:44-48):
| 标志 | 含义 |
|---|---|
PD_FLAG_NO_DEV_LINK |
不为电源域创建 device_link |
PD_FLAG_DEV_LINK_ON |
创建时携带 DL_FLAG_RPM_ACTIVE,上电供应方 |
PD_FLAG_REQUIRED_OPP |
按索引分配 required OPP |
PD_FLAG_ATTACH_POWER_ON |
attach 时立即上电 |
PD_FLAG_DETACH_POWER_OFF |
detach 时下电 |
genpd->stay_on 用于防止启动阶段在 sync_state 之前对已上电的域进行不必要的下电。对于 OF provider,pm_genpd_init(is_off=false) 时若没有 GENPD_FLAG_NO_STAY_ON,则设置 stay_on=true。
sync_state 完成后调用 of_genpd_sync_state()(drivers/pmdomain/core.c:3502),对属于该 provider 的所有 genpd 清除 stay_on 并尝试下电。
_genpd_set_performance_state() 中(drivers/pmdomain/core.c:472-497):
- 上调时:先通知父域提升性能状态,确保电源轨电压足够,再提升本域
- 下调时:先降低本域性能状态,再降低父域(避免父域在本域仍高频运行时断电)
若设备是 irq_safe(power.irq_safe=true)但其所属域不是 IRQ safe,则调用 irq_safe_dev_in_sleep_domain() 后 genpd_runtime_suspend 会在步骤 5 处直接返回 0(不触发域下电)。这意味着该设备可以 RPM suspend,但其域不会因此下电。WARN_ONCE 会提示这是次优配置(drivers/pmdomain/core.c:208)。
drivers/pmdomain/core.c:821,用于处理异步下电回调返回 0(表示成功发起)但实际下电失败的情形。调用者需手动调用此函数纠正 rejected/usage 统计计数:
void pm_genpd_inc_rejected(struct generic_pm_domain *genpd, unsigned int state_idx)
{
genpd_lock(genpd);
genpd->states[genpd->state_idx].rejected++;
genpd->states[genpd->state_idx].usage--;
genpd_unlock(genpd);
}genpd_power_off_unused()(drivers/pmdomain/core.c:1372)以 late_initcall_sync 注册,在所有驱动 probe 完成后遍历所有已注册 genpd,对无设备且无子域在用的域执行下电。可通过内核命令行参数 pd_ignore_unused 禁用此行为(drivers/pmdomain/core.c:1361-1367):
static bool pd_ignore_unused;
static int __init pd_ignore_unused_setup(char *__unused)
{
pd_ignore_unused = true;
return 1;
}
__setup("pd_ignore_unused", pd_ignore_unused_setup);当子域和父域使用不同的 OPP 表时,genpd_xlate_performance_state()(drivers/pmdomain/core.c:400)通过 dev_pm_opp_xlate_performance_state() 将子域的性能状态值映射到父域的等效状态值。
genpd 的状态定义极为简洁,只有两个枚举值(include/linux/pm_domain.h:137-140):
enum gpd_status {
GENPD_STATE_ON = 0, /* PM domain is on */
GENPD_STATE_OFF, /* PM domain is off */
};但通过 state_idx 字段区分不同的"off 子状态"(如 retention、deep-off 等)。从外部看,只要 status == GENPD_STATE_OFF,state_idx 就指示当前所处的具体空闲状态。
完整状态转换图:
+-------------------+
pm_genpd_init(is_off=false) |
+---------+ |
| v |
| +----------+ |
| | ON |<-----------+
| +----------+ |
| | |
| [genpd_power_off] |
| governor->power_down_ok |
| 所有设备已 suspend |
| sd_count == 0 |
| | |
| v |
| +----------+ |
| | OFF-[N] | | [genpd_power_on]
| | (N=0..n) | | 设备 runtime resume
| +----------+ |
| | |
+--------+------------------+
genpd->status: GENPD_STATE_ON <-> GENPD_STATE_OFF
genpd->state_idx: 0..state_count-1 (OFF 期间有效)
状态转换的触发时机:
| 触发事件 | 方向 | 相关函数 |
|---|---|---|
| 设备 rpm_resume | OFF -> ON | genpd_runtime_resume -> genpd_power_on |
| 设备 rpm_suspend(最后一个) | ON -> OFF | genpd_runtime_suspend -> genpd_power_off |
| 系统 suspend noirq | ON -> OFF | genpd_suspend_noirq -> genpd_sync_power_off |
| 系统 resume noirq | OFF -> ON | genpd_resume_noirq -> genpd_sync_power_on |
| syscore / s2idle | ON <-> OFF | dev_pm_genpd_suspend/resume -> genpd_switch_state |
| 启动关闭未用域 | ON -> OFF | genpd_power_off_unused |
| sync_state 完成 | ON -> OFF | of_genpd_sync_state |
genpd_status_on() 宏(drivers/pmdomain/core.c:181):
#define genpd_status_on(genpd) (genpd->status == GENPD_STATE_ON)这是判断域状态的唯一方式,贯穿整个 genpd 框架。
genpd_update_accounting() 时间统计(drivers/pmdomain/core.c:297-317):
static void genpd_update_accounting(struct generic_pm_domain *genpd)
{
now = ktime_get_mono_fast_ns();
delta = now - genpd->accounting_time;
/* 若当前为 ON,说明刚退出 OFF,更新 idle_time */
if (genpd->status == GENPD_STATE_ON)
genpd->states[genpd->state_idx].idle_time += delta;
else
/* 若当前为 OFF,说明刚退出 ON,更新 on_time */
genpd->on_time += delta;
genpd->accounting_time = now;
}注意:这个函数在状态转换之前调用,所以"当前状态"是切换前的状态,统计的是切换前状态的持续时间。
genpd 通过 dev_pm_domain_attach() 系列函数将设备与电源域绑定,同时为每个电源域关系创建 device_link,以在设备模型层面建立供需关系。
dev_pm_domain_attach_list() 批量绑定多个电源域(通过 dev_pm_domain_attach_data):
dev_pm_domain_attach_list(dev, data, list)
|
+-- for each pd_name in data->pd_names:
| __genpd_dev_pm_attach(dev, base_dev, index, power_on)
| |-- of_parse_phandle_with_args(dev->of_node, "power-domains", ...)
| |-- genpd_get_from_provider(&pd_args)
| |-- genpd_add_device(genpd, dev, base_dev)
| |-- genpd_power_on() 若 PD_FLAG_ATTACH_POWER_ON
|
+-- for each attached pd:
dev_pm_domain_create_link(genpd_dev, dev, dl_flags)
device_link 标志位映射(drivers/pmdomain/core.c 中 genpd_add_device 附近):
PD_FLAG_NO_DEV_LINK -> 不创建 device_link
PD_FLAG_DEV_LINK_ON -> DL_FLAG_RPM_ACTIVE | DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS
(默认) -> DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS
DL_FLAG_PM_RUNTIME 保证:当消费者设备 resume 时,供应方(genpd 设备)必须先 resume;消费者 suspend 后,供应方才可 suspend。
dev_pm_domain_attach_data 结构(include/linux/pm_domain.h:50-54):
struct dev_pm_domain_attach_data {
const char * const *pd_names; // 电源域名称数组(对应 power-domain-names)
const u32 num_pd_names; // 名称数组长度
const u32 pd_flags; // PD_FLAG_* 标志
};dev_pm_domain_list 结构(include/linux/pm_domain.h:56-61):
struct dev_pm_domain_list {
struct device **pd_devs; // genpd 设备指针数组
struct device_link **pd_links; // device_link 指针数组
u32 *opp_tokens; // OPP 配置 token 数组
u32 num_pds; // 绑定的域数量
};调用方在 probe 结束时通过 dev_pm_domain_detach_list() 解绑。
QoS 约束从设备传播到电源域的完整链路:
用户空间 / 内核驱动
|
v
dev_pm_qos_add_request(dev, DEV_PM_QOS_RESUME_LATENCY, value_us)
|
v
dev_pm_qos_update_request() 触发 notifier
|
v
genpd_dev_pm_qos_notifier() [drivers/pmdomain/core.c:1103]
|-- 遍历设备及其所有父设备
|-- 将 td->constraint_changed = true
|-- 将 genpd->gd->max_off_time_changed = true
|
v (下次 genpd_runtime_suspend 时)
governor->suspend_ok(dev)
|-- default_suspend_ok()
|-- 重新计算 td->effective_constraint_ns
|
v (若该设备是最后一个 suspend)
governor->power_down_ok(pd)
|-- _default_power_down_ok()
|-- 验证 min_off_time_ns > off_on_time_ns
genpd_dev_pm_qos_notifier() 的传播路径(drivers/pmdomain/core.c:1103-1143):
static int genpd_dev_pm_qos_notifier(struct notifier_block *nb, ...)
{
gpd_data = container_of(nb, struct generic_pm_domain_data, nb);
dev = gpd_data->base.dev;
for (;;) {
spin_lock_irq(&dev->power.lock);
pdd = dev->power.subsys_data->domain_data;
if (pdd) {
td = to_gpd_data(pdd)->td;
if (td) {
td->constraint_changed = true; // 标记设备约束变化
genpd = dev_to_genpd(dev);
}
}
spin_unlock_irq(&dev->power.lock);
if (!IS_ERR(genpd)) {
genpd_lock(genpd);
genpd->gd->max_off_time_changed = true; // 标记域需重新计算
genpd_unlock(genpd);
}
dev = dev->parent; // 向上传播到父设备
if (!dev || dev->power.ignore_children)
break;
}
return NOTIFY_DONE;
}这里向上遍历父设备的原因:子设备的 QoS 约束通过 dev_update_qos_constraint() 被父设备的 default_suspend_ok() 考虑。父设备的约束变化可能影响祖父设备,因此需要沿树向上传播。
dev_pm_genpd_set_next_wakeup()(include/linux/pm_domain.h:326):允许设备预告下次唤醒时刻,供 governor 做更精准的状态选择(GENPD_FLAG_MIN_RESIDENCY 场景):
void dev_pm_genpd_set_next_wakeup(struct device *dev, ktime_t next)
{
// 存储到 gpd_data->td->next_wakeup
// governor 在 update_domain_next_wakeup() 中收集
}genpd_governor_data 是 governor 运行时状态的容器(include/linux/pm_domain.h:166-175):
struct genpd_governor_data {
s64 max_off_time_ns; // 当前可安全下电的最大时间(ns),-1 表示无限制
bool max_off_time_changed; // 需要重新计算 max_off_time_ns
ktime_t next_wakeup; // 聚合的下次唤醒时刻(MIN_RESIDENCY 使用)
ktime_t next_hrtimer; // CPU domain governor 使用
ktime_t last_enter; // 进入 OFF 状态的时刻(用于 reflect_residency)
bool reflect_residency; // 是否需要在下次唤醒时统计驻留时间
bool cached_power_down_ok; // power_down_ok 的缓存结果
bool cached_power_down_state_idx; // 缓存的 state_idx
};缓存失效触发链:
设备 QoS 约束变化
|
v
genpd_dev_pm_qos_notifier()
genpd->gd->max_off_time_changed = true
|
v
_default_power_down_ok()
if (!gd->max_off_time_changed) {
// 使用缓存
genpd->state_idx = gd->cached_power_down_state_idx;
return gd->cached_power_down_ok;
}
// 重新计算
...
gd->max_off_time_changed = false;
gd->cached_power_down_ok = result;
|
// 父域缓存也需要失效(子域变化影响父域)
v
list_for_each_entry(link, &genpd->child_links) {
pgd->max_off_time_changed = true;
}
这个多级缓存机制避免了每次下电决策都重新遍历整个域树,在设备数量多时显著提升性能。
系统睡眠路径涉及多个不同的"noirq"阶段,genpd 为每个阶段都实现了对应的回调。
完整系统睡眠阶段映射:
系统 suspend 阶段 genpd 回调
------------------ ----------------
prepare genpd_prepare
genpd->prepared_count++ 阻止运行时下电
suspend pm_generic_suspend(直接调用)
suspend_noirq genpd_suspend_noirq
调用 pm_generic_suspend_noirq
genpd_stop_dev() 关闭设备时钟
suspended_count++
genpd_sync_power_off() 同步下电域
(系统休眠中)
resume_noirq genpd_resume_noirq
genpd_sync_power_on() 同步上电域
suspended_count--
genpd_start_dev() 恢复设备时钟
调用 pm_generic_resume_noirq
resume pm_generic_resume(直接调用)
complete genpd_complete
prepared_count--
若 prepared_count==0,排队下电任务
Hibernate 特有路径:
Freeze 阶段(进入 hibernate snapshot):
genpd_freeze_noirq -> genpd_finish_suspend(pm_generic_freeze_noirq, ...)
genpd_poweroff_noirq -> genpd_finish_suspend(pm_generic_poweroff_noirq, ...)
Restore 阶段(从 hibernate 恢复):
genpd_thaw_noirq -> genpd_finish_resume(pm_generic_thaw_noirq)
genpd_restore_noirq -> genpd_finish_resume(pm_generic_restore_noirq)
wakeup path 优化(GENPD_FLAG_ACTIVE_WAKEUP):
对于设置了 GENPD_FLAG_ACTIVE_WAKEUP 的域,若设备处于唤醒路径(device_awake_path(dev) 为真)且非带外唤醒(!device_out_band_wakeup(dev)),则 genpd_finish_suspend 跳过 genpd_stop_dev 和域下电(drivers/pmdomain/core.c:1554-1556),保持域上电以保证唤醒时序。
genpd_sync_power_off 与 genpd_power_off 的差异:
| 特性 | genpd_sync_power_off |
genpd_power_off |
|---|---|---|
| 调用上下文 | 系统 noirq 阶段 | 运行时 RPM |
| 状态选择 | 默认最深状态 | governor 决策 |
| 延迟测量 | 不测量(timed=false) |
测量(timed=true) |
| 设备检查 | suspended_count == device_count |
pm_runtime_suspended() |
| 锁使用 | 可选(use_lock 参数) |
必须持锁 |
Rockchip 电源域驱动(drivers/pmdomain/rockchip/pm-domains.c)是最复杂也最具代表性的 genpd 后端实现之一,支持从 RK3036 到 RK3588 几乎所有 Rockchip SoC。
rockchip_pm_domain(drivers/pmdomain/rockchip/pm-domains.c:93):
struct rockchip_pm_domain {
struct generic_pm_domain genpd; // 嵌入 genpd 基类
const struct rockchip_domain_info *info; // 域静态配置
struct rockchip_pmu *pmu; // 指向 PMU 控制器
int num_qos;
struct regmap **qos_regmap; // QoS 寄存器 regmap(用于保存/恢复)
u32 *qos_save_regs[MAX_QOS_REGS_NUM]; // QoS 寄存器保存缓冲区
int num_clks;
struct clk_bulk_data *clks; // 域内时钟(上电前需使能)
struct device_node *node;
struct regulator *supply; // 可选的外部稳压器
};rockchip_domain_info(drivers/pmdomain/rockchip/pm-domains.c:45):
struct rockchip_domain_info {
const char *name;
int pwr_mask; // 电源控制寄存器位掩码
int status_mask; // 电源状态寄存器位掩码
int req_mask; // 总线空闲请求位掩码
int idle_mask; // 总线空闲状态位掩码
int ack_mask; // 总线空闲确认位掩码
bool active_wakeup; // 是否需要 active wakeup 支持
bool need_regulator; // 是否需要外部稳压器
int pwr_w_mask; // write-enable 掩码(RK3399 双寄存器模式)
int req_w_mask; // 总线空闲请求 write-enable 掩码
// ...偏移量字段...
};Rockchip PMU 的下电时序(以 RK3399 为例)较为复杂,需要三个阶段:
1. 请求总线空闲:
regmap_write(pmu->regmap, req_offset, req_mask | req_w_mask)
等待 idle_ack:poll(ack_offset & ack_mask)
2. 保存 QoS 寄存器(下电会导致 QoS 寄存器复位):
regmap_read(qos_regmap[i], QOS_PRIORITY, &qos_save_regs[i])
...
3. 写 PMU 电源控制寄存器:
regmap_write(pmu->regmap, pwr_offset, pwr_w_mask)
等待 status_mask:poll(status_offset & status_mask == 0)
上电时序相反,最后还需要恢复 QoS 寄存器。
rockchip_pmu_block()(drivers/pmdomain/rockchip/pm-domains.c:266)允许 DRAM 控制器驱动在进行 DRAM DVFS 时阻止 PMU 域切换,防止 ARM TF-A(Trust Firmware A)与 Linux 内核同时操作 PMU 寄存器:
int rockchip_pmu_block(void)
{
mutex_lock(&dmc_pmu_mutex);
mutex_lock(&pmu->mutex); // 阻止所有 idle 请求
for (i = 0; i < pmu->genpd_data.num_domains; i++) {
pd = to_rockchip_pd(genpd);
clk_bulk_enable(pd->num_clks, pd->clks); // 保持时钟使能
}
return 0;
}Rockchip 使用一系列宏(DOMAIN_M、DOMAIN_M_O_R 等)描述每款 SoC 的每个电源域,例如 RK3399 GPU 域:
// drivers/pmdomain/rockchip/pm-domains.c 中类似:
DOMAIN_RK3399("gpu", BIT(1), BIT(1), BIT(0), false),
// name pwr status req wakeup这些宏展开后直接构成 struct rockchip_domain_info 数组,由 rockchip_pm_add_one_domain() 在 probe 时遍历并为每项调用 pm_genpd_init()。
SCMI genpd 驱动(drivers/pmdomain/arm/scmi_pm_domain.c)是 genpd 后端的最简洁实现,将 SCMI power 协议的 state_set 直接映射为 power_on/power_off。
scmi_pm_domain 结构(drivers/pmdomain/arm/scmi_pm_domain.c:16):
struct scmi_pm_domain {
struct generic_pm_domain genpd; // 嵌入 genpd
const struct scmi_protocol_handle *ph; // SCMI 协议句柄
const char *name;
u32 domain; // SCMI 电源域 ID
};
#define to_scmi_pd(gpd) container_of(gpd, struct scmi_pm_domain, genpd)power_on/power_off 实现(drivers/pmdomain/arm/scmi_pm_domain.c:25-40):
static int scmi_pd_power(struct generic_pm_domain *domain, u32 state)
{
struct scmi_pm_domain *pd = to_scmi_pd(domain);
return power_ops->state_set(pd->ph, pd->domain, state);
}
static int scmi_pd_power_on(struct generic_pm_domain *domain)
{
return scmi_pd_power(domain, SCMI_POWER_STATE_GENERIC_ON);
}
static int scmi_pd_power_off(struct generic_pm_domain *domain)
{
return scmi_pd_power(domain, SCMI_POWER_STATE_GENERIC_OFF);
}probe 流程(drivers/pmdomain/arm/scmi_pm_domain.c:42-123):
scmi_pm_domain_probe()
|-- power_ops = handle->devm_protocol_get(SCMI_PROTOCOL_POWER)
|-- num_domains = power_ops->num_domains_get(ph)
|-- 为每个域:
| power_ops->state_get(ph, i, &state) // 获取当前状态
| 若 state==ON:state_set(ON) // 重新声明使用,防止固件误关
| scmi_pd->genpd.power_off = scmi_pd_power_off
| scmi_pd->genpd.power_on = scmi_pd_power_on
| scmi_pd->genpd.flags = GENPD_FLAG_ACTIVE_WAKEUP
| pm_genpd_init(&scmi_pd->genpd, NULL, state==OFF)
| domains[i] = &scmi_pd->genpd
|-- of_genpd_add_provider_onecell(np, scmi_pd_data)
注意初始状态的处理:若 SCMI 报告域当前为 ON,则传 is_off=false 给 pm_genpd_init,这会设置 stay_on=true,防止在 sync_state 之前被错误关断。
ARM 平台上,CPU 电源域通常由 PSCI(Power State Coordination Interface)管理,genpd 通过 GENPD_FLAG_CPU_DOMAIN 标志与 PSCI 协作。
CPU domain 由 ARM PSCI cpuidle 驱动通过 dt_idle_pd_alloc() 或等效机制创建,并注册到 genpd:
arch/arm64/kernel/cpuidle.c(或 drivers/cpuidle/cpuidle-psci.c)
|
|-- 调用 psci_pd_init() 等函数
|-- 注册带 GENPD_FLAG_CPU_DOMAIN | GENPD_FLAG_IRQ_SAFE 的 genpd
|-- 设置 governor = pm_domain_cpu_gov
|-- power_off = psci_pd_power_off (调用 psci_cpu_off 或 cluster_off)
|-- power_on = NULL(由 CPU reset vector 完成,不需要显式回调)
CPU domain 使用 cpumask 跟踪哪些 CPU 仍在线。当一个 CPU 进入 idle 时:
cpuidle -> pm_runtime_put(cpu_dev)
|
v
genpd_runtime_suspend(cpu_dev)
|-- governor->suspend_ok() [检查 CPU QoS]
|-- genpd_power_off(cpu_pd, ...)
| cpumask 中所有 CPU 都已 suspend?
| 是 -> _genpd_power_off() -> psci_pd_power_off()
| -> PSCI_CPU_SUSPEND / CLUSTER_OFF
| 否 -> 仅 suspend 本 CPU,不下电域
CPU mask 通过 genpd_update_cpumask() 在设备 attach/detach 时维护,递归传播到父域(drivers/pmdomain/core.c:1874-1894):
static void genpd_update_cpumask(struct generic_pm_domain *genpd,
int cpu, bool set, unsigned int depth)
{
// 先递归更新父域的 cpumask
list_for_each_entry(link, &genpd->child_links, child_node) {
genpd_lock_nested(parent, depth + 1);
genpd_update_cpumask(parent, cpu, set, depth + 1);
genpd_unlock(parent);
}
// 再更新本域
if (set)
cpumask_set_cpu(cpu, genpd->cpus);
else
cpumask_clear_cpu(cpu, genpd->cpus);
}cpu_power_down_ok() 在决定下电前调用 cpus_peek_for_pending_ipi(genpd->cpus) 检查是否有待处理的 IPI(drivers/pmdomain/governor.c:419):
if (cpus_peek_for_pending_ipi(genpd->cpus))
return false;这防止了在有 IPI 等待处理时关断 CPU 域,避免 IPI 丢失或延迟过大。
PSCI CPU_SUSPEND 命令中的 power_state 参数需要编码电源域的状态:
power_state[31] = 1(扩展状态格式)或 0
power_state[27:26] = StateType(0=retention, 1=powerdown)
power_state[25:24] = PowerLevel(0=CPU, 1=Cluster, ...)
power_state[15:0] = StateID(实现定义)
这些编码通常通过 DT 的 arm,psci-suspend-param 属性指定给 genpd 的 genpd_power_state.fwnode 中。当状态有 fwnode 时(即来自 DT),_genpd_power_on/off() 不进行延迟测量(timed=false)。
genpd_add_device()(drivers/pmdomain/core.c:1923):
genpd_add_device(genpd, dev, base_dev)
|-- genpd_alloc_dev_data(dev, gd)
| kzalloc generic_pm_domain_data + gpd_timing_data
| 设置 td->constraint_changed = true
| 设置 td->effective_constraint_ns = NO_CONSTRAINT
| dev->power.subsys_data->domain_data = &gpd_data->base
|
|-- gpd_data->cpu = genpd_get_cpu(genpd, base_dev)
|-- gpd_data->hw_mode = genpd->get_hwmode_dev(genpd, dev) 若存在
|-- genpd->attach_dev(genpd, dev) 若存在
|
|-- genpd_lock(genpd)
|-- genpd_set_cpumask(genpd, gpd_data->cpu)
|-- genpd->device_count++
|-- gd->max_off_time_changed = true
|-- list_add_tail(&gpd_data->base.list_node, &genpd->dev_list)
|-- genpd_unlock(genpd)
|
|-- dev_pm_domain_set(dev, &genpd->domain) 设置 dev->pm_domain
|-- dev_pm_qos_add_notifier(dev, &gpd_data->nb, RESUME_LATENCY)
genpd_remove_device()(drivers/pmdomain/core.c:1986):
genpd_remove_device(genpd, dev)
|-- dev_pm_qos_remove_notifier() 先移除 QoS 通知
|-- genpd_lock(genpd)
|-- 检查 prepared_count == 0 系统睡眠中不允许移除
|-- genpd->device_count--
|-- genpd_clear_cpumask(genpd, gpd_data->cpu)
|-- list_del_init(&pdd->list_node)
|-- genpd_unlock(genpd)
|-- dev_pm_domain_set(dev, NULL) 清除 dev->pm_domain
|-- genpd->detach_dev(genpd, dev) 若存在
|-- genpd_free_dev_data(dev, gpd_data)
dev_pm_opp_clear_config(gpd_data->opp_token)
kfree(gpd_data->td)
kfree(gpd_data)
dev_pm_put_subsys_data(dev)
pm_genpd_remove()(drivers/pmdomain/core.c:2516):
移除条件:!has_provider && parent_links 为空 && device_count == 0
genpd_remove(genpd)
|-- 检查 has_provider(必须先移除 provider)
|-- 检查 parent_links 空 && device_count == 0
|-- 清除所有 child_links(断开与父域的连接)
|-- list_del(&genpd->gpd_list_node)
|-- genpd_debug_remove(genpd)
|-- cancel_work_sync(&genpd->power_off_work)
|-- genpd_free_data(genpd)
put_device(&genpd->dev)
ida_free(&genpd_ida, genpd->device_id)
free_cpumask_var(genpd->cpus)
genpd->free_states(genpd->states, ...)
kfree(genpd->gd)
of_genpd_add_provider_simple()(drivers/pmdomain/core.c:2670):
of_genpd_add_provider_simple(np, genpd)
|-- 检查 genpd_bus_registered(总线必须已初始化)
|-- 检查 genpd_present(genpd)(域必须已注册)
|-- genpd->dev.of_node = np
|-- 判断 sync_state 管理方式:
| 若 np 没有对应设备且 !GENPD_FLAG_NO_SYNC_STATE:
| genpd->sync_state = GENPD_SYNC_STATE_SIMPLE
| device_set_node(&genpd->dev, fwnode) 设置 sync_state 回调触发
| 否则:
| dev_set_drv_sync_state(dev, genpd_sync_state)
|-- device_add(&genpd->dev)
|-- dev_pm_opp_of_add_table(&genpd->dev) 若有 set_performance_state
|-- genpd_add_provider(np, genpd_xlate_simple, genpd)
|-- genpd->provider = fwnode
|-- genpd->has_provider = true
genpd->sync_state 三种取值(include/linux/pm_domain.h:149-153):
enum genpd_sync_state {
GENPD_SYNC_STATE_OFF = 0, // 无 sync_state 支持
GENPD_SYNC_STATE_SIMPLE, // simple provider:直接触发 genpd 下电
GENPD_SYNC_STATE_ONECELL, // onecell provider:通过 of_genpd_sync_state 触发
};genpd_provider_sync_state()(drivers/pmdomain/core.c:3527):
static void genpd_provider_sync_state(struct device *dev)
{
switch (genpd->sync_state) {
case GENPD_SYNC_STATE_SIMPLE:
// 清除 stay_on 并尝试下电
genpd_lock(genpd);
genpd->stay_on = false;
genpd_power_off(genpd, false, 0);
genpd_unlock(genpd);
break;
case GENPD_SYNC_STATE_ONECELL:
// 通过 of_genpd_sync_state 查找并处理该 provider 的所有域
of_genpd_sync_state(dev->of_node);
break;
}
}of_genpd_sync_state()(drivers/pmdomain/core.c:3502)遍历全局 gpd_list,对所有 provider == of_fwnode_handle(np) 的域清除 stay_on 并尝试下电。
genpd_bus_init()(drivers/pmdomain/core.c:3559)使用 core_initcall 级别初始化(早于 subsys_initcall),确保在任何 genpd provider 注册之前 bus 已就绪:
static int __init genpd_bus_init(void)
{
device_register(&genpd_provider_bus);
bus_register(&genpd_provider_bus_type);
bus_register(&genpd_bus_type);
driver_register(&genpd_provider_drv);
genpd_bus_registered = true;
return 0;
}
core_initcall(genpd_bus_init);某些 SoC 支持硬件自动管理电源域(HW mode),无需软件干预即可根据硬件信号自动上下电。genpd 通过 hw_mode 字段和 set_hwmode_dev/get_hwmode_dev 回调支持此功能。
dev_pm_genpd_set_hwmode() 和 dev_pm_genpd_get_hwmode()(include/linux/pm_domain.h:329-330):
int dev_pm_genpd_set_hwmode(struct device *dev, bool enable);
bool dev_pm_genpd_get_hwmode(struct device *dev);当设备设置为 HW mode(gpd_data->hw_mode = true)后:
- 设备的 RPM 状态不再直接控制域的上下电
- 域的上下电由硬件信号(如 hardware idle request)自动触发
genpd_runtime_suspend/resume仍然调用,但跳过某些 genpd 相关逻辑
典型用例:GPU 或视频编解码器在繁忙时自动保持上电,空闲时由硬件自动下电,无需频繁的 RPM suspend/resume 开销。
当 genpd 设置了 GENPD_FLAG_PM_CLK 时,框架自动使用 PM clock 管理设备时钟(drivers/pmdomain/core.c:2432-2435):
if (genpd->flags & GENPD_FLAG_PM_CLK) {
genpd->dev_ops.stop = pm_clk_suspend;
genpd->dev_ops.start = pm_clk_resume;
}pm_clk_suspend(dev) 和 pm_clk_resume(dev) 来自 drivers/base/power/clock_ops.c,它们管理通过 pm_clk_add() 注册的设备时钟:
genpd_runtime_suspend(dev)
|-- __genpd_runtime_suspend(dev) [驱动回调]
|-- genpd_stop_dev(genpd, dev)
pm_clk_suspend(dev)
|-- clk_disable(clk) 关闭每个 PM clock
|-- clk_unprepare(clk)
genpd_runtime_resume(dev)
|-- genpd_start_dev(genpd, dev)
pm_clk_resume(dev)
|-- clk_prepare(clk) 重新准备
|-- clk_enable(clk) 使能每个 PM clock
|-- __genpd_runtime_resume(dev) [驱动回调]
PM clock 框架允许设备驱动通过 devm_pm_clk_create(dev) + pm_clk_add(dev, clk_name) 声明设备使用的时钟,设备 suspend/resume 时由 genpd 统一管理,驱动无需自己调用 clk_enable/disable。
现代 SoC 中,一个设备可能需要同时属于多个电源域(例如 GPU 同时属于"core"域和"memory"域)。genpd 通过 dev_pm_domain_attach_list() 支持这种场景。
典型使用模式(驱动 probe 中):
static int my_driver_probe(struct platform_device *pdev)
{
struct my_priv *priv;
struct dev_pm_domain_attach_data attach_data = {
.pd_names = (const char *[]){"core", "mem"},
.num_pd_names = 2,
.pd_flags = PD_FLAG_NO_DEV_LINK,
};
int ret;
ret = dev_pm_domain_attach_list(&pdev->dev, &attach_data, &priv->pd_list);
if (ret < 0)
return ret;
// priv->pd_list->pd_devs[0] 是 "core" 域的 genpd 设备
// priv->pd_list->pd_devs[1] 是 "mem" 域的 genpd 设备
return 0;
}
static void my_driver_remove(struct platform_device *pdev)
{
dev_pm_domain_detach_list(priv->pd_list);
}实现细节:dev_pm_domain_attach_list() 内部对每个域名调用 __genpd_dev_pm_attach(),并收集返回的 genpd 设备指针和 device_link 指针到 dev_pm_domain_list 结构中。若任一域绑定失败,函数自动清理已绑定的域。
scripts/gdb/linux/genpd.py 提供了在 GDB 中检查 genpd 状态的命令,对于调试电源管理问题非常有用。
主要命令:
(gdb) lx-genpd-summary
输出类似 debugfs pm_genpd_summary 的信息,但直接读取内核内存,无需目标系统运行。
脚本通过遍历内核全局变量 gpd_list 实现,需要内核编译时包含调试信息(CONFIG_DEBUG_INFO)。
手动检查方法(无脚本时):
(gdb) p gpd_list
(gdb) p *(struct generic_pm_domain *)((unsigned long)gpd_list.next - offsetof(struct generic_pm_domain, gpd_list_node))
(gdb) p genpd->status
(gdb) p genpd->state_idx
(gdb) p genpd->sd_count.counter
适用于纯寄存器控制、无时钟管理需求的简单电源域控制器:
struct my_pd {
struct generic_pm_domain genpd;
void __iomem *base;
/* ... */
};
static int my_pd_power_on(struct generic_pm_domain *domain)
{
struct my_pd *pd = container_of(domain, struct my_pd, genpd);
writel(PWR_ON_BIT, pd->base + PWR_CTRL);
return readl_poll_timeout(pd->base + PWR_STATUS, val,
val & PWR_DONE, 10, 1000);
}
static int my_pd_power_off(struct generic_pm_domain *domain)
{
struct my_pd *pd = container_of(domain, struct my_pd, genpd);
writel(0, pd->base + PWR_CTRL);
return 0;
}
static int my_pmc_probe(struct platform_device *pdev)
{
struct my_pd *pd;
struct genpd_onecell_data *data;
pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL);
pd->genpd.name = "my_pd";
pd->genpd.power_on = my_pd_power_on;
pd->genpd.power_off = my_pd_power_off;
/* 解析并注册空闲状态(可选) */
of_genpd_parse_idle_states(pdev->dev.of_node,
&pd->genpd.states,
&pd->genpd.state_count);
pm_genpd_init(&pd->genpd, &simple_qos_governor, /* is_off= */ false);
/* 注册为 OF provider */
data = devm_kzalloc(...);
data->domains[0] = &pd->genpd;
data->num_domains = 1;
return of_genpd_add_provider_onecell(pdev->dev.of_node, data);
}/* 注册父域和子域 */
pm_genpd_init(&parent_pd.genpd, &simple_qos_governor, false);
pm_genpd_init(&child_pd.genpd, &simple_qos_governor, false);
/* 建立层级关系(必须在两者均已 init 后调用) */
pm_genpd_add_subdomain(&parent_pd.genpd, &child_pd.genpd);注意:pm_genpd_add_subdomain 要求:若父域处于 OFF 状态,子域也必须处于 OFF 状态(drivers/pmdomain/core.c:2171-2174),否则返回 -EINVAL。
大多数设备驱动不需要直接调用 genpd API,只需在设备树中声明 power-domains 属性即可。内核在 of_platform_device_create() 时自动调用 dev_pm_domain_attach() 完成绑定。
驱动只需正确使用 Runtime PM:
static int my_device_probe(struct platform_device *pdev)
{
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev); // 保证初始化时域上电
/* ... 初始化硬件 ... */
pm_runtime_put(&pdev->dev);
return 0;
}
static int my_device_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
static const struct dev_pm_ops my_pm_ops = {
SET_RUNTIME_PM_OPS(my_runtime_suspend, my_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};查看所有域的状态:
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary查看特定域的空闲状态统计:
cat /sys/kernel/debug/pm_genpd/<domain_name>/idle_states查看域的活跃时间比例:
# 上电时间
cat /sys/kernel/debug/pm_genpd/<domain_name>/active_time
# 总空闲时间
cat /sys/kernel/debug/pm_genpd/<domain_name>/total_idle_time若 Rejected 计数持续增大,可能原因:
power_off回调返回非零(硬件忙)- governor
power_down_ok总是返回 false(QoS 约束太严) sd_count不为零(子域仍在上电)
若域从不下电(idle_states 中 Usage 为 0),检查:
- 是否设置了
GENPD_FLAG_ALWAYS_ON或GENPD_FLAG_RPM_ALWAYS_ON - 是否有设备设置了
rpm_always_on = true stay_on标志是否未被清除(sync_state是否已完成)- 设备是否都正确调用了
pm_runtime_put()
genpd 框架在关键路径上设置了 tracepoints(通过 pr_debug),可通过 dynamic_debug 动态开启:
# 开启 genpd 相关的 debug 日志
echo "file drivers/pmdomain/core.c +p" > /sys/kernel/debug/dynamic_debug/control
echo "file drivers/pmdomain/governor.c +p" > /sys/kernel/debug/dynamic_debug/control或通过 ftrace 追踪电源域状态变化:
# 追踪所有 PM domain 上下电事件
echo 1 > /sys/kernel/debug/tracing/events/power/enable
cat /sys/kernel/debug/tracing/trace-EBUSY(governor->suspend_ok 返回 false):
设备的 PM QoS resume latency 约束不满足,表示系统要求设备能够在 N 微秒内恢复,但当前 suspend + resume 延迟之和超过了这个约束。解决方法:
- 降低设备的 suspend/resume 延迟
- 放宽 PM QoS 约束
- 使用
pm_runtime_dont_use_autosuspend()禁用自动 suspend
-EPROBE_DEFER(域未就绪):
of_genpd_add_subdomain() 在找不到 provider 时返回 -ENOENT,但该函数内部将其转换为 -EPROBE_DEFER(drivers/pmdomain/core.c:2987):
return ret == -ENOENT ? -EPROBE_DEFER : ret;这保证了驱动 probe 会延迟到 provider 注册后重试。
WARN: Parent X of subdomain Y must be IRQ safe:
IRQ safe 子域(用于 CPU idle)的父域未设置 GENPD_FLAG_IRQ_SAFE。对于 CPU domain 的父域,必须同时设置 GENPD_FLAG_CPU_DOMAIN 或 GENPD_FLAG_IRQ_SAFE。
若 power_on_latency_ns 或 power_off_latency_ns 超过实际测量值,max_off_time_changed 被设为 true,触发 governor 重新计算。如果新的延迟值使得约束不满足,域可能被 governor 阻止下电。
可通过在 DT 中使用 idle-state-name 和准确的延迟值,或在驱动初始化时手动设置 genpd->states[i].power_on_latency_ns 来预设精确延迟,避免首次测量时的延迟误判。
| 函数 | 文件 | 行号 | 作用 |
|---|---|---|---|
pm_genpd_init |
drivers/pmdomain/core.c |
2394 | 初始化并注册一个 genpd |
pm_genpd_add_device |
drivers/pmdomain/core.c |
1971 | 将设备加入 genpd |
pm_genpd_remove_device |
drivers/pmdomain/core.c |
2037 | 从 genpd 移除设备 |
pm_genpd_add_subdomain |
drivers/pmdomain/core.c |
2203 | 建立父子域关系 |
pm_genpd_remove_subdomain |
drivers/pmdomain/core.c |
2221 | 解除父子域关系 |
pm_genpd_remove |
drivers/pmdomain/core.c |
2516 | 移除 genpd |
pm_genpd_inc_rejected |
drivers/pmdomain/core.c |
821 | 调整异步下电失败统计 |
genpd_power_on |
drivers/pmdomain/core.c |
1043 | 级联上电(递归) |
genpd_power_off |
drivers/pmdomain/core.c |
956 | 级联下电 |
_genpd_power_on |
drivers/pmdomain/core.c |
831 | 调用 power_on 回调+通知 |
_genpd_power_off |
drivers/pmdomain/core.c |
882 | 调用 power_off 回调+通知 |
genpd_sync_power_on |
drivers/pmdomain/core.c |
1468 | 系统睡眠路径同步上电 |
genpd_sync_power_off |
drivers/pmdomain/core.c |
1409 | 系统睡眠路径同步下电 |
genpd_runtime_suspend |
drivers/pmdomain/core.c |
1214 | RPM suspend 入口 |
genpd_runtime_resume |
drivers/pmdomain/core.c |
1290 | RPM resume 入口 |
genpd_prepare |
drivers/pmdomain/core.c |
1501 | 系统睡眠 prepare 阶段 |
genpd_suspend_noirq |
drivers/pmdomain/core.c |
1582 | 系统睡眠 noirq suspend |
genpd_resume_noirq |
drivers/pmdomain/core.c |
1635 | 系统睡眠 noirq resume |
genpd_complete |
drivers/pmdomain/core.c |
1714 | 系统睡眠 complete 阶段 |
genpd_switch_state |
drivers/pmdomain/core.c |
1735 | syscore/s2idle 路径 |
dev_pm_genpd_suspend |
drivers/pmdomain/core.c |
1770 | 同步 suspend(syscore) |
dev_pm_genpd_resume |
drivers/pmdomain/core.c |
1784 | 同步 resume(syscore) |
genpd_add_device |
drivers/pmdomain/core.c |
1923 | 内部设备加入实现 |
genpd_alloc_dev_data |
drivers/pmdomain/core.c |
1803 | 分配设备域数据 |
genpd_update_cpumask |
drivers/pmdomain/core.c |
1874 | 更新 CPU domain mask |
genpd_lock_init |
drivers/pmdomain/core.c |
2360 | 初始化锁(根据 flags) |
genpd_add_subdomain |
drivers/pmdomain/core.c |
2143 | 内部建立父子域关系 |
genpd_sd_counter_inc |
drivers/pmdomain/core.c |
278 | 子域上电计数 +1 |
genpd_sd_counter_dec |
drivers/pmdomain/core.c |
268 | 子域上电计数 -1 |
genpd_update_accounting |
drivers/pmdomain/core.c |
297 | 更新上电/空闲时间统计 |
genpd_reflect_residency |
drivers/pmdomain/core.c |
320 | 更新驻留时间统计 |
of_genpd_add_provider_simple |
drivers/pmdomain/core.c |
2670 | 注册单域 OF provider |
of_genpd_add_provider_onecell |
drivers/pmdomain/core.c |
2744 | 注册多域 OF provider |
of_genpd_del_provider |
drivers/pmdomain/core.c |
2846 | 注销 OF provider |
of_genpd_parse_idle_states |
drivers/pmdomain/core.c |
3461 | 从 DT 解析空闲状态 |
of_genpd_sync_state |
drivers/pmdomain/core.c |
3502 | 触发所有域下电尝试 |
genpd_get_from_provider |
drivers/pmdomain/core.c |
2896 | 从 provider 查找域 |
genpd_power_off_unused |
drivers/pmdomain/core.c |
1372 | 关闭未使用域(boot) |
genpd_debug_add |
drivers/pmdomain/core.c |
3899 | 创建 debugfs 条目 |
genpd_debug_init |
drivers/pmdomain/core.c |
3925 | 初始化 debugfs |
_genpd_set_performance_state |
drivers/pmdomain/core.c |
463 | 设置性能状态(内部) |
_genpd_reeval_performance_state |
drivers/pmdomain/core.c |
355 | 重新评估最大性能状态 |
genpd_xlate_performance_state |
drivers/pmdomain/core.c |
400 | 跨域性能状态转换 |
dev_pm_genpd_add_notifier |
drivers/pmdomain/core.c |
2063 | 注册上下电通知 |
dev_pm_genpd_remove_notifier |
drivers/pmdomain/core.c |
2109 | 注销上下电通知 |
default_suspend_ok |
drivers/pmdomain/governor.c |
56 | QoS 约束检查 |
_default_power_down_ok |
drivers/pmdomain/governor.c |
270 | 延迟预算检查(带缓存) |
__default_power_down_ok |
drivers/pmdomain/governor.c |
177 | 延迟预算检查(核心) |
update_domain_next_wakeup |
drivers/pmdomain/governor.c |
126 | 聚合下次唤醒时刻 |
cpu_power_down_ok |
drivers/pmdomain/governor.c |
347 | CPU domain 下电检查 |
cpu_system_power_down_ok |
drivers/pmdomain/governor.c |
428 | CPU domain 系统睡眠检查 |
scmi_pd_power_on |
drivers/pmdomain/arm/scmi_pm_domain.c |
32 | SCMI 上电回调 |
scmi_pd_power_off |
drivers/pmdomain/arm/scmi_pm_domain.c |
37 | SCMI 下电回调 |
scmi_pm_domain_probe |
drivers/pmdomain/arm/scmi_pm_domain.c |
42 | SCMI genpd driver probe |
rockchip_pmu_block |
drivers/pmdomain/rockchip/pm-domains.c |
266 | 阻止 PMU 切换(DMC) |
由 Claude Code 分析生成