2. Linux电源管理基础

在嵌入式Linux系统中,电源管理(Power Management, PM)是决定设备续航、散热和性能的关键环节。本章将系统梳理Linux电源管理的三大支柱:ACPI(高级配置与电源接口)、DT(设备树)和PM子系统,并深入分析驱动开发与电源管理之间的协作关系。理解这些基础,是后续实现功耗感知调度与驱动节能方案的先决条件。

2.1 电源管理架构总览

Linux电源管理是一个分层、模块化的体系,从上到下依次为:用户空间策略、内核PM核心框架、硬件抽象层(ACPI/DT)和物理硬件。下图展示了这一架构的核心组件与数据流。

Linux电源管理分层架构 用户空间策略层 cpufreq governor | cpuidle governor | powercapping | udev 规则 策略决策:性能 vs 功耗权衡、调度器提示、设备运行时状态控制 内核PM核心框架 PM核心(suspend/resume) | 运行时PM(runtime PM) | 时钟框架(clk) 稳压器框架(regulator) | 功耗管理域(PM Domain) | CPUIdle / CPUFreq 提供通用API:pm_runtime_get/put、device_set_wakeup_capable、suspend_ops 硬件抽象层(ACPI / DT) ACPI:_PSx、_PRx、_CST、_PPC 对象 | 设备树:power-domains、clocks、regulators 描述硬件拓扑、电源状态、时钟关系、唤醒能力 提供标准化接口,隔离硬件差异 物理硬件层 SoC | PMIC | 外设 | 时钟源 | 电源轨 | 传感器 驱动通过PM API交互

从上图可以看出,驱动位于内核PM核心框架与硬件抽象层之间,既需要调用PM核心API管理设备状态,也需要解析ACPI或DT中的电源描述信息。驱动开发者必须理解这三层之间的协作关系,才能写出真正节能的代码。

2.2 ACPI:高级配置与电源接口

ACPI(Advanced Configuration and Power Interface)是x86/ARM64服务器和PC领域的事实标准电源管理接口。它通过固件(BIOS/UEFI)提供一系列描述性表和方法,操作系统无需了解硬件细节即可管理电源。

2.2.1 ACPI核心概念

概念 说明 驱动关注点
ACPI表 DSDT、SSDT、FADT、MADT等,描述硬件拓扑和电源能力 驱动通过acpi_device_id匹配设备
控制方法 AML字节码,如 _PS0(开启电源)、_PS3(关闭电源) 驱动调用acpi_evaluate_object执行方法
设备电源状态 D0(全开)~ D3cold(完全断电) 驱动在suspend/resume回调中切换状态
处理器电源状态 C0(运行)~ C3(深度睡眠) cpuidle驱动使用_CST对象获取状态参数
性能状态 P0(最高性能)~ Pn(最低性能) cpufreq驱动使用_PPC/_PSS调节频率电压

2.2.2 驱动中的ACPI电源管理示例

一个典型的ACPI兼容驱动,需要在probe阶段获取设备句柄,并在suspend/resume中调用控制方法:

static int my_driver_suspend(struct device *dev)
{
    acpi_handle handle = ACPI_HANDLE(dev);
    if (handle) {
        // 执行 _PS3 方法,将设备置于D3hot状态
        acpi_evaluate_object(handle, "_PS3", NULL, NULL);
    }
    // 关闭设备时钟、电源轨
    clk_disable_unprepare(my_clk);
    regulator_disable(my_reg);
    return 0;
}

static int my_driver_resume(struct device *dev)
{
    acpi_handle handle = ACPI_HANDLE(dev);
    // 恢复时钟和电源
    regulator_enable(my_reg);
    clk_prepare_enable(my_clk);
    if (handle) {
        // 执行 _PS0 方法,将设备恢复到D0状态
        acpi_evaluate_object(handle, "_PS0", NULL, NULL);
    }
    return 0;
}

static SIMPLE_DEV_PM_OPS(my_pm_ops, my_driver_suspend, my_driver_resume);
实践要点:在ACPI系统中,驱动应优先使用 acpi_device_set_power() 等封装函数,而非直接调用 _PSx 方法。封装函数会自动处理状态转换约束和依赖设备。

2.3 设备树(DT)与电源管理

设备树(Device Tree, DT)是ARM嵌入式Linux世界的主流硬件描述方式。与ACPI不同,DT是静态的、编译为二进制(DTB)的文本文件,由bootloader传递给内核。电源管理信息通过特定的属性和节点描述。

2.3.1 DT中关键的电源管理属性

属性/节点 用途 驱动解析方式
clocks 指定设备使用的时钟源和时钟门控 devm_clk_get()
power-domains 关联到电源管理域,实现整体电源门控 dev_pm_domain_attach()
regulators 指定设备使用的稳压器(电压调节) devm_regulator_get()
wakeup-source 标记设备可作为唤醒源 device_init_wakeup()
operating-points-v2 定义工作频率-电压对(OPP) dev_pm_opp_of_add_table()

2.3.2 DT电源管理域(PM Domain)示例

下图展示了一个典型的SoC电源管理域拓扑,以及设备如何挂载到不同域上。

设备树电源管理域拓扑示例 always-on 域(VDD_CORE) GPU 域(VDD_GPU) ISP 域(VDD_ISP) DSP 域(VDD_DSP) gpu@f0000000 isp@f1000000 dsp@f2000000 DT中的电源域描述示例 gpu_pd: power-domain@0 { compatible = "my-soc,gpu-power-domain"; #power-domain-cells = <0>; };

在驱动中,通过 dev_pm_domain_attach() 将设备关联到电源域,内核会在设备suspend时自动关闭该域电源:

static int my_driver_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;

    // 关联到电源管理域
    dev_pm_domain_attach(dev, true);

    // 获取时钟和稳压器
    my_clk = devm_clk_get(dev, "bus");
    my_reg = devm_regulator_get(dev, "vdd");

    // 启用运行时PM
    pm_runtime_enable(dev);
    return 0;
}

static int my_driver_runtime_suspend(struct device *dev)
{
    // 关闭时钟和稳压器
    clk_disable_unprepare(my_clk);
    regulator_disable(my_reg);
    return 0;
}

static int my_driver_runtime_resume(struct device *dev)
{
    regulator_enable(my_reg);
    clk_prepare_enable(my_clk);
    return 0;
}

static const struct dev_pm_ops my_pm_ops = {
    SET_RUNTIME_PM_OPS(my_driver_runtime_suspend,
                       my_driver_runtime_resume, NULL)
};

2.4 Linux PM子系统核心框架

Linux PM子系统是内核中负责电源管理策略和机制的核心代码集合,主要包括以下模块:

  • PM核心(pm核心):管理系统级suspend/resume流程,提供 suspend_ops 注册接口。
  • 运行时PM(runtime PM):允许设备在不使用时自动进入低功耗状态,通过引用计数控制。
  • CPUIdle:管理CPU空闲状态(C-states),在空闲时切换处理器到低功耗模式。
  • CPUFreq:动态调节CPU频率和电压(DVFS),根据负载调整性能。
  • 时钟框架(clk):管理设备时钟的开启/关闭和频率调整。
  • 稳压器框架(regulator):管理电压调节器,控制设备供电电压。
  • 功耗管理域(PM Domain):管理一组设备的整体电源门控。

2.4.1 运行时PM(Runtime PM)核心流程

运行时PM是驱动开发中最常接触的PM机制。其核心思想是:当设备不再被使用时,自动挂起;当需要访问时,自动唤醒。下图展示了运行时PM的状态机。

运行时PM状态机 ACTIVE 引用计数 > 0 SUSPENDING 调用 runtime_suspend SUSPENDED 引用计数 = 0 RESUMING 调用 runtime_resume pm_runtime_put() 回调成功 pm_runtime_get() 回调成功 回调失败
驱动开发黄金法则:在probe函数中调用 pm_runtime_enable(),在remove函数中调用 pm_runtime_disable()。每次访问硬件前调用 pm_runtime_get_sync(),访问完成后调用 pm_runtime_put()。这能确保设备在不使用时自动进入低功耗状态。

2.5 驱动与电源管理的关系

驱动是电源管理策略的最终执行者。无论上层的governor如何决策,最终都需要驱动来实现具体的电源操作。驱动与PM的关系可以归纳为以下三个层次:

层次 驱动职责 关键API/机制
设备级PM 管理单个设备的时钟、电源轨、复位信号 clk_prepare_enable() / clk_disable_unprepare()
regulator_enable() / regulator_disable()
运行时PM 根据使用情况动态挂起/唤醒设备 pm_runtime_get_sync() / pm_runtime_put()
实现 runtime_suspend / runtime_resume 回调
系统级PM 响应系统suspend/resume事件,保存/恢复设备状态 实现 suspend / resume 回调
使用 dev_pm_ops 结构体注册

2.5.1 驱动PM回调的完整实现模式

一个符合最佳实践的驱动,应该同时实现系统级PM和运行时PM回调,并利用PM核心的自动休眠机制:

static int my_dev_suspend(struct device *dev)
{
    // 1. 保存硬件状态(寄存器上下文)
    my_dev_save_context(dev);

    // 2. 关闭时钟
    clk_disable_unprepare(my_clk);

    // 3. 关闭电源轨
    regulator_disable(my_reg);

    // 4. 设置唤醒源(如果需要)
    if (device_may_wakeup(dev))
        enable_irq_wake(my_irq);

    return 0;
}

static int my_dev_resume(struct device *dev)
{
    // 1. 恢复电源轨
    regulator_enable(my_reg);

    // 2. 恢复时钟
    clk_prepare_enable(my_clk);

    // 3. 恢复硬件状态
    my_dev_restore_context(dev);

    // 4. 禁用唤醒源
    if (device_may_wakeup(dev))
        disable_irq_wake(my_irq);

    return 0;
}

static int my_dev_runtime_suspend(struct device *dev)
{
    // 运行时挂起:只关闭时钟,保留电源(快速唤醒)
    clk_disable_unprepare(my_clk);
    return 0;
}

static int my_dev_runtime_resume(struct device *dev)
{
    clk_prepare_enable(my_clk);
    return 0;
}

static const struct dev_pm_ops my_pm_ops = {
    SET_SYSTEM_SLEEP_PM_OPS(my_dev_suspend, my_dev_resume)
    SET_RUNTIME_PM_OPS(my_dev_runtime_suspend,
                       my_dev_runtime_resume, NULL)
};
常见陷阱:不要在runtime_suspend中执行耗时操作(如msleep),这会导致调度延迟。运行时PM回调应设计为快速(微秒级),而系统级suspend可以容忍毫秒级延迟。如果设备需要较长时间才能进入低功耗状态,考虑使用 pm_runtime_autosuspend() 机制。

2.6 本章小结

本章建立了Linux电源管理的整体知识框架:

  • ACPI 是x86/ARM64服务器的标准,通过AML方法描述电源状态,驱动通过 acpi_evaluate_object 交互。
  • 设备树 是嵌入式ARM的主流方案,通过 power-domains、clocks、regulators 等属性描述电源拓扑。
  • PM子系统 提供运行时PM、CPUIdle、CPUFreq等核心框架,驱动通过标准API接入。
  • 驱动是PM的执行者,需要在probe/remove、suspend/resume、runtime PM回调中正确管理时钟、电源和状态。