1. 课程导入

在嵌入式、移动终端、车载与服务器场景中,Linux 设备的功耗与稳定性往往同时被考核:既要“该睡就睡、该醒就醒”,又要保证设备在热插拔、系统休眠、驱动卸载等过程中不丢状态、不崩溃。本课程围绕一条主线展开——把设备的电源状态变化与设备生命周期事件放在同一套协同模型中理解与实现。学完本课程,你应能读懂并编写 Runtime PM、System Sleep、设备树电源域、时钟/稳压器/复位等资源管理代码,并能定位“休眠后设备不工作”“卸载驱动后仍耗电”“唤醒源失效”等典型问题。

核心目标:建立“电源状态机 + 生命周期事件 + 资源依赖”三位一体的分析能力,能够从设备模型、驱动回调、电源域拓扑三个层面解释并修复功耗与稳定性缺陷。

一、核心概念:设备、电源状态与生命周期

Linux 设备模型用 struct device 描述一个设备实例,用 struct device_driver 描述驱动,用总线(platform、i2c、spi、pci、usb 等)完成匹配与绑定。电源管理并不是独立模块,而是嵌入在设备模型中的一组回调与状态字段:

  • Runtime PM:设备在系统运行期间的动态电源管理,核心是 pm_runtime_get_sync() / pm_runtime_put_sync() 的引用计数,以及 runtime_suspend / runtime_resume 回调。
  • System Sleep:系统级休眠(suspend-to-idle、suspend-to-RAM、hibernation),对应 suspend / resume 回调,按设备树或总线顺序遍历设备。
  • 电源域(Power Domain):多个设备共享同一供电/时钟/复位资源,形成层级依赖,典型实现为 genpd(Generic Power Domain)。
  • 生命周期事件:probe、remove、shutdown、runtime_suspend、system_suspend、wakeup 等,它们与电源状态迁移一一对应。

理解这些概念的关键,是认识到“电源状态”不是孤立的布尔值,而是由引用计数、子设备状态、父域状态共同决定的复合状态。一个设备能否进入低功耗,取决于它自身是否空闲、子设备是否已挂起、父电源域是否允许关闭。

二、整体知识框架与协同关系

下图给出本课程的知识框架:左侧是设备生命周期事件,右侧是电源状态迁移,中间通过设备模型与电源域拓扑耦合。图中同时标出 Runtime PM 与 System Sleep 两条路径,以及它们共享的回调与资源。

设备生命周期事件 × 电源状态迁移 协同框架 设备生命周期事件 probe / 绑定驱动 runtime_resume runtime_suspend system_suspend remove / shutdown wakeup 唤醒源 设备模型与电源域拓扑 struct device dev_pm_info / wakeup / links struct device_driver pm 回调 / of_match_table genpd 电源域 domain->power_on/off 资源依赖 clk / regulator / reset / gpio 设备链接 device_link consumer / supplier 引用计数决定状态迁移 电源状态迁移 Active(全开) Runtime Suspend System Suspend Deep Sleep / Off Resume 恢复 唤醒后状态重建 协同要点:生命周期事件触发状态迁移;电源域与资源依赖决定迁移能否成功;引用计数与设备链接保证顺序正确

从图中可以看到,生命周期事件是“触发源”,电源状态是“结果”,而设备模型与电源域拓扑是“约束条件”。三者缺一不可:只改状态机不理解依赖,会出现父域已关、子设备仍访问寄存器导致总线挂死;只处理生命周期不管理引用计数,会出现设备被提前挂起、数据传输失败。

三、方法流程:从事件到状态的协同路径

实际开发中,推荐按以下流程建立协同逻辑:

  1. 识别设备依赖:通过设备树 power-domains、clocks、regulators 等属性,列出该设备依赖的父域与资源。
  2. 实现 Runtime PM 回调:在 runtime_suspend 中关闭时钟、释放复位、保存寄存器;在 runtime_resume 中反向恢复。
  3. 接入引用计数:在数据传输前后成对调用 pm_runtime_get_sync() 与 pm_runtime_put_sync(),避免设备在传输中被挂起。
  4. 处理 System Sleep:实现 suspend / resume,必要时用 pm_runtime_force_suspend() 统一两条路径。
  5. 配置唤醒源:通过 device_set_wakeup_capable() 与 enable_irq_wake() 让设备在低功耗下仍能唤醒系统。
  6. 验证与观测:使用 /sys/kernel/debug/pm_genpd/、powertop、dmesg 观察状态迁移与失败原因。
/* 典型 Runtime PM 回调骨架 */
static int mydev_runtime_suspend(struct device *dev)
{
    struct mydev *d = dev_get_drvdata(dev);
    clk_disable_unprepare(d->clk);
    regulator_disable(d->vdd);
    return 0;
}

static int mydev_runtime_resume(struct device *dev)
{
    struct mydev *d = dev_get_drvdata(dev);
    regulator_enable(d->vdd);
    clk_prepare_enable(d->clk);
    return 0;
}

static const struct dev_pm_ops mydev_pm_ops = {
    SET_RUNTIME_PM_OPS(mydev_runtime_suspend,
                       mydev_runtime_resume, NULL)
    SET_SYSTEM_SLEEP_PM_OPS(mydev_suspend, mydev_resume)
};

四、案例场景与常见误区

以一个 I2C 温度传感器为例:系统空闲时希望它进入 Runtime Suspend,关闭供电;但应用层每秒读取一次温度。若驱动在读取前未调用 pm_runtime_get_sync(),设备可能正处于挂起中,I2C 传输会超时。正确做法是在读取函数入口获取引用,读取完成后释放,让 Runtime PM 在两次读取之间自动挂起。

误区现象根因排查手段
休眠后设备不工作resume 未恢复时钟/寄存器检查 resume 回调与 dmesg
卸载驱动后仍耗电remove 未释放 regulator/clk查看 /sys/kernel/debug/clk
唤醒源失效未调用 enable_irq_wake()检查 /sys/kernel/debug/wakeup_sources
父域关闭导致子设备异常缺少 device_link 依赖检查设备树 power-domains
常见错误:把 Runtime PM 与 System Sleep 当成两套互不相关的机制。实际上它们共享同一组资源与回调,若在 System Sleep 路径中重复关闭已由 Runtime PM 关闭的时钟,会导致引用计数不平衡,出现“无法再次唤醒”的问题。
学习建议:每学一个概念,都在开发板上用 cat /sys/kernel/debug/pm_genpd/pm_genpd_summary 观察一次真实状态变化,把抽象回调与具体功耗数字对应起来。

本课程后续将沿着“设备模型 → Runtime PM → System Sleep → 电源域与资源 → 唤醒与调试”的路线逐步展开。掌握本节框架后,你在面对任何功耗问题时,都可以先问三个问题:当前处于哪个生命周期事件?目标电源状态是什么?依赖的资源与父域是否允许这次迁移?这三个问题,就是设备电源与生命周期协同的分析起点。