Skip to content

Latest commit

 

History

History
2457 lines (1929 loc) · 94.5 KB

File metadata and controls

2457 lines (1929 loc) · 94.5 KB

Linux 内核 Generic Power Domain(genpd)框架深度分析

目录

  1. 框架概述
  2. 源码文件分布
  3. 整体架构:电源域树
  4. 核心数据结构
  5. 锁机制
  6. 引用计数与级联上电:genpd_power_on 递归流程
  7. 级联下电:genpd_power_off 流程
  8. 电源通知机制
  9. Governor:simple_qos 与 cpu governor
  10. Runtime PM 与 genpd 集成
  11. 设备树 Binding
  12. 初始化流程:pm_genpd_init
  13. 性能状态管理
  14. 系统睡眠集成
  15. SCMI 电源域后端
  16. 调试接口:/sys/kernel/debug/pm_genpd/
  17. genpd 标志位汇总
  18. 关键设计决策与注意事项
  19. 电源域状态机详解
  20. 设备绑定与 device_link 机制
  21. QoS 约束传播机制
  22. genpd_governor_data 与缓存机制
  23. 系统挂起/恢复路径完整分析
  24. Rockchip 电源域驱动实现分析
  25. SCMI genpd 驱动完整分析
  26. PSCI 与 ARM CPU 电源管理协作
  27. genpd 设备生命周期管理
  28. Provider 注册与 sync_state 机制
  29. HW mode 硬件自动电源管理
  30. genpd 与 PM clock 框架集成
  31. 多电源域设备(dev_pm_domain_list)
  32. GDB 调试辅助脚本
  33. 典型使用模式与驱动开发指南
  34. 性能分析与常见问题排查
  35. 附录:关键函数索引

1. 框架概述

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 等固件接口将电源控制委托给片外固件

2. 源码文件分布

文件 说明
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/


3. 整体架构:电源域树

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_listdrivers/pmdomain/core.c:54):static LIST_HEAD(gpd_list),所有已注册的 genpd 挂载于此
  • gpd_list_lockdrivers/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_countatomic_t,记录当前处于上电状态的子域数量

子域上电时,通过 genpd_sd_counter_inc() 增加父域的 sd_count;子域下电时,通过 genpd_sd_counter_dec() 减少。只有 sd_count == 0 且所有设备均已 RPM suspend,父域才可下电。


4. 核心数据结构

4.1 generic_pm_domain

定义在 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 通知链。

4.2 genpd_power_state:空闲状态定义

定义在 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;
}

4.3 pm_domain_data 与 generic_pm_domain_data

pm_domain_datainclude/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_datainclude/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-1961

4.4 gpd_link:父子域连接

include/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 中的遍历顺序一致,防止死锁。

4.5 gpd_timing_data:延迟预算数据

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_nsresume_latency_nsgenpd_runtime_suspend/resume 中实际测量,如果测量值超过已记录值则更新(drivers/pmdomain/core.c:1256-1264drivers/pmdomain/core.c:1334-1343)。


5. 锁机制

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 内核下也不可调度)

6. 引用计数与级联上电:genpd_power_on 递归流程

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() 会立即返回,拒绝下电。这保证了父域不会在任一子域仍然上电时被关断。


7. 级联下电: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_syncdrivers/pmdomain/core.c:1392)。


8. 电源通知机制

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 从链表中移除。


9. Governor:simple_qos 与 cpu governor

9.1 dev_power_governor 接口

定义在 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

9.2 simple_qos_governor

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 约束:

  1. 若约束未变化,返回缓存结果(td->cached_suspend_ok
  2. 读取 __dev_pm_qos_resume_latency(dev)(单位 µs,转换为 ns)
  3. 若设备有子设备(!dev->power.ignore_children),递归取 dev_update_qos_constraint() 中各子设备的约束最小值
  4. 最终计算:effective_constraint = constraint - suspend_latency - resume_latency
  5. 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)实现:

  1. 调用 update_domain_next_wakeup() 收集所有设备和子域的下次唤醒时刻
  2. 如果设置了 GENPD_FLAG_MIN_RESIDENCY,从最深状态向上找第一个满足 next_wakeup - now >= power_off_latency + residency 的状态
  3. 遍历所有子域和设备,验证它们的 max_off_time_ns 是否满足 off_on_time_ns(下电+上电延迟总和)
  4. 计算并缓存 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;

9.3 CPU domain governor

cpu_power_down_ok()drivers/pmdomain/governor.c:347)增加了以下 CPU 特有检查:

  1. 先调用 _default_power_down_ok() 验证 QoS 约束
  2. 收集 CPU QoS latency 约束(wakeup_constraintglobal_constraint
  3. 遍历所有在线 CPU 的 cpuidle_devices->next_hrtimer,取最早唤醒时刻
  4. 计算空闲时长 idle_duration_ns,从 governor 已选 state_idx 开始向上找第一个满足驻留时间和唤醒延迟约束的状态
  5. 调用 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() 约束的最深状态。

9.4 延迟预算计算流程

设备的 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()

10. Runtime PM 与 genpd 集成

10.1 genpd_runtime_suspend

完整实现在 drivers/pmdomain/core.c:1214-1280,在 pm_genpd_init 时被注册为 genpd->domain.ops.runtime_suspenddrivers/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

10.2 genpd_runtime_resume

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()

11. 设备树 Binding

11.1 power-domains 属性解析

设备树中的典型声明:

/* 电源域控制器节点 */
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 附近)完整流程:

  1. 解析第 indexpower-domains phandle 参数
  2. 调用 genpd_get_from_provider(&pd_args)drivers/pmdomain/core.c:2896)查找 genpd provider 并获得对应 generic_pm_domain
  3. 调用 genpd_add_device(pd, dev, base_dev) 将设备加入域
  4. 设置 dev->pm_domain->detach = genpd_dev_pm_detach
  5. 可选:调用 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)。

11.2 of_genpd_parse_idle_states

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-stateavailable 的节点,调用 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>;
};

12. 初始化流程:pm_genpd_init

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 条目

13. 性能状态管理

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:前者是子域换算到父域坐标系的值,后者是子域内部设备的投票值。


14. 系统睡眠集成

系统 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)。


15. SCMI 电源域后端

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 字节短名称限制的扩展名称。


16. 调试接口:/sys/kernel/debug/pm_genpd/

调试 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_adddrivers/pmdomain/core.c:3899):

文件 说明 实现函数
current_state 当前状态:onoff-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:本可进入更深状态但选了此状态(不够深)的次数

17. genpd 标志位汇总

定义在 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 时下电

18. 关键设计决策与注意事项

18.1 stay_on 机制

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 并尝试下电。

18.2 性能状态的上调/下调顺序

_genpd_set_performance_state() 中(drivers/pmdomain/core.c:472-497):

  • 上调时:先通知父域提升性能状态,确保电源轨电压足够,再提升本域
  • 下调时:先降低本域性能状态,再降低父域(避免父域在本域仍高频运行时断电)

18.3 设备 IRQ safe 与域的兼容性

若设备是 irq_safepower.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)。

18.4 pm_genpd_inc_rejected

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);
}

18.5 Boot 时关闭未使用域

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);

18.6 性能状态与 OPP 跨域转换

当子域和父域使用不同的 OPP 表时,genpd_xlate_performance_state()drivers/pmdomain/core.c:400)通过 dev_pm_opp_xlate_performance_state() 将子域的性能状态值映射到父域的等效状态值。


19. 电源域状态机详解

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_OFFstate_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;
}

注意:这个函数在状态转换之前调用,所以"当前状态"是切换前的状态,统计的是切换前状态的持续时间。


20. 设备绑定与 device_link 机制

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.cgenpd_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() 解绑。


21. QoS 约束传播机制

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() 中收集
}

22. genpd_governor_data 与缓存机制

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;
}

这个多级缓存机制避免了每次下电决策都重新遍历整个域树,在设备数量多时显著提升性能。


23. 系统挂起/恢复路径完整分析

系统睡眠路径涉及多个不同的"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_offgenpd_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 参数) 必须持锁

24. Rockchip 电源域驱动实现分析

Rockchip 电源域驱动(drivers/pmdomain/rockchip/pm-domains.c)是最复杂也最具代表性的 genpd 后端实现之一,支持从 RK3036 到 RK3588 几乎所有 Rockchip SoC。

24.1 核心数据结构

rockchip_pm_domaindrivers/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_infodrivers/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 掩码
    // ...偏移量字段...
};

24.2 下电时序

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 寄存器。

24.3 与 DMC 的协作

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;
}

24.4 域描述宏体系

Rockchip 使用一系列宏(DOMAIN_MDOMAIN_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()


25. SCMI genpd 驱动完整分析

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=falsepm_genpd_init,这会设置 stay_on=true,防止在 sync_state 之前被错误关断。


26. PSCI 与 ARM CPU 电源管理协作

ARM 平台上,CPU 电源域通常由 PSCI(Power State Coordination Interface)管理,genpd 通过 GENPD_FLAG_CPU_DOMAIN 标志与 PSCI 协作。

26.1 CPU domain 的注册

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 完成,不需要显式回调)

26.2 last-man-standing 算法

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);
}

26.3 IPI 检查

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 丢失或延迟过大。

26.4 PSCI 电源状态编码

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)。


27. genpd 设备生命周期管理

27.1 设备加入域

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)

27.2 设备从域移除

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)

27.3 域的移除

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)

28. Provider 注册与 sync_state 机制

28.1 Provider 注册流程

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

28.2 sync_state 状态机

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 并尝试下电。

28.3 genpd bus 初始化

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);

29. HW mode 硬件自动电源管理

某些 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 开销。


30. genpd 与 PM clock 框架集成

当 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


31. 多电源域设备(dev_pm_domain_list)

现代 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 结构中。若任一域绑定失败,函数自动清理已绑定的域。


32. GDB 调试辅助脚本

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

33. 典型使用模式与驱动开发指南

33.1 最简单的 genpd 驱动

适用于纯寄存器控制、无时钟管理需求的简单电源域控制器:

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);
}

33.2 父子域层级配置

/* 注册父域和子域 */
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

33.3 设备驱动侧使用

大多数设备驱动不需要直接调用 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)
};

34. 性能分析与常见问题排查

34.1 通过 debugfs 排查

查看所有域的状态

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_statesUsage 为 0),检查:

  • 是否设置了 GENPD_FLAG_ALWAYS_ONGENPD_FLAG_RPM_ALWAYS_ON
  • 是否有设备设置了 rpm_always_on = true
  • stay_on 标志是否未被清除(sync_state 是否已完成)
  • 设备是否都正确调用了 pm_runtime_put()

34.2 内核追踪(tracing)

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

34.3 常见错误分析

-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_DEFERdrivers/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_DOMAINGENPD_FLAG_IRQ_SAFE

34.4 延迟过长导致的问题

power_on_latency_nspower_off_latency_ns 超过实际测量值,max_off_time_changed 被设为 true,触发 governor 重新计算。如果新的延迟值使得约束不满足,域可能被 governor 阻止下电。

可通过在 DT 中使用 idle-state-name 和准确的延迟值,或在驱动初始化时手动设置 genpd->states[i].power_on_latency_ns 来预设精确延迟,避免首次测量时的延迟误判。


35. 附录:关键函数索引

函数 文件 行号 作用
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 分析生成